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、封装类代码实现
-
该类封装了分页查询的页码和页面大小,还有排序规。
/** * 通用分页请求参数 * @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; }}
-
分页结果集封装
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; }}
-
拦截器部分
这里的只要思路是:
建立一个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(); }}
-
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