改进版通用dao实现三:分页实现

前面我们已经实现了dao的增删改查功能,但是在封装的查询方法中并没有分页方法,那么我们又要如何来实现呢?

spring-jdbc2

先来看看分页查询的代码:

@Test
public void queryList1() {
    User user = new User();
    PageControl.performPage(user);
    jdbcDao.queryList(user);
    Pager pager = PageControl.getPager();
    List<User> users = pager.getList(User.class);
    System.out.println("总记录数:" + pager.getItemsTotal());
    for (User us : users) {
        System.out.println(us.getUserName() + " " + us.getUserAge());
    }
}
@Test
public void queryList2() {
    PageControl.performPage(1, 10);
    Criteria criteria = Criteria.create(User.class).include("userName", "userId")
        .where("userName", new Object[]{"liyd"}).asc("userId");
    jdbcDao.queryList(criteria);
    List<User> users = PageControl.getPager().getList(User.class);
    for (User us : users) {
        System.out.println(us.getUserId() + " " + us.getUserName() + " " + us.getUserAge());
    }
}

没错,只需要在前面加一行PageControl.performPage(…);就可以实现分页查询,这是一个完全解耦的实现。

它的设计思维参考了osc上的mybatis分页插件,非常不错的想法,内部使用了拦截器+ThreadLocal的方式来实现。

因为分页必定是查询列表,而spring JdbcTemlate所有的查询方法都是以query开头的,我们只需要拦截这类方法即可。

原理是先调用PageControl.performPage(…)方法将分页信息存放在ThreadLocal中,执行方法时查看ThreadLocal中有无分页信息,有则进行分页,没有则不分页。

PageControl.performPage方法提供了几个重载:

public static void performPage(Pageable pageable) {
	performPage(pageable.getCurPage(), pageable.getItemsPerPage(), true);
}
public static void performPage(Pageable pageable, boolean isGetCount) {
	performPage(pageable.getCurPage(), pageable.getItemsPerPage(), isGetCount);
}
public static void performPage(int curPage, int itemsPerPage) {
	performPage(curPage, itemsPerPage, true);
}
public static void performPage(int curPage, int itemsPerPage, boolean isGetCount) {
	Pager pager = new Pager();
	pager.setCurPage(curPage);
	pager.setItemsPerPage(itemsPerPage);
	GET_ITEMS_TOTAL.set(isGetCount);
	LOCAL_PAGER.set(pager);
}

可以传入Pageable对象或直接的页码和每页条数,其实Pageable对象里面也仅仅定义了这两个属性而已:

public class Pageable implements Serializable {
    /** serialVersionUID */
    private static final long serialVersionUID = 4060766214127186912L;
    /** 每页显示条数 */
    protected int             itemsPerPage     = 20;
    /** 当前页码 */
    protected int             curPage          = 1;
	//......
}

一般的做法是让实体类都继承于Pageable,查询时只需要一个页码和每页条数的信息,它会自动帮你算好所需的一切。

最后一个参数isGetCount表示是否要进行总记录数的查询,false表示不查询,默认为true。这在有时候只要数据并不需要知道总记录数时可以减少一次数据库的count查询。

下面是分页拦截器PageControl的核心代码:

public Object pagerAspect(ProceedingJoinPoint pjp) throws Throwable {
	if (LOCAL_PAGER.get() == null) {
		return pjp.proceed();
	}
	JdbcTemplate target = (JdbcTemplate) pjp.getTarget();
	if (DATABASE == null) {
		DatabaseMetaData metaData = target.getDataSource().getConnection().getMetaData();
		DATABASE = metaData.getDatabaseProductName().toUpperCase();
	}
	Object[] args = pjp.getArgs();
	String querySql = (String) args[0];
	Pager pager = LOCAL_PAGER.get();
	args[0] = this.getPageSql(querySql, pager);
	if (GET_ITEMS_TOTAL.get()) {
		String countSql = this.getCountSql(querySql);
		Object[] countArgs = null;
		for (Object obj : args) {
			if (obj instanceof Object[]) {
				countArgs = (Object[]) obj;
			}
		}
		int itemsTotal = target.queryForInt(countSql, countArgs);
		pager.setItemsTotal(itemsTotal);
	}
	Object result = pjp.proceed(args);
	pager.setList((List<?>) result);
	return null;
}

这里在首次查询时会获取数据库的信息,用来判断数据库类型方便组装分页sql语句。

下面是对查询方法参数的一个动态修改,用于把正常的列表sql查询换成分页的sql查询,另外如果有获取总记录数的需要,动态设置参数执行一次count查询。

最后,这里修改了方法的返回值,统一返回null,把数据保存在pager对象当中。这里返回的值不止一个(列表、总记录数)是一个原因,还有一个原因就是强制数据只能通过PageControl.getPager来获取,用来清空保存在ThreadLocal中的分页信息,如页码和每页的记录数。

就这么简单,一个方便快捷的分页方式就实现了。

最后要说的一点就是,因为分页使用了拦截器的方式,而Java中拦截的代理对象都是基于接口的,所以一旦使用分页,我们前面dao中的JdbcTemplate对象就会出现无法注入的异常。

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcDao' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'com.sun.proxy.$Proxy14 implementing org.springframework.jdbc.core.JdbcOperations,org.springframework.beans.factory.InitializingBean,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised' to required type 'org.springframework.jdbc.core.JdbcTemplate' for property 'jdbcTemplate'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy14 implementing org.springframework.jdbc.core.JdbcOperations,org.springframework.beans.factory.InitializingBean,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [org.springframework.jdbc.core.JdbcTemplate] for property 'jdbcTemplate': no matching editors or conversion strategy found

这是因为类型不匹配,解决方式也很简单,把JdbcTemplate换成接口对象即可,这点spring应该也早就想到了,所以接口也早定义好了,我们只需要换个声明方式使用JdbcOperations即可:

public class JdbcDaoImpl implements JdbcDao {
    /** spring jdbcTemplate 对象 */
    protected JdbcOperations jdbcTemplate;
	//.....
}

因为分页的拦截器是基于jdbcTemplate的,不光是这个通用dao,你自己实现的所有dao只要使用了jdbcTemplate,就可以使用这个分页。

至此,应该是都介绍完了,还有什么不明白的地方可以自己看看源代码,也欢迎交流,更欢迎提出bug和建议,谢谢!

本文标题:改进版通用dao实现三:分页实现
本文来自码农的士,转载请注明出处
交流QQ群:32261424
Previous:
Next:

发表评论

,将以游客形式发表

网友最新评论(10)

  1. 1楼 jackshen 发表于 2015-06-22 12:06:01 回复此评论
    lz,我在使用分页时并没有触发分页拦截器,是什么原因造成的呢?其他增删改查方法都可以正常使用~!
  2. 2楼 jackshen 发表于 2015-06-22 12:27:45 回复此评论
    引用来自“jackshen”的评论
    lz,我在使用分页时并没有触发分页拦截器,是什么原因造成的呢?其他增删改查方法都可以正常使用~!
    问题已解决,是因为没有开启aop注解
  3. 3楼 101.233.33.*** [游客] 发表于 2015-08-01 22:50:57 回复此评论
    引用来自“jackshen”的评论
    lz,我在使用分页时并没有触发分页拦截器,是什么原因造成的呢?其他增删改查方法都可以正常使用~!
    引用来自“jackshen”的评论
    问题已解决,是因为没有开启aop注解
    kkkk
  4. 4楼 101.233.33.*** [游客] 发表于 2015-08-01 22:51:38 回复此评论
    引用来自“jackshen”的评论
    lz,我在使用分页时并没有触发分页拦截器,是什么原因造成的呢?其他增删改查方法都可以正常使用~!
    引用来自“jackshen”的评论
    问题已解决,是因为没有开启aop注解
    引用来自“101.233.33.***”的评论
    kkkk
    eeee
  5. 5楼 xiehui 发表于 2015-08-15 10:23:34 回复此评论
    引用来自“jackshen”的评论
    lz,我在使用分页时并没有触发分页拦截器,是什么原因造成的呢?其他增删改查方法都可以正常使用~!
    引用来自“jackshen”的评论
    问题已解决,是因为没有开启aop注解
    分页拦截器需要配置吗?我分页查不出数据。
  6. 6楼 xiehui 发表于 2015-08-15 11:04:15 回复此评论
    这个分页拦截器PageControl,需要在xml文件里配置吗?
  7. 7楼 xiehui 发表于 2015-08-16 11:24:53 回复此评论
    引用来自“xiehui”的评论
    这个分页拦截器PageControl,需要在xml文件里配置吗?
    这个问题明白了,配置好后注解实现拦截,起初有问题是因为相关JAR包版本底报错。
  8. 8楼 xiehui 发表于 2015-08-16 11:27:11 回复此评论
    有一个问题请教下楼主,这个好像不提供多表联系查询,多表关联查询应该是要自己写DAO来继承这个通用DAO,写方法来实现吧?
  9. 码农的士首席的哥队长
    9楼 码农的士首席的哥队长 发表于 2015-10-09 10:30:36 回复此评论
    引用来自“xiehui”的评论
    有一个问题请教下楼主,这个好像不提供多表联系查询,多表关联查询应该是要自己写DAO来继承这个通用DAO,写方法来实现吧?
    不用继承这个dao啊,自己实现的按spring标准的JdbcTemplate路子走就行,分页也一样可以用
  10. 10楼 220.172.104.*** [游客] 发表于 2015-10-16 15:10:35 回复此评论
    引用来自“xiehui”的评论
    有一个问题请教下楼主,这个好像不提供多表联系查询,多表关联查询应该是要自己写DAO来继承这个通用DAO,写方法来实现吧?
    引用来自“selfly”的评论
    不用继承这个dao啊,自己实现的按spring标准的JdbcTemplate路子走就行,分页也一样可以用
    谢谢老大的解答。 这样的话,我的service层就需要注入两个DAO了,一个是你写的那个,一个是自己联合查询用的。 如果你的这个通用DAO能支持关联表查询就好了,毕竟实际项目中,关联查询是必不可少的。建议您添加一个关联表查询功能在里面。