mybatis使用高阶技巧

news/2024/11/8 5:42:18/

自定义结果集处理

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>

http://www.ppmy.cn/news/40571.html

相关文章

02-PostgreSQL 存储过程的进阶介绍(含游标、错误处理、自定义函数、事务)

&#x1f3c6; 文章目标&#xff1a;帮助那些了解Oracle PL/SQL&#xff0c;但是不熟悉PL/pgSQL 的人&#xff0c;用于快速整改或者上手。 &#x1f340; 02-PostgreSQL 存储过程的进阶介绍&#xff08;含游标、错误处理、自定义函数、事务&#xff09; ✅ 创作者&#xff1a;J…

小游戏如何带动直播平台发展?

据艾媒咨询数据显示&#xff0c;截至2021年6月&#xff0c;我国网络直播用户规模达6.38亿&#xff0c;与2020年同期相比增长47.2%&#xff0c;占网民整体的63.1%。而随着互联网技术的高速发展&#xff0c;网络直播生态链备受用户关注&#xff0c;网络直播用户规模持续上升&…

使用pycharm远程连接服务器,可进行远程调试

利用Pycharm连接服务器 前言 当下&#xff0c;很多深度学习的模型需要高配置的设备来跑&#xff0c;本地的pc可能无法满足要求。所以就需要利用服务器来训练&#xff0c;但是在服务器上操作代码不是很方便。利用Pycharm可以在本地编写/修改代码&#xff0c;能够同步到服务器上&…

02 | 论文中的「题目、目录、摘要和结论」应该怎么写?

前言 写好外审的重点部分&#xff1a;题目、目录、摘要和结论。 文章目录前言一、专家&#xff08;教授&#xff09;一般审稿顺序二、题目1. 包含的要素2. 注意点3. 达到的效果三、研究背景及意义1. 要点2. 撰写思路四、目录1. 作用2. 逻辑关系的体现五、摘要1. 包含要素2. 关键…

12款 Macmini A1347 跑 Stable Diffusion,20多分钟一张图

设备 2012款 Macmini A1347 12款 mini A1347 跑 Stable Diffusion 要20多分钟一张图 来欣赏一下20分钟画出来的图片 a black and white cat 环境&#xff1a;

坦克大战第一阶段代码

package tanke.game;import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Vector;//为了监听键盘事件&#xff0c;实现keylistener public class mypanel extends JPanel implements KeyListener …

Hadoop 配置历史服务器及日志的聚集 (六)

配置历史服务器 为了查看程序的历史运行情况&#xff0c;需要配置一下历史服务器。具体配置步骤如下&#xff1a; 1 &#xff09; 配置 mapred-site.xml 只需在mapred-site.xml配置文件加两个参数&#xff1a; [leokadiahadoop102 hadoop]$ vim mapred-site.xml 在该文件里面…

数据库并发控制基本概念和基本技术

并发控制与基本技术一、并发控制1. 概述2. 并发访问可能出现的问题二、并发控制的主要技术1、基本技术2、封锁及锁的类型2.1、什么是封锁2.2、基本封锁类型2.2.1、排它锁&#xff08;Exclusive Locks&#xff0c;简记为 X 锁&#xff09;2.2.2、共享锁&#xff08;Share Locks&…