为了便于源码分析,还是先来一个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执行器不写操作数据库的代码,如果不写,那要这个执行器还有什么用呢?