自定义结果集处理
mybatis的结果集处理在DefaultResultSetHandler.handleResultSet里:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);} else if (this.resultHandler == null) {// 如果不指定自己的resultHandler,就走这里。所以,一般情况下都会走这条分支DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);multipleResults.add(defaultResultHandler.getResultList());} else {// 如果指定了自己的resultHandler,走这里this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);}} finally {this.closeResultSet(rsw.getResultSet());}}
DefaultResultHandler的处理逻辑就是拿到ResultSet里的所有行记录,以List方式返回:
public class DefaultResultHandler implements ResultHandler<Object> {private final List<Object> list;public DefaultResultHandler() {this.list = new ArrayList();}public DefaultResultHandler(ObjectFactory objectFactory) {this.list = (List)objectFactory.create(List.class);}public void handleResult(ResultContext<?> context) {this.list.add(context.getResultObject());}public List<Object> getResultList() {return this.list;}
}
如果行记录数很大,比如几百万条,使用DefaultResultHandler就会占用大量的内存空间。这时,我们可能需要指定自己的resultHandler来做分批处理。
如何指定自己的resultHandler呢?很简单,重载mapper里已有的接口即可:
// 使用DefaultResultHandlerList<Group> selectXXX(@Param("ids") List<Long> ids);// 使用自定义的resultHandlervoid selectXXX(@Param("ids") List<Long> ids, ResultHandler resultHandler);
xml文件也不用改,因为我们就使用selectXXX的配置,只不过拿到List后,走我们自己的resultHandler而已。
如何获得mybatis的执行sql
参考下面代码:
// 先拿到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) springContext().getBean("sqlSessionFactory", SqlSessionFactory.class);
// 根据sql id拿到MappedStatement对象MappedStatement ms = sqlSessionFactory.getConfiguration().getMappedStatement("com.lee.selectXXX");// 这里要根据dao接口里的@Param注解,将参数逐个加入ParamMapMapperMethod.ParamMap paramMap = new MapperMethod.ParamMap();paramMap.put("ids", Arrays.asList(0L, 1L)); String sql = ms.getBoundSql(paramMap).getSql();
最后拿到的bound sql形如:
select GROUP_ID,......from tbl_twhere GROUP_ID in( ?, ?)
将#{}换成?占位符,相当于把mybatis的语法替换成了PrepareStatement可以接受的语法。
要最终执行上述boundsql,还得把参数flatten,传给PrepareStatement执行,因为@Param描述的一个参数有可能对应PrepareStatement的多个占位符。这个flatten的动作在DefaultParameterHandler.setParameters里完成。
PageInterceptor原理
使用样例代码:
@Named
@Slf4j
public class RepairService implements IRepairService {@Injectprivate IRepairDao iRepairDao;@Overridepublic PagedResult<RepairPlanVo> listRepairPlans(PageVO pageVO) throws ApplicationException {IPageQueryHandler<RepairPlanVo> iPageQueryHandler = () -> iRepairDao.listRepairPlans();PagedResult<RepairPlanVo> pagedResult = iPageQueryHandler.page(pageVO);return pagedResult;}
iPageQueryHandler.page逻辑为:
public interface IPageQueryHandler<R> {default PagedResult<R> page(PageVO pageVO) {PagedResult var4;try {PageHelper.startPage(pageVO);//我们会发现,到了这一步,this.list()返回的是已经分页了的结果列表,但list函数的实现() -> iRepairDao.listRepairPlans()明明是查了所有的记录啊,why?奥秘就在PageInterceptor,它拦截了mybatis的sql查询动作,篡改了iRepairDao.listRepairPlans()的行为PageInfo<R> pageInfo = new PageInfo(this.list());PagedResult<R> pagedResult = new PagedResult();pageVO.setTotalRows((int)pageInfo.getTotal());pagedResult.setPageVO(pageVO);pagedResult.setResult(pageInfo.getList());var4 = pagedResult;} finally {PageHelper.clearPage();}return var4;}List<R> list();
}
PageInterceptor的intercept方法:
public Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement)args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds)args[2];ResultHandler resultHandler = (ResultHandler)args[3];Executor executor = (Executor)invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;if (args.length == 4) {//boundSql就是原始sql:select * from t_repair_planboundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {cacheKey = (CacheKey)args[4];boundSql = (BoundSql)args[5];}this.checkDialectExists();if (this.dialect instanceof Chain) {boundSql = ((Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);}List resultList;//skip表示是否跳过分页,无分页信息就会跳过if (!this.dialect.skip(ms, parameter, rowBounds)) {if (this.dialect.beforeCount(ms, parameter, rowBounds)) {//这里会将原始sql转成count sql并执行:select COUNT(0) from t_repair_planLong count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);if (!this.dialect.afterCount(count, parameter, rowBounds)) {Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);return var12;}}//这里执行分页查询sql:select * from t_repair_plan limit ? offset ?resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);return var16;} finally {if (this.dialect != null) {this.dialect.afterAll();}}}
动态sql的OGNL语法说明
mybatis里的动态sql使用的是OGNL表达式语法,类似于SPEL。
语法如下:
e1 or e2 或e1 and e2 与e1 == e2,e1 eq e2 相等e1 != e2,e1 neq e2 不等e1 lt e2 小于e1 lte e2 小于等于,其他:gt(大于),gte(大于等于)e1 in e2e1 not in e2e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2 四则运算!e,not e 非e.method(args) 调用对象方法e.property 对象属性值e1[e2] 按索引取值,如数组和字典@class@method(args) 调用类的静态方法@class@field 调用类的静态字段值
OGNL里的变量
变量全都是java对象,该对象的public方法可以直接在OGNL里调用,getXXX方法可以简写为obj.XXX属性。这给了mybatis非常强大的动态能力。
如何打印sql
在log4j2.xml里配置日志级别为debug:
<Logger name="org.apache.ibatis" level="debug" additivity="true"><AppenderRef ref="sql"/>
</Logger>