Mybatis @Flush注解分析

在看源码的的时候,发现了@Flush注解。之前没用过,于是就有了这篇文章

注意:这里的执行器的类型肯定是BatchExecutor

先来例子

 @Testpublic void testShouldSelectClassUsingMapperClass(){try(//指定这次查询的执行器器的类型为BATCH。对应的类型为BatchExecutorSqlSession session = sqlMapper.openSession(ExecutorType.BATCH)){ClassMapper mapper = session.getMapper(ClassMapper.class);long start = System.currentTimeMillis();// 这个查询就是正常的查询方法List<ClassDo> classDos = mapper.listClassOwnerStudentByClassId(1, new String[]{"狗蛋"});System.out.println(classDos);System.out.println("开始更新");// 下面是两个更新操作System.out.println(mapper.updateClassNameById(1, "计科一班1"));System.out.println(mapper.updateClassNameById(2, "计科一班2"));System.out.println("结束更新");// 调用flush方法,这个flush方法是我自己写的。返回值为List<BatchResult> ,并且这个flush方法没有对应的Xml。// 在调用这个方法的时候,才会去真正的更新数据库for (BatchResult flush : mapper.flush()) {//更新的sqlSystem.out.println(flush.getSql());//更新的参数System.out.println(flush.getParameterObjects());//更新影响的行数,这里包括上面的getParameterObjects,这返回值都是一个数组// 这个批量是以statement为维度的。所以,上面的两个查询都属于一个statement,只是参数不一样// 所以,getParameterObjects就是一个数组,并且getUpdateCounts也必须是一个数组。System.out.println(flush.getUpdateCounts());}}public interface ClassMapper {// 这个没有,必须没有@FlushList<BatchResult> flush();//下面两个都有XMl文件List<ClassDo> listClassOwnerStudentByClassId(Integer orderId,@Param("array") String[] names);int updateClassNameById(@Param("id") Integer id,@Param("className") String className);}

在看结果

1. 注解的作用

先看源码上的注解


/*** The maker annotation that invoke a flush statements via Mapper interface.** <pre>* public interface UserMapper {*   @Flush*   List&lt;BatchResult&gt; flush();* }* </pre>*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Flush {}

这个注解就是一个标记接口,通过Mapper的接口来调用刷新statements

2. 注解是怎么发挥作用的?

在方法调用的时候,会调用到MapperProxyinvoke方法,在这里面会构建PlainMethodInvoker,在PlainMethodInvoker里面会包装MapperMethod。在MapperMethod里面构建SqlCommandMethodSignature,第一个解析@Flush操作就在构建SqlCommand对象。在这里面会如果当前的方法,被@Flush修饰,并且没有对应的Mapper,就将此次查询变为SqlCommandType.FLUSH

  1. 第一步,解析@Flush注解。
   public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {// 方法的名字final String methodName = method.getName();// method所在的类final Class<?> declaringClass = method.getDeclaringClass();// 通过全限定类型名和方法名字从Configuration里面获取MappedStatement。MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);// 如果ms是null。使用Flush的前提是不要写MapperStatement。if (ms == null) {if (method.getAnnotation(Flush.class) != null) {//此次查询的类型就是FLUSH。name = null;type = SqlCommandType.FLUSH;} else {throw new BindingException("Invalid bound statement (not found): "+ mapperInterface.getName() + "." + methodName);}} else {name = ms.getId();type = ms.getSqlCommandType();if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}}
  1. 使用@Flush注解。

MapperMethodexecute方法里面会通过Type来确定此次操作的类型,上面已经确定了他的类型是FLUSH

  public Object execute(SqlSession sqlSession, Object[] args) {Object result; //美其名曰,策略模式// 类型为FLUSHswitch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:// 重点就是`flushStatements`方法result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}

继续看sqlSession.flushStatements()
跟着上面的代码一直点下去,这里会有多态的情况产生。一种是到BatchExecutor的doFlushStatements方法里面,一种是SimpleExecutordoFlushStatements的方法。这里最重要的是要走第一种情况。但是下面我会继续两种情况都分析一下。

  1. BatchExecutor

    statementListBatchExecutor的属性,在这个方法里面会将之前添加到statementList里面的Statement真正执行,然后将更新的行数封装为 batchResult的一个属性。 这就是批量更新。

    有几个问题?

    statementList为什么是一个List

    因为一个更新操作里面可能有多个Statement,所有,这里是一个List,

    batchResult为什么也是一个List,并且parameterObjects也是一个List,

    因为多个Statement对应多个Result,parameterObjects是因为,一个Statement可能会调用多次,并且参数值不一样。

    那么statementListbatchResult值是在哪里添加的?

    @Overridepublic List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {try {List<BatchResult> results = new ArrayList<>();if (isRollback) {return Collections.emptyList();}//遍历statementfor (int i = 0, n = statementList.size(); i < n; i++) {//设置查询的参数Statement stmt = statementList.get(i);applyTransactionTimeout(stmt);BatchResult batchResult = batchResultList.get(i);try {// 这里会调用statement的executeBatch来把之前的准备好的sql批量执行。// 那么,问题来了,statement是在哪里添加的?batchResult是在哪里添加的?batchResult.setUpdateCounts(stmt.executeBatch());MappedStatement ms = batchResult.getMappedStatement();//得到更新的操作List<Object> parameterObjects = batchResult.getParameterObjects();// 如果使用了KeyGenerator。KeyGenerator keyGenerator = ms.getKeyGenerator();if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141for (Object parameter : parameterObjects) {keyGenerator.processAfter(this, ms, stmt, parameter);}}//关闭StatementcloseStatement(stmt);} catch (BatchUpdateException e) {StringBuilder message = new StringBuilder();message.append(batchResult.getMappedStatement().getId()).append(" (batch index #").append(i + 1).append(")").append(" failed.");if (i > 0) {message.append(" ").append(i).append(" prior sub executor(s) completed successfully, but will be rolled back.");}throw new BatchExecutorException(message.toString(), e, results, batchResult);}// 将结果添加在result里面返回。results.add(batchResult);}return results;} finally {for (Statement stmt : statementList) {closeStatement(stmt);}currentSql = null;statementList.clear();batchResultList.clear();}}
    
  2. SimpleExecutor

    // 空操作。返回一个空的列表,这没啥可看的。 @Overridepublic List<BatchResult> doFlushStatements(boolean isRollback) {return Collections.emptyList();}
    

3. 啥时候给statementListbatchResultList添加值

最简单的方式就是点一下,找到他所有引用的地址,并且找add方法,因为他是一个List嘛

看来只有一个地方调用了add方法

在更新操作的时候才会把构建好的Statement添加到statementList里面,同时还会构建BatchResult,并把他添加在里面。最后调用的是PreparedStatement#addBatch()方法。并且返回了一个固定值。

可以看到BatchResult的几个参数,分别是MapperStatement,sql,parameterObject参数

问题?

  1. 下面代码中的currentSql是干嘛的?

    这就是一个更新的Mapper多次调用只会生成一个Statement和一个BatchResult的

    在第一次走到这个方法的时候,currentSql为null,在添加到第一个Statement之后,会赋值currentSql和currentStatement为当前组装好的,如果这个方法第二次调用的时候,就会走到if里面,并且会将这次查询的参数添加到之前的BatchResult的parameterObject属性里面去

// BatchExecutor的doUpdate方法
// 这里的逻辑和之前查询基本一致。都是获取Connection,处理参数,但是这里并没有执行,而是调用了PreparedStatement#addBatch();@Overridepublic int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);final BoundSql boundSql = handler.getBoundSql();final String sql = boundSql.getSql();final Statement stmt;if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);// fix Issues 322BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);    // fix Issues 322currentSql = sql;currentStatement = ms;statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}handler.batch(stmt);//可以看到,返回的都是一个固定值  为  Integer.MIN_VALUE + 1002;return BATCH_UPDATE_RETURN_VALUE;}

补充说明

在Mybatis中删除和插入和更新都会进对调用到doUpdate方法,上面是以Update举例子。也就是说,除了查询之外,剩下的三种操作都执行批量处理。

总结

Mybatis @Flush是为批量操作做准备的。必须要将执行器的类型设置为BatchExecutor,(可以通过全局设置和获取Session的时候局部设置),并且@Flush标注的方法不能有对应的xml文件。返回值为List,如下所示

  @FlushList<BatchResult> flush();

那么在方法调用的时候,就可以通过调用Mapper的被@Flush修饰的方法来更新。

Mybatis @Flush注解分析相关推荐

  1. Java-Mybatis(二): Mybatis配置解析、resultMap结果集映射、日志、分页、注解开发、Mybatis执行流程分析

    Java-Mybatis-02 学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1NE411Q7Nx 学习资料:mybatis 参考文档 – ht ...

  2. MyBatis 源码分析 - SQL 的执行过程

    本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...

  3. 源码通透-mybatis源码分析以及整合spring过程

    源码通透-mybatis源码分析以及整合spring过程 mybatis源码分析版本:mybaits3 (3.5.0-SNAPSHOT) mybatis源码下载地址:https://github.co ...

  4. Mybatis源码分析: MapperMethod功能讲解

    canmengqian </div><!--end: blogTitle 博客的标题和副标题 --> <div id="navigator"> ...

  5. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  6. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  7. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  8. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

  9. springboot集成mybatis源码分析(一)

    springboot集成mybatis源码分析(一) 本篇文章只是简单接受使用,具体源码解析请看后续文章 1.新建springboot项目,并导入mybatis的pom配置 配置数据库驱动和mybat ...

最新文章

  1. WPF自学入门(十一)WPF MVVM模式Command命令 WPF自学入门(十)WPF MVVM简单介绍...
  2. IT民工系列——c#操作Microsoft IE,实现自动登录吧!
  3. (七)OpenStack---M版---双节点搭建---Dashboard安装和配置
  4. 关于1970-1-1 00:00.000的知识【转】
  5. NET 应用架构指导 V2 学习笔记(十九) 表现层组件设计指导
  6. Oracle 11g客户端及PLSQL Developer配置|Instant Client Setup-64位|OraClientLite11g_x86
  7. mysql profiling 应用
  8. 积分和人民币比率_通过比率路由到旧版和现代应用程序–通过Spring Cloud的Netflix Zuul...
  9. 二叉树题目----4 前序遍历重构二叉树 AND 求二叉树中所有结点的个数
  10. 计算机网络考试成绩分析报告,成绩分析报告范文_成绩分析总结与反思
  11. intellij IDEA 控制台中文乱码
  12. JavaScriptSerializer 类
  13. 使用 WordPress 自定义字段功能为文章添加下载按钮
  14. 注册为linux系统服务,注册程序为Linux系统服务并设置成自启动
  15. msi笔记本u盘装linux,msi微星笔记本bios设置u盘启动教程
  16. 文件系统(一)---文件系统基础知识
  17. Flux、Mono、Reactor 实战(史上最全)
  18. C# 如何生成CHM帮助文件
  19. Android应用推广渠道分享
  20. ARFoundation系列讲解 - 31 光照估计

热门文章

  1. dw使用html语言吗,HTML基础知识与DW 基础应用
  2. rsync+sersync实现两台web服务器的实时同步
  3. IT专业大学生就业压力很大吗?IT行业是吃青春饭的吗?
  4. linux显示usb内容,技术|Linux中显示系统中USB信息的lsusb命令
  5. 专业术语之------耦合?依赖?耦合和依赖的关系?耦合就是依赖
  6. Django ORM数据库回滚
  7. javaweb -- jdbc
  8. python 实现跳一跳自动化代码_Guoq
  9. java导出标底文件_投标文件怎样导出XML格式
  10. 有效性控件与正则表达式子 与使验证控件无效的按钮?Button1.CausesValidation=false;