zoukankan      html  css  js  c++  java
  • MyBatis中Executor源码解析之BatchExecutor搞不懂

    为了便于源码分析,还是先来一个MyBatis的Demo吧

    mybatis-mysql-config.xml

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
     3         "http://mybatis.org/dtd/mybatis-3-config.dtd">
     4 
     5 <configuration>
     6     <properties>
     7         <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
     8         <property name="url" value="jdbc:mysql://127.0.0.1:3306/gys?serverTimezone=UTC"/>
     9     </properties>
    10     <settings>
    11         <setting name="defaultExecutorType" value="SIMPLE"/>
    12     </settings>
    13     <!--环境配置,连接的数据库,这里使用的是MySQL-->
    14     <environments default="dev">
    15         <environment id="dev">
    16             <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置-->
    17             <transactionManager type="JDBC" />
    18             <!--dataSource 指连接源配置,POOLED是JDBC连接对象的数据源连接池的实现-->
    19             <dataSource type="POOLED">
    20                 <property name="driver" value="${driver}"></property>
    21                 <property name="url" value="${url}"></property>
    22                 <property name="username" value="root"></property>
    23                 <property name="password" value="gys"></property>
    24             </dataSource>
    25         </environment>       
    26     </environments>
    27     <mappers>   
    28         <mapper resource="mapper/user.xml"></mapper>
    29     </mappers>
    30 </configuration>

    user.xml

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     3         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     4 <mapper namespace="dao.IUserMapper">  
     5 
     6     <insert id="insertUser" parameterType="model.User">
     7         insert into user
     8         (name,age)
     9         values
    10         (#{name},#{age})
    11     </insert>
    12 
    13 </mapper>

    入口方法main:

     1     public static void main(String[] args) throws Exception {
     2         SqlSessionFactory sqlSessionFactory1=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-mysql-config.xml"),"dev");
     3         SqlSession sqlSession1= sqlSessionFactory1.openSession(true);
     4         IUserMapper userMapper=sqlSession1.getMapper(IUserMapper.class);
     5         User user=new User();
     6         user.setAge(28);
     7         user.setName("a");
     8         int i=userMapper.insertUser(user);
     9         System.out.println("受影响的行数"+i);
    10         sqlSession1.close();
    11     }

    这个Executor的代码离上面Demo执行代码还有一段很长封装,如果分析Executor,就要分析分析这段很长的封装代码;

    这个源码该怎么开始才能让人觉得水到渠成,顺其自然呢?

    算了,还是硬着头皮一步一步来吧;

    第一步:build过程中,如何获取到 defaultExecutorType配置

     

      第75行实例化一个XMLConfigBuilder对象,这是一个xml解析器。

    第78行调用第91行的build方法,这个方法的参数是个Configuration对象,那么parser.parse()方法返回的一定是一个Configuration对象;

    换句话说就是在parser.parse()中读取的配置文件,并且赋值给configuratiion对象。

    parser.parse()源码:

     parseConfiguration()源码

     1 private void parseConfiguration(XNode root) {
     2       try {     
     3         propertiesElement(root.evalNode("properties"));
     4         Properties settings = settingsAsProperties(root.evalNode("settings"));
     5        loadCustomVfs(settings);
     6         loadCustomLogImpl(settings);
     7         typeAliasesElement(root.evalNode("typeAliases"));
     8         pluginElement(root.evalNode("plugins"));
     9         objectFactoryElement(root.evalNode("objectFactory"));
    10        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    11        reflectorFactoryElement(root.evalNode("reflectorFactory"));
    12     //解析settings中的内容
    13        settingsElement(settings);     
    14        environmentsElement(root.evalNode("environments"));
    15        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    16        typeHandlerElement(root.evalNode("typeHandlers"));
    17        mapperElement(root.evalNode("mappers"));
    18      } catch (Exception e) {
    19        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    20      }
    21    }

    根据方法名大概能推断出处理的都是那些配置;直接看13行的代码吧。

    settingsElement()源码:

     1 private void settingsElement(Properties props) {
     2     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
     3     configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
     4     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
     5     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
     6     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
     7     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
     8     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
     9     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    10     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    11     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    12     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    13     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    14     configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    15     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    16     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    17     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    18     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    19     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    20     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    21     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    22     configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    23     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    24     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    25     configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    26     configuration.setLogPrefix(props.getProperty("logPrefix"));
    27     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    28   }

    看看第11行代码就是给configuration明确defaultExecutorType值的。

    getProperty(key,defaultValue)有两个参数,第一个参数是属性的键值,根据这个键值获取属性值,如果获取不到就用第二个参数作为默认值。
    如果没有配置 <setting name="defaultExecutorType" value="SIMPLE"/>,则用SIMPLE值。
    说到这就来说说defaultExecutorType有哪些参数
    SIMPLE:就是普通的执行器
    REUSE: 执行器会重(读:虫)用预处理语句(PreparedStatements)(re+use=重复+使用)
    BATCH 执行器将重(虫)用语句并执行批量更新。

    我一开始的看到官方的内容既不知道这三个什么意思,也不知道那个字到底读chong,还是读zhong.
    先改参数跑Demo看结果。
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultExecutorType" value="REUSE"/>
    这两个配置分别配置后,都可以正常的往数据库中插入一条数据,并且返回影响行数是1,只有
    <setting name="defaultExecutorType" value="BATCH"/>
    这条配置返回的数据是下面这个奇怪的东西;也没有往数据库中插入数据,也不抛异常。
    受影响的行数-2147482646
    1.SIMPLE和REUSE配置都能正常运行,那么区别在哪?
    2.BATCH配置返回的奇怪的数据是什么东西,为什么不成功。

    说了这么久都还没有说Executor是干什么的。
    Executor代表执行器,有它来调度StatementdHandler,ParameterHandler,ResultHandler等来执行对应的SQL.
    对于JDBC熟悉一点的人对上面几个Handler名字应该有点熟悉。如果忘记了JDBC,看看这篇博客:不了解jdbc,何谈Mybatis的源码解析?
    Executor既然是总调度,那么它应该是在MyBatis中Mapper的JDK动态代理的地方开始调用的。

    如果不清楚这个动态代理,可以看看这篇博客:从mybatis源码看JDK动态代理
    可以从Demo中的第四行代码getMapper()方法往下追。

     

       

      

     getMapper()的调用顺序如下: 

    defaultSqlSession.getMapper()==》configuration.getMapper()==>MapperRegistry.getMapper()==>MapperProxyFactory.newInstance();

    根据最后一张图的46和47行代码,结合JDK动态代理的调用规则,可以推断MapperProxy中一定实现了  InvocationHandler 接口;并且实现了invoke方法。

     1 @Override
     2   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     3     try {
     4      //调用object中的方法
     5       if (Object.class.equals(method.getDeclaringClass())) {
     6         return method.invoke(this, args);
     7       } else if (method.isDefault()) {
     8    //调用接口中的默认方法(jdk9之后,接口可以有方法体)
     9         if (privateLookupInMethod == null) {
    10           return invokeDefaultMethodJava8(proxy, method, args);
    11         } else {
    12           return invokeDefaultMethodJava9(proxy, method, args);
    13         }
    14       }
    15     } catch (Throwable t) {
    16       throw ExceptionUtil.unwrapThrowable(t);
    17     }
    18     final MapperMethod mapperMethod = cachedMapperMethod(method);//将method包装成MapperMethod对象
    19     return mapperMethod.execute(sqlSession, args);
    20   }

    execute()源码:

     1 public Object execute(SqlSession sqlSession, Object[] args) {
     2     Object result;
     3     switch (command.getType()) {
     4       case INSERT: {//sql是insert语句
     5         Object param = method.convertArgsToSqlCommandParam(args);
     6         result = rowCountResult(sqlSession.insert(command.getName(), param));
     7         break;
     8       }
     9       case UPDATE: {//sql是update语句
    10         Object param = method.convertArgsToSqlCommandParam(args);
    11         result = rowCountResult(sqlSession.update(command.getName(), param));
    12         break;
    13       }
    14       case DELETE: {//sql是delete语句
    15         Object param = method.convertArgsToSqlCommandParam(args);
    16         result = rowCountResult(sqlSession.delete(command.getName(), param));
    17         break;
    18       }
    19       case SELECT://sql是select语句
    20         if (method.returnsVoid() && method.hasResultHandler()) {
    21           executeWithResultHandler(sqlSession, args);
    22           result = null;
    23         } else if (method.returnsMany()) {
    24           result = executeForMany(sqlSession, args);
    25         } else if (method.returnsMap()) {
    26           result = executeForMap(sqlSession, args);
    27         } else if (method.returnsCursor()) {
    28           result = executeForCursor(sqlSession, args);
    29         } else {
    30           Object param = method.convertArgsToSqlCommandParam(args);
    31           result = sqlSession.selectOne(command.getName(), param);
    32           if (method.returnsOptional()
    33               && (result == null || !method.getReturnType().equals(result.getClass()))) {
    34             result = Optional.ofNullable(result);
    35           }
    36         }
    37         break;
    38       case FLUSH:
    39         result = sqlSession.flushStatements();
    40         break; 
    41     return result;
    42   }

    代码加注释应该能知道判断本次sql的类型。我们Demo执行的是insert语句;所以我们查看第六行的sqlsession.insert()

     1 @Override
     2   public int insert(String statement, Object parameter) {
     3     return update(statement, parameter);
     4   }
     5 
     6   @Override
     7   public int update(String statement, Object parameter) {
     8     try {
     9       dirty = true;
    10       MappedStatement ms = configuration.getMappedStatement(statement);
       //记住这个地方,等会还要回到这个地方;现在我们要去搞明白executor是从哪里传过来的,是何种执行器。
    11 return executor.update(ms, wrapCollection(parameter)); 12 } catch (Exception e) { 13 throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); 14 } finally { 15 ErrorContext.instance().reset(); 16 } 17 }

    上面的insert方法调用的是update方法。

    在update方法中终于看到了executor这个变量,到此终于看到了本篇的主角。

    从当前的分析来看,还没有看到executor是从什么地方进行传过来的。

    executor是defaultSqlsession的一个属性,

     既是私有的、又是final修饰的,没有办法进行set赋值,所以必然只能在实例化的时候进行赋值,上图源码第57行应验了我们的观点。

    现在就是要找到DefaultSqlSession在什么地方调用实例化的,就能知道执行器是怎么传的了。

    捋一下思路,sqlSession是一次与数据库会话的玩意,相当于Jdbc中的connection。

    这时候我们翻到Demo里面的代码看到第三行代码:SqlSession sqlSession1= sqlSessionFactory1.openSession(true);方法。

    从字面就能理解sqlSessionFactory工厂创建一个会话对象。继续追踪openSession源码。

     @Override
      public SqlSession openSession(boolean autoCommit) {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
      }

    openSessionFromDataSource()传了构建configuration对象时的执行器;getDefaultExecutorType()获取配置的执行器。

    继续追踪openSessionFromDataSource()源码;去除多余的代码

     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          //获取执行器,因为configuration存的是枚举,需要根据枚举转换成实际的执行器对象
          final Executor executor = configuration.newExecutor(tx, execType);
    //实例化defaultSqlSession;本次数据库会话的执行器确定下来了。
          return new DefaultSqlSession(configuration, executor, autoCommit);   
      }

    确定了执行器之后,继续回到上面  executor.update(ms, wrapCollection(parameter));方法调用的地方。

    executor是一个接口变量,他一定指向某个实现类。

     

     搞清楚了executor的来源。现在这个地方的update就应该知道是哪个执行器里买的方法了。

    根据配置只会从SimpleExecutor,ReuseExecutor,BatchExecutor三个类中去执行。

    现在可以把这三个类单独拿出来分析了。

    SimpleExecutor.java

    这个里面没有找到update(),可能是继承的原因,在BaseExecutor里面去找。

     

    调用的是doUpdate()方法 

     

     它是一个抽象方法,点击左边的继承按钮。

     

     继续回到SimpleExecutor中的doUpdate()方法。

     1   @Override
     2   public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
     3     Statement stmt = null;
     4     try {
     5       Configuration configuration = ms.getConfiguration();
     6 //StatementHandler后面再说吧
     7       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     8 //获取Statement具体对象
     9       stmt = prepareStatement(handler, ms.getStatementLog());
    10       return handler.update(stmt);
    11     } finally {
    12       closeStatement(stmt);
    13     }
    14   }

    还是继续追踪prepareStatement()源码

    1  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    2     Statement stmt;
    3     Connection connection = getConnection(statementLog);
    4     //对sql进行预编译,并返回预编译对象。
    5     stmt = handler.prepare(connection, transaction.getTimeout());
    6    //对sql中的?参数进行设置
    7     handler.parameterize(stmt);
    8     return stmt;
    9   }

    这里面的getConnection(),preparse(),parameterize()方法的执行还有很长的调用链,后面就是参数如何转换的问题,牵扯的TypeHandler模块了,后面在单独开辟一篇分析吧。

    至此SimpleExecutor执行器分析完了; 

    继续分析ReuseExecutor.java

    用和SimpleExecutor同样的方法找到prepareStatement()方法

     1  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     2     Statement stmt;
     3     BoundSql boundSql = handler.getBoundSql();
     4   //获取sql语句
     5     String sql = boundSql.getSql();
     6 //查询这条sql是否预编译过,有的话直接获取预编译对象
     7     if (hasStatementFor(sql)) {
     8       stmt = getStatement(sql);
     9       applyTransactionTimeout(stmt);
    10     } else {
    11 //第一次预编译,直接创建一个预编译对象
    12       Connection connection = getConnection(statementLog);
    13       stmt = handler.prepare(connection, transaction.getTimeout());
    14    //缓存预编译对象
    15       putStatement(sql, stmt);
    16     }
    17   //sql中的参数替换
    18     handler.parameterize(stmt);
    19     return stmt;
    20   }

    ReuseExecutor的prepareStatement方法比SimExecutor的prepareStatement()方法多了一个sql的预编译对象的保存。

    至此终于明白了为什么 REUSE配置叫 重(虫)用sql语句了;re(重复)+use(使用)=reuse。

    BatchExecutor分析。

    //代码很长大,但是只要看最后两行代码就行了。
     @Override
      public 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 322
          BatchResult batchResult = batchResultList.get(last);
          batchResult.addParameterObject(parameterObject);
        } else {
          Connection connection = getConnection(ms.getStatementLog());
          stmt = handler.prepare(connection, transaction.getTimeout());
          handler.parameterize(stmt);    //fix Issues 322
          currentSql = sql;
          currentStatement = ms;
          statementList.add(stmt);
          batchResultList.add(new BatchResult(ms, sql, parameterObject));
        }
        handler.batch(stmt);
        return BATCH_UPDATE_RETURN_VALUE;
      }

    首先这个方法本身就很奇怪;不管执行结果如何,直接返回  BATCH_UPDATE_RETURN_VALUE ;

    我们看看BATCH_UPDATE_RETURN_VALUE 是什么东西。

     直接就是int的最小值加1002;不明白为什么要返回这么个特定的值;为什么不直接返回1,2,张三,阿毛、阿狗?

    现在终于知道上面的insert为什么在BATCH配置的情况下,返回下面这个截图内容了

    handler.batch(stmt)源码

     直接addBatch()就结束了,没有向数据库发送执行操作的代码,比如ps.executeBatch()方法。

    现在也终于知道为什么数据库里面一直都没有新增数据了。

    可是为什么要这么设计呢?是代码没写完么?

    1.SimpleExecutor是对jdbc中PreParement的封装

    2.ReuseExecutor是对Jdbc中PreParement封装的基础上进行缓存,以达到sql活预编译对象的重复使用

    3.希望知道答案的博友们告知一下,为什么Batch执行器不写操作数据库的代码,如果不写,那要这个执行器还有什么用呢?

     

     

     

     

     

     

     

    
    
  • 相关阅读:
    基于Hadoop的改进Apriori算法
    iOS 访问URL转码
    第一次进入应用图片轮播效果
    购物篮模型&Apriori算法
    Ionicons的使用
    如何在 Eclipse 中使用命令行
    Eclipse输入命令行参数
    CHAR,TCHAR,WCHAR 三者的区别与转换
    函数名、变量前后的_(一个下划线)、__(两个下划线)分别有什么用
    java爬虫简单实现
  • 原文地址:https://www.cnblogs.com/guoyansi19900907/p/12691459.html
Copyright © 2011-2022 走看看