MyBatis是一款非常好用的持久层框架,它支持定制化SQL、数据库存储过程及高级映射。MyBatis让使用者避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis可采用XML和注解两种方式配置和映射原生类型。接口和 Java 的 POJO。

一、拦截器Mybatis
而我们实现的分页功能就是基于mybatis的插件模块,Mybatis为我们提供了Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

public interface Interceptor {
  //是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
  Object intercept(Invocation invocation) throws Throwable;
  //就是用当前这个拦截器生成对目标target的代理
  Object plugin(Object target);
  //用于设置额外的参数,参数配置在拦截器的Properties节点里
 void setProperties(Properties properties);}

三、简单案例实现
Demo采用技术SpringBoot+MyBatis
1、pom.xml文件引入mybatis依赖

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version></dependency><dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope></dependency>

2、封装类代码实现

  1. 该类封装了分页查询的页码和页面大小,还有排序规。

/**
 * 通用分页请求参数
 * @author wuyh
 */public class PageRequest implements Serializable {
    private static final long serialVersionUID = -2464407342708149892L;
    /**
    *页码(从0开始)
    */
    private  int page;
    /**
     *每页显示数量
     */
    private  int size;
    /**
     *排序参数
     */
    private Sort sort;
    
    public PageRequest() {
        this(0, 10, (Sort)null);
    }
    public PageRequest(Sort sort) {
        this(0, 10, sort);
    }
    public PageRequest(int page, int size) {
        this(page, size, (Sort)null);
    }
    public PageRequest(int page, int size, Sort sort) {
        if (page < 0) {
            page = 0;
        }
        if (size < 0) {
            size = 0;
        }
        this.page = page;
        this.size = size;
        this.sort = sort;
    }
    
    public int getPage() {
        return this.page;
    }
    public int getSize() {
        return this.size;
    }
    
    public int getPageSize() {
        return this.size;
    }
    
    public int getPageNumber() {
        return this.page;
    }
    
    public long getOffset() {
        return this.page * this.size;
    }
    
    public boolean hasPrevious() {
        return this.page > 0;
    }
    
    public PageRequest next() {
        return new PageRequest(this.page + 1, this.size);
    }
    
    public PageRequest previousOrFirst() {
        return this.hasPrevious() ? new PageRequest(this.page - 1, this.size) : this;
    }
    public PageRequest first() {
        return new PageRequest(0, this.size);
    }
  
    public Sort getSort() {
        return this.sort;
    }}
  1. 分页结果集封装

public class Page<T> implements Serializable {
    private static final long serialVersionUID = 1625981207349025919L;
    //查询结果集
    private final List<T> content;
    //分页参数
    private  PageRequest pageRequest;
    //总记录数
    private  int total;
    public Page(List<T> content, PageRequest pageRequest, int total) {
        this.content = new ArrayList();
        if (null == content) {
            throw new IllegalArgumentException("Content must not be null!");
        } else {
            this.content.addAll(content);
            this.total = total;
            this.pageRequest = pageRequest;
        }
    }
    public Page(List<T> content, PageRequest pageRequest) {
        this(content, pageRequest, null == content ? 0 : content.size());
    }
    public Page(List<T> content) {
        this(content, (PageRequest)null, null == content ? 0 : content.size());
    }
    public int getNumberOfElements() {
        return this.content.size();
    }
    public int getTotalElements() {
        return this.total;
    }
    public List<T> getContent() {
        return Collections.unmodifiableList(this.content);
    }
    public boolean hasContent() {
        return !this.content.isEmpty();
    }
    public PageRequest getPageRequest() {
        return this.pageRequest;
    }
    public int getTotal() {
        return this.total;
    }}
  1. 拦截器部分
    这里的只要思路是:
    建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后如果参数列表有分页请求对象,我这里分页重新拼接sql执行实现自己的query方法逻辑,否则按原来方式执行。
    @Intercepts 在实现Interceptor接口的类声明,使该类PageInterceptor注册成为拦截器。

package com.wuyh.demo.interceptor;import com.wuyh.demo.utils.Page;import com.wuyh.demo.utils.PageRequest;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlSource;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.DefaultReflectorFactory;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.factory.DefaultObjectFactory;import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.domain.Sort;import org.springframework.stereotype.Component;import org.apache.ibatis.binding.MapperMethod.ParamMap;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.util.*;/**
 * @ClassName UserInterceptor
 * @Description: TODO
 * @Author wuyh
 * @Date 2022/2/10
 * @Version V1.0
**/@Component@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class PageInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(PageInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("进入拦截器");
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        //获取参数
        Object param = invocation.getArgs()[1];
        PageRequest pageRequest = this.getPageRequest(param);
        BoundSql boundSql = null;
        Object parameterObject = null;
        /**
         * 判断参数列表是否有PageRequest来判断是否需要进行分页
         */
        if (pageRequest != null) {
            Object whereParam = getWhereParameter(param);
            boundSql = mappedStatement.getBoundSql(whereParam);
            //强转 为了拿到分页数据
            PageRequest pageVo = pageRequest;
            String sql = boundSql.getSql();
            //获取相关配置
            Configuration config = mappedStatement.getConfiguration();
            Connection connection = config.getEnvironment().getDataSource().getConnection();
            //拼接查询当前条件的sql的总条数
            String countSql = "select count(*) from (" + sql + ") a";
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(config, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);
            parameterHandler.setParameters(preparedStatement);
            //执行获得总条数
            ResultSet rs = preparedStatement.executeQuery();
            int count = 0;
            if (rs.next()) {
                count = rs.getInt(1);
            }
            //拼接分页sql
            String pageSql = sql + " limit " + pageVo.getOffset() + " , " + pageVo.getPageSize();
            //重新执行新的sql
            doNewSql(invocation, pageSql);
            Object result = invocation.proceed();
            connection.close();
            //处理新的结构
            Page<?> page = new Page((List)result, pageVo, count);
            List<Page> returnResultList = new ArrayList<>();
            returnResultList.add(page);
            return returnResultList;
        }
        return invocation.proceed();
    }
    private void doNewSql(Invocation invocation, String sql){
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = getWhereParameter(args[1]);
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }
    /**
     * 获取新的MappedStatement
     * @param ms
     * @param newSqlSource
     * @return
     */
    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }
    @Override
    public Object plugin(Object o) {
        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }
    @Override
    public void setProperties(Properties properties) {
    }
    /**
     * 新的SqlSource需要实现
     */
    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
    /**
    * 从参数列表返回PageRequest
    */
    public PageRequest getPageRequest(Object paramMap) {
        if (paramMap == null) {
            return null;
        } else if (PageRequest.class.isAssignableFrom(paramMap.getClass())) {
            return (PageRequest)paramMap;
        } else {
            if (paramMap instanceof ParamMap) {
                ParamMap map = (ParamMap)paramMap;
                Iterator iterator = map.entrySet().iterator();
                while(iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry)iterator.next();
                    Object obj = entry.getValue();
                    if (obj != null && PageRequest.class.isAssignableFrom(obj.getClass())) {
                        return (PageRequest)obj;
                    }
                }
            }
            return null;
        }
    }
    
    private Object getWhereParameter(Object obj) {
        if (obj instanceof ParamMap) {
            ParamMap paramMap = (ParamMap)obj;
            if (paramMap.size() == 4) {
                Iterator iterator = paramMap.entrySet().iterator();
                while(iterator.hasNext()) {
                    Map.Entry var4 = (Map.Entry)iterator.next();
                    Object var5 = var4.getValue();
                    if (Sort.class.isAssignableFrom(var5.getClass()) || PageRequest.class.isAssignableFrom(var5.getClass())) {
                        return paramMap.get("param1");
                    }
                }
            }
        }
        return obj;
    }}

4)业务代码

@Mapperpublic interface SysUserMapper  {
   
   Page<SysUser> selectUserPage(SysUser user, PageRequest pageRequest);
   int addUser(SysUser user);
   void deleteUser(Long userId);}

@Servicepublic class UserService {
    @Autowired
    SysUserMapper userMapper;
    public Page<SysUser> selectUserPage(){
        SysUser sysUser = new SysUser();
        return userMapper.selectUserPage(sysUser, new PageRequest(0, 10));
    }
    
    public void add(Long userId, String userName, String passWord) {
        userMapper.deleteUser(userId);
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setUserName(userName);
        sysUser.setPassWord(passWord);
        userMapper.addUser(sysUser);
    }}

@RestController@RequestMapping("/user")public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("getUser")
    public String GetUser(){
        userService.add(11L,"pageOne", "Admin1");
        userService.add(12L,"pageTwo", "Admin1");
        return userService.selectUserPage().getContent().toString();
    }}
  1. Demo测试结果

    image

四、 小结
Executor是sql语句的执行器,Executor通过配置对象创建StatementHandler,继而得到了StatementHandler,StatementHandler是整个数据库访问过程的控制关键,它的内部持有ParameterHandler,因此StatementHandler可以通过后者来处理参数。在StatementHandler处理参数的过程中会通过参数类型来找到对应的typeHandler来处理参数,整个过程中Statement对象都作为参数在传递,到了typeHandler他会调用Statement的setInt来设置值,其实整个过程中Statement对象都在传递,Mybatis通过封装,但是还是在使用JDBC的API。

 
链接:https://www.jianshu.com/p/2f32d34e9d71