MyBatis SqlSession事务与批量执行正确方式(默认不生效)
1. 容易误用的写法
某些情况下会使用MyBatis的SqlSessionFactory.openSession()方法获取SqlSession对象,再进行数据库操作,但默认情况下SqlSession的事务与批量执行均不生效,假如希望使用SqlSession时事务或批量执行能够生效,则需要进行额外的处理
1.1. SqlSession事务默认不生效
调用org.apache.ibatis.session.SqlSessionFactory接口的以下openSession()方法时,默认情况下,指定autoCommit参数为false,实际上不会关闭自动提交,即事务不会生效
:
SqlSession openSession(boolean autoCommit);SqlSession openSession(ExecutorType execType, boolean autoCommit);
默认情况下,以下写法效果是相同的,即事务不生效:
SqlSession sqlSession = usedSqlSessionFactory.openSession(..., (autoCommit=)false);// 两种写法效果相同SqlSession sqlSession = usedSqlSessionFactory.openSession(..., (autoCommit=)true);
1.2. SqlSession批量执行默认不生效
调用SqlSessionFactory接口的以下openSession()方法时,默认情况下,指定execType参数为ExecutorType.BATCH,实际上sql语句不会批量执行,还是单条执行的(使用MySQL数据库)
SqlSession openSession(ExecutorType execType);SqlSession openSession(ExecutorType execType, boolean autoCommit);SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType, Connection connection);
默认情况下,以下写法效果是相同的,即sql语句不会批量执行
SqlSession sqlSession = usedSqlSessionFactory.openSession(ExecutorType.BATCH, ...);// 两种写法效果相同SqlSession sqlSession = usedSqlSessionFactory.openSession(ExecutorType.SIMPLE, ...);
2. SqlSession事务正确使用方式
- 使用方式
使用SqlSessionFactory.openSession()方法获取SqlSession时,假如需要在事务中执行数据库操作,除了指定autoCommit参数为false外,还需要进行以下处理:
在定义SqlSessionFactory接口的实现类org.mybatis.spring.SqlSessionFactoryBean时,将transactionFactory字段使用org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory的实例
假如使用XML方式定义Spring相关配置,则如下所示:
<bean id="jdbcTransactionFactory" class="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="failFast" value="true"/> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations"> <list> <value>classpath*:xxx/*.xmlvalue> list> property> <property name="transactionFactory" ref="jdbcTransactionFactory"/>bean>
以上SqlSessionFactoryBean需要新建一个,不能使用org.mybatis.spring.mapper.MapperScannerConfigurer中使用的sqlSessionFactory
假如将MapperScannerConfigurer中使用的sqlSessionFactory的transactionFactory使用JdbcTransactionFactory的实例,会导致MyBatis中普通的Mapper数据库操作也使用事务
- MyBatis推荐
SqlSessionFactoryBean.setTransactionFactory()方法用于设置以上transactionFactory对象
SqlSessionFactoryBean默认使用的TransactionFactory是SpringManagedTransactionFactory,默认的SpringManagedTransactionFactory类能很好地适用于所有的情况,强烈推荐使用默认的TransactionFactory
即MyBatis不推荐使用以上修改TransactionFactory的方式
2.1. 编程式事务建议使用方式
当需要使用编程式事务执行数据库操作时,建议使用Spring事务模板TransactionTemplate
Spring事务模板TransactionTemplate不需要人工对事务进行管理,即不需要在代码中显式执行事务开启、事务提交、事务回滚等操作,更方便稳定
可参考https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html
3. SqlSession批量执行正确使用方式
使用MySQL数据库,使用SqlSessionFactory.openSession()方法获取SqlSession时,假如需要在批量执行数据库操作,除了指定execType参数为ExecutorType.BATCH外,还需要进行以下处理:
在MySQL的jdbc url中,指定rewriteBatchedStatements=true
参数,使mysql-connector对SQL语句进行重写,进行批量执行,示例如下:
jdbc:mysql://1.1.1.1:3306/db_name?characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
进行以上处理后,调用SqlSession.flushStatements()时,会批量执行当前缓存的数据库操作(调用SqlSession.flushStatements()之前的数据库写操作会被缓存)
- 支持批量执行的情况
仅当对同一个表执行insert、replace语句,且SQL语句的结构相同时,支持批量执行
当insert、replace语句被批量执行时,insert、replace语句会被重写为以下多值形式:
insert into xxx(...) values (...),(...);replace into xxx(...) values (...),(...);
3.1. Statement.executeBatch()与rewriteBatchedStatements
调用Statement.executeBatch()方法时,会受到rewriteBatchedStatements参数的影响
3.2. SqlSession.flushStatements()与rewriteBatchedStatements
SqlSession.flushStatements()方法中调用了Statement.executeBatch()方法,因此SqlSession.flushStatements()也会受到rewriteBatchedStatements参数的影响
3.3. MyBatis XML insert foreach与rewriteBatchedStatements
在MyBatis的XML文件的insert语句中,使用foreach方式批量执行时,MyBatis生成的SQL语句就是多值的形式,不需要重写,因此不会受到rewriteBatchedStatements参数的影响
3.4. 批量执行建议使用方式
当需要批量执行时,建议使用MyBatis的XML文件的insert语句中foreach方式,原因如上
4. 相关组件及版本
组件 | 版本 |
---|---|
spring | 5.3.22 |
mybatis | 3.5.9 |
mybatis-spring | 2.0.7 |
druid | 1.2.10 |
mysql-connector-java | 8.0.31 |
5. 细节分析
5.1. 相关概念
在使用MyBatis执行数据库操作及事务时,涉及到以下概念:
接口名或类名 | 说明 | 相关实现类或子类 |
---|---|---|
org.apache.ibatis.session.SqlSessionFactory | 创建SqlSession的工厂类 | org.apache.ibatis.session.defaults.DefaultSqlSessionFactory org.apache.ibatis.session.SqlSessionManager |
org.apache.ibatis.session.SqlSession | 管理Sql会话,其中包含了Executor | org.apache.ibatis.session.SqlSessionManager org.apache.ibatis.session.defaults.DefaultSqlSession org.mybatis.spring.SqlSessionTemplate |
org.apache.ibatis.executor.Executor | 数据库操作执行类,其中包含了Transaction | org.apache.ibatis.executor.CachingExecutor org.apache.ibatis.executor.BaseExecutor org.apache.ibatis.executor.SimpleExecutor org.apache.ibatis.executor.BatchExecutor org.apache.ibatis.executor.ReuseExecutor |
org.apache.ibatis.transaction.TransactionFactory | 创建Transaction的工厂类 | org.mybatis.spring.transaction.SpringManagedTransactionFactory org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory org.apache.ibatis.transaction.managed.ManagedTransactionFactory |
org.apache.ibatis.transaction.Transaction | 管理事务,其中包含了Connection | org.apache.ibatis.transaction.managed.ManagedTransaction org.apache.ibatis.transaction.jdbc.JdbcTransaction org.mybatis.spring.transaction.SpringManagedTransaction |
java.sql.Connection | 数据库连接 | 略 |
5.2. 基本步骤
5.2.1. 初始化
进行初始化操作时的步骤如下:
- SqlSessionFactory获取TransactionFactory
- TransactionFactory创建Transaction
- SqlSessionFactory创建Executor
- SqlSessionFactory创建SqlSession
如下所示:
SqlSessionFactory -> TransactionFactory -> Transaction-> Executor-> SqlSession
5.2.2. 数据库操作
进行获取数据库连接、提交/回滚事务等数据库操作时,步骤如下:
- SqlSession调用其中的Executor
- Executor调用其中的Transaction
如下所示:
SqlSession -> Executor -> Transaction
5.3. SqlSessionFactoryBean的openSession()方法实现
SqlSessionFactoryBean类没有直接实现SqlSessionFactory接口,而是实现了org.springframework.beans.factory.FactoryBean接口,泛型类型为SqlSessionFactory,代码如下:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, ...
FactoryBean接口中,getObject()方法返回工厂类管理的对象的实例,相当于做了一层代理
SqlSessionFactoryBean.getObject()方法返回的是字段SqlSessionFactory sqlSessionFactory
SqlSessionFactoryBean.afterPropertiesSet()方法中,将sqlSessionFactory字段赋值为buildSqlSessionFactory()方法返回值
SqlSessionFactoryBean.buildSqlSessionFactory()方法中,返回了sqlSessionFactoryBuilder.build()方法返回值,sqlSessionFactoryBuilder字段类型为org.apache.ibatis.session.SqlSessionFactoryBuilder
SqlSessionFactoryBuilder.build()方法中,创建了DefaultSqlSessionFactory对象并返回
即SqlSessionFactoryBean类相当于继承了DefaultSqlSessionFactory类
SqlSessionFactoryBean类未实现openSession()方法,可继承DefaultSqlSessionFactory类的对应方法
5.4. SqlSessionFactory.openSession()方法实现-TransactionFactory、Transaction、Executor、SqlSession初始化过程
调用SqlSessionFactoryBean.openSession()方法时,实际调用的是DefaultSqlSessionFactory.openSession()方法
在DefaultSqlSessionFactory.openSession()方法中,会通过TransactionFactory创建Transaction,将Transaction与Executor进行关联,再将Executor与SqlSession关联
DefaultSqlSessionFactory类有多个openSession()方法,参数中不包含Connection的openSession()方法都是调用openSessionFromDataSource()方法:
若openSession()方法有指定autoCommit参数,则调用openSessionFromDataSource()方法时autoCommit参数使用指定的值;
若openSession()方法未指定autoCommit参数,则调用openSessionFromDataSource()方法时autoCommit参数使用固定值false
DefaultSqlSessionFactory.openSessionFromDataSource()方法部分代码如下:
Transaction tx = null;try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit);}
以上方法的执行步骤如下:
- 获取环境Environment
调用org.apache.ibatis.session.Configuration.getEnvironment()方法
用于获取当前配置对应的环境信息
- 获取事务工厂TransactionFactory
调用getTransactionFactoryFromEnvironment()方法
用于获取当前环境对应的TransactionFactory
在getTransactionFactoryFromEnvironment()方法中,若Environment.getTransactionFactory()方法返回值非null,则使用对应的TransactionFactory
对Environment中TransactionFactory进行赋值的过程见后文对SqlSessionFactoryBean.buildSqlSessionFactory()方法的分析
- 获取事务Transaction
调用TransactionFactory.newTransaction()方法
用于创建org.apache.ibatis.transaction.Transaction
TransactionFactory存在多个实现类,newTransaction()方法有不同的实现,TransactionFactory及对应的Transaction实现类如下:
TransactionFactory实现类 | Transaction实现类 | 对应包名 |
---|---|---|
SpringManagedTransactionFactory | SpringManagedTransaction | org.mybatis.spring.transaction |
JdbcTransactionFactory | JdbcTransaction | org.apache.ibatis.transaction.jdbc |
ManagedTransactionFactory | ManagedTransaction | org.apache.ibatis.transaction.managed |
- 获取执行器Executor
调用Configuration.newExecutor(),使用Transaction、ExecutorType创建Executor
Configuration.newExecutor()方法部分代码如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); }
ExecutorType类型决定了创建的Executor类型:
ExecutorType类型 | 对应的Executor类型 | 说明 |
---|---|---|
BATCH | BatchExecutor | 支持批量执行的执行器 |
REUSE | ReuseExecutor | 支持重用预编译SQL语句的执行器 |
SIMPLE | SimpleExecutor | 简单执行器 |
BatchExecutor、ReuseExecutor、SimpleExecutor都是org.apache.ibatis.executor.BaseExecutor的子类
以上三种Executor的构造函数中都会调用BaseExecutor的构造函数,在其中会将参数中指定的Transaction transaction保存到类的字段中
Configuration.newExecutor()方法执行的操作如下:
使用Transaction、ExecutorType创建对应的Executor(SimpleExecutor、BatchExecutor)等
由于cacheEnabled字段默认为true,因此会使用已创建的Executor创建CachingExecutor,使用Executor delegate字段保存刚创建的Executor
- 获取SQL会话SqlSession
使用Executor、autoCommit创建DefaultSqlSession
DefaultSqlSessionFactory.openSessionFromDataSource()方法返回的类型为DefaultSqlSession
5.5. BaseExecutor中通过Transaction获取Connection的过程
在BaseExecutor的子类BatchExecutor、ReuseExecutor、SimpleExecutor,数据库读写操作对应的doQuery()、doQueryCursor()、doUpdate()方法中,都会先直接或间接调用getConnection()方法,以获取数据库连接Connection
在BaseExecutor.getConnection()方法中,会调用Transaction transaction字段的getConnection()方法并返回:
protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; }}
5.6. SqlSession.commit()方法执行过程
5.6.1. SqlSession的commit过程
SqlSession.commit()方法对应DefaultSqlSession.commit()方法,该方法中会调用commit(boolean force)方法,参数值为false:
@Overridepublic void commit() { commit(false);}@Overridepublic void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false;
在commit(boolean force)方法中,会调用Executor executor字段的commit()方法,参数值为isCommitOrRollbackRequired()方法的返回值
isCommitOrRollbackRequired()方法代码如下:
private boolean isCommitOrRollbackRequired(boolean force) { return (!autoCommit && dirty) || force;}
由于force参数为false,因此仅当autoCommit=false且dirty=true时,isCommitOrRollbackRequired()方法会返回true
即仅当关闭自动提交,且执行了写操作时,SqlSession在调用Executor的commit()方法时,传入参数为true
在DefaultSqlSession.update()方法中,会将dirty字段设置为true
insert()、delete()方法也是调用了update()方法
即dirty字段代表当前Sql会话是否有执行数据库写操作
5.6.2. Executor的commit过程
在SqlSession.commit()方法中调用Executor.commit()方法时,步骤如下:
- CachingExecutor.commit()
在DefaultSqlSession.commit()方法中,由于DefaultSqlSession中的Executor executor字段类型为CachingExecutor,因此会调用CachingExecutor.commit()方法
在CachingExecutor.commit()方法中,会调用Executor delegate字段的commit()方法
delegate字段的类型为BaseExecutor的子类BatchExecutor、ReuseExecutor、SimpleExecutor,由于以上类中没有实现commit()方法,因此会调用父类BaseExecutor的commit()方法
- BaseExecutor.commit()
在BaseExecutor.commit()方法中,当参数required=true时会调用Transaction transaction字段的commit()方法,参数required对应DefaultSqlSession.commit()方法中isCommitOrRollbackRequired()方法的返回值
即DefaultSqlSession中的autoCommit=false(关闭自动提交)且dirty=true(执行了写操作)时,BaseExecutor.commit()方法中会执行事务对象对应的Transaction.commit()方法
5.6.3. Transaction的commit过程
Transaction实现类的commit()方法处理见后续内容
5.6.4. SqlSession.commit()方法执行过程调用堆栈
以默认使用的Transaction实现类SpringManagedTransaction为例,SqlSession.commit()方法执行过程调用堆栈如下:
org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession:214)org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession:220)org.apache.ibatis.executor.CachingExecutor.commit(CachingExecutor:119)org.apache.ibatis.executor.BaseExecutor.commit(BaseExecutor:244)org.mybatis.spring.transaction.SpringManagedTransaction.commit(SpringManagedTransaction:93)
5.7. SqlSession.rollback()方法执行过程
与commit()方法执行过程类似,略
5.8. 为什么openSession(autoCommit=false)时事务默认未生效
使用SqlSessionFactory接口的实现类SqlSessionFactoryBean时,在buildSqlSessionFactory()方法中决定使用的TransactionFactory transactionFactory字段的类型,若未指定则默认使用org.mybatis.spring.transaction.SpringManagedTransactionFactory类的实例
targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource));
在SpringManagedTransactionFactory的newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit)方法中,生成了SpringManagedTransaction类实例并返回,在生成SpringManagedTransaction类实例时,未使用autoCommit参数
:
@Overridepublic Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new SpringManagedTransaction(dataSource);}
前文已分析,Executor执行数据库读写操作前,会先调用Transaction.getConnection()方法获取数据库连接,默认情况下会执行SpringManagedTransaction.getConnection()方法
SpringManagedTransaction.getConnection()方法中会调用SpringManagedTransaction.openConnection()方法
SpringManagedTransaction.openConnection()方法用于打开数据库连接,autoCommit字段使用当前获取的connection对象的autoCommit
:
this.connection = DataSourceUtils.getConnection(this.dataSource);this.autoCommit = this.connection.getAutoCommit();
前文已分析,SqlSession.commit()方法执行时,会通过Executor,执行Transaction的commit()方法,默认情况下会执行SpringManagedTransaction.commit()方法
SpringManagedTransaction类用于进行事务提交与回滚处理的commit()、rollback()方法中,当autoCommit字段为true时,不会执行connection的commit()、rollback()方法以进行事务提交或回滚:
@Overridepublic void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]"); this.connection.commit(); }}@Overridepublic void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]"); this.connection.rollback(); }}
默认情况下使用的SpringManagedTransactionFactory对应的SpringManagedTransaction不会改变连接对象connection的autoCommit值;在MySQL中,autoCommit参数默认值为true,因此SqlSession默认情况下事务不生效
5.9. 为什么JdbcTransactionFactory能够使事务生效
- JdbcTransactionFactory.newTransaction()方法创建事务对象过程
在TransactionFactory的实现类JdbcTransactionFactory中,newTransaction()方法(DefaultSqlSessionFactory.openSessionFromDataSource()方法中执行的)代码如下:
@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}
以上JdbcTransactionFactory.newTransaction()方法中会创建JdbcTransaction对象,传递了autoCommit参数,对应DefaultSqlSessionFactory.openSessionFromDataSource()方法的autoCommit参数
- JdbcTransaction.getConnection()方法获取数据库连接过程
前文已分析,BaseExecutor中会通过Transaction.getConnection()方法获取Connection
在JdbcTransaction.getConnection()方法中,会调用openConnection()方法
在JdbcTransaction.openConnection()方法中,会调用setDesiredAutoCommit()方法,参数值为类的autoCommit字段
在setDesiredAutoCommit()方法中,会将Connection的autoCommit设置为参数desiredAutoCommit指定的值,当数据库连接的自动提交关闭时,事务可以生效
:
protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } }
以上调用Connection.setAutoCommit()方法设置autoCommit的参数desiredAutoCommit,对应DefaultSqlSessionFactory.openSessionFromDataSource()方法的autoCommit参数
假如使用JdbcTransactionFactory,且调用DefaultSqlSessionFactory.openSessionFromDataSource()方法时autoCommit参数值为false,则获取数据库连接时会关闭自动提交,因此事务可以生效
5.10. MyBatis Mapper初始化过程
MyBatis Mapper对应的Spring Bean的初始化过程中涉及到以下类:
org.mybatis.spring.mapper.MapperFactoryBeanorg.mybatis.spring.support.SqlSessionDaoSupportorg.mybatis.spring.SqlSessionTemplateorg.apache.ibatis.binding.MapperProxy
MyBatis Mapper初始化过程比较复杂,这里不展开说明
MyBatis Mapper在完成初始化之后,会变成动态代理类,对应org.apache.ibatis.binding.MapperProxy类
MapperProxy类中的SqlSession sqlSession字段类型为org.mybatis.spring.SqlSessionTemplate
SqlSessionTemplate类中的SqlSessionFactory sqlSessionFactory字段,对应org.mybatis.spring.mapper.MapperScannerConfigurer中的SqlSessionFactory sqlSessionFactory字段(可以通过sqlSessionFactoryBeanName指定)
前文已说明,在SqlSessionFactory进行处理后,会将Transaction与SqlSession关联起来,在获取数据库连接、执行提交/回滚操作时,都需要调用Transaction
5.11. 直接使用MyBatis Mapper时获取Connection过程
直接使用MyBatis Mapper执行数据库操作时,获取数据库连接Connection的调用堆栈如下:
以下使用的是默认的SpringManagedTransactionFactory对应的SpringManagedTransaction,下同
com.sun.proxy.$Proxy38.insert(Unknown Source)org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:86)org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy:145)org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:62)org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate:272)com.sun.proxy.$Proxy36.insert(Unknown Source)org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:427)java.lang.reflect.Method.invoke(Method:498)sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl:43)sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl:62)sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession:181)org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession:194)org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor:76)org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor:117)org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor:49)org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor:86)org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor:337)org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction:66)
直接使用MyBatis Mapper执行数据库操作时,获取数据库连接Connection的过程与使用SqlSessionFactory.openSession()方法时的过程类似
5.12. 直接使用MyBatis Mapper时创建SqlSession与commit过程
直接使用MyBatis Mapper执行数据库操作时,执行commit的调用堆栈如下:
com.sun.proxy.$Proxy38.insert(Unknown Source)org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:86)org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy:145)org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:62)org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate:272)com.sun.proxy.$Proxy36.insert(Unknown Source)org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:431)org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession:220)org.apache.ibatis.executor.CachingExecutor.commit(CachingExecutor:119)org.apache.ibatis.executor.BaseExecutor.commit(BaseExecutor:244)org.mybatis.spring.transaction.SpringManagedTransaction.commit(SpringManagedTransaction:93)
在SqlSessionTemplate$SqlSessionInterceptor.invoke()方法中,完成获取SqlSession、执行数据库语句、提交事务等操作,代码如下:
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result;}
- 获取SqlSession
执行SqlSessionUtils类的getSqlSession()方法,获取SqlSession
- 执行数据库语句
执行method.invoke()方法,执行数据库语句
- 提交事务
当isSqlSessionTransactional()返回false,即当前事务不是由Spring管理时,执行sqlSession.commit()方法,提交事务
5.13. 为什么JdbcTransactionFactory会使所有Mapper都使用事务
以上执行的SqlSessionUtils.getSqlSession()方法代码如下:
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);SqlSession session = sessionHolder(executorType, holder);if (session != null) { return session;}LOGGER.debug(() -> "Creating a new SqlSession");session = sessionFactory.openSession(executorType);
TransactionSynchronizationManager.getResource()方法用于获取Spring管理的事务信息,由于未使用Spring管理事务,因此以上获取到的SqlSessionHolder holder、SqlSession session均为null
后续会执行sessionFactory.openSession()方法,对应DefaultSqlSessionFactory的openSession(ExecutorType execType)方法:
public SqlSession openSession(ExecutorType execType) { return openSessionFromDataSource(execType, null, false);}
以上对应的DefaultSqlSessionFactory.openSession()方法中调用openSessionFromDataSource()方法时,参数3 autoCommit指定的值是false
假如SqlSessionFactoryBean中的transactionFactory字段使用JdbcTransactionFactory代替默认的SpringManagedTransactionFactory,在前文已说明在创建数据库连接时会关闭自动提交,会使事务生效
5.14. 为什么事务未提交时SqlSession关闭会回滚事务
SqlSession的数据库操作执行完毕进行关闭时,调用堆栈如下:
org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession:260)org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor:64)org.apache.ibatis.executor.BaseExecutor.close(BaseExecutor:87)org.apache.ibatis.executor.BaseExecutor.rollback(BaseExecutor:256)org.apache.ibatis.transaction.jdbc.JdbcTransaction.rollback(JdbcTransaction:78)
DefaultSqlSession.close()部分代码如下:
executor.close(isCommitOrRollbackRequired(false));
isCommitOrRollbackRequired()方法前文已分析过,仅当关闭自动提交(autoCommit=false),且执行了写操作(dirty=true)时会返回true
BaseExecutor.close()方法中会调用rollback()方法,部分代码如下:
@Overridepublic void close(boolean forceRollback) { try { try { rollback(forceRollback);
BaseExecutor.rollback()方法代码如下:
public void rollback(boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true); } finally { if (required) { transaction.rollback(); } } }}
在BaseExecutor.rollback()方法中,当required参数为true时,会调用Transaction transaction字段的rollback()方法。required参数对应BaseExecutor.close()方法的forceRollback参数,对应DefaultSqlSession.isCommitOrRollbackRequired()方法返回值
即DefaultSqlSession中的autoCommit=false(关闭自动提交)且dirty=true(执行了写操作)时,BaseExecutor.rollback()方法中会执行事务对象对应的Transaction.rollback()方法
(SqlSession.close()方法中会执行事务回滚操作,可以通过try-with-resource方式使用SqlSession对象,不需要在异常时显式执行rollback()方法。但由于MyBatis不建议以上使用事务的方式,因此可以忽略)
5.15. 为什么事务正常提交后SqlSession关闭不会回滚事务
在DefaultSqlSession.commit()方法中,执行Executor executor字段的commit()方法后,会将dirty字段修改为false
在以上情况下isCommitOrRollbackRequired()方法会返回false,BaseExecutor.rollback()方法执行时required参数为false,不会再执行Transaction transaction字段的rollback()方法
因此事务正常提交后SqlSession关闭不会回滚事务
5.16. 为什么openSession(ExecutorType.BATCH)时批量执行默认未生效(MySQL)
参考https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-performance-extensions.html
mysql-connector的文档中说明,rewriteBatchedStatements参数决定executeBatch()方法执行时,是否需要将预编译的insert与replace语句重写为多值语句
该参数默认值为false,默认情况下不会将SQL语句重写为批量执行形式,即默认未开启SQL语句的批量执行
5.17. 为什么只有insert、replace支持批量执行
以上mysql-connector的文档中已说明insert与replace语句支持批量执行
在mysql-connector的com.mysql.cj.QueryInfo类,构造函数QueryInfo(String sql, Session session, String encoding)中,仅当处理insert或replace语句,且rewriteBatchedStatements参数值为true时,isRewritableWithMultiValuesClause字段值为true:
boolean rewriteBatchedStatements = session.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue();...// Only INSERT and REPLACE statements support multi-values clause rewriting.boolean isInsert = strInspector.matchesIgnoreCase(INSERT_STATEMENT) != -1;if (isInsert) { strInspector.incrementPosition(INSERT_STATEMENT.length()); // Advance to the end of "INSERT".}boolean isReplace = !isInsert && strInspector.matchesIgnoreCase(REPLACE_STATEMENT) != -1;if (isReplace) { strInspector.incrementPosition(REPLACE_STATEMENT.length()); // Advance to the end of "REPLACE".}// Check if the statement has potential to be rewritten as a multi-values clause statement, i.e., if it is an INSERT or REPLACE statement and// 'rewriteBatchedStatements' is enabled.boolean rewritableAsMultiValues = (isInsert || isReplace) && rewriteBatchedStatements;...this.isRewritableWithMultiValuesClause = rewritableAsMultiValues;
QueryInfo.isRewritableWithMultiValuesClause()方法返回了isRewritableWithMultiValuesClause字段值:
public boolean isRewritableWithMultiValuesClause() { return this.isRewritableWithMultiValuesClause;}
在com.mysql.cj.jdbc.ClientPreparedStatement类的executeBatchInternal()方法中,当QueryInfo.isRewritableWithMultiValuesClause()方法返回true时,才会执行executeBatchWithMultiValuesClause()方法,executeBatchWithMultiValuesClause()方法用于将预编译的insert与replace语句重写为多值语句:
if (getQueryInfo().isRewritableWithMultiValuesClause()) { return executeBatchWithMultiValuesClause(batchTimeout);}
6. 可参考的内容
关于autoCommit参数及MySQL数据库操作使用事务时的执行过程,可参考以下内容:
“MySQL SQL语句与事务执行及日志分析”https://blog.csdn.net/a82514921/article/details/126563449
“Spring、MyBatis、Druid、MySQL使用事务执行SQL语句分析”https://blog.csdn.net/a82514921/article/details/126563542
关于对MySQL的SQL语句与事务执行过程的分析与监控方式,可参考以下内容:
“Spring、MyBatis、Druid、MySQL执行SQL语句与事务监控”https://blog.csdn.net/a82514921/article/details/126563558
“tcpdump、Wireshark抓包分析MySQL SQL语句与事务执行”https://blog.csdn.net/a82514921/article/details/126563471
来源地址:https://blog.csdn.net/a82514921/article/details/129370033
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341