本文隶属于专题系列: 通用数据库访问层dexcoder-dal

注意:本组件已重构并全新发布,更加方便易于使用。代码全部开源,详情访问 Github仓库码云

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

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

@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分页插件][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和建议,谢谢!

[spring-jdbc2]: [mybatis]: http://git.oschina.net/free/Mybatis_PageHelper

你可能感兴趣的内容
Spring 之 JMS 监听JMS消息 收藏,4408 浏览
Spring 之 JMS 基于JMS的RPC 收藏,3256 浏览
Spring的BeanFactory和FactoryBean 收藏,10408 浏览
6条评论
selfly 1年前
不用继承这个dao啊,自己实现的按spring标准的JdbcTemplate路子走就行,分页也一样可以用
xiehui 1年前
有一个问题请教下楼主,这个好像不提供多表联系查询,多表关联查询应该是要自己写DAO来继承这个通用DAO,写方法来实现吧?
xiehui 1年前
这个问题明白了,配置好后注解实现拦截,起初有问题是因为相关JAR包版本底报错。

selfly

交流QQ群:32261424
Owner