我的编程空间,编程开发者的网络收藏夹
学习永远不晚

MyBatis SqlSession事务与批量执行正确方式(默认不生效)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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对象

参考该方法的说明:https://mybatis.org/spring/apidocs/reference/org/mybatis/spring/SqlSessionFactoryBean.html#setTransactionFactory(org.apache.ibatis.transaction.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. 相关组件及版本

组件版本
spring5.3.22
mybatis3.5.9
mybatis-spring2.0.7
druid1.2.10
mysql-connector-java8.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会话,其中包含了Executororg.apache.ibatis.session.SqlSessionManager

org.apache.ibatis.session.defaults.DefaultSqlSession

org.mybatis.spring.SqlSessionTemplate

org.apache.ibatis.executor.Executor数据库操作执行类,其中包含了Transactionorg.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管理事务,其中包含了Connectionorg.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实现类对应包名
SpringManagedTransactionFactorySpringManagedTransactionorg.mybatis.spring.transaction
JdbcTransactionFactoryJdbcTransactionorg.apache.ibatis.transaction.jdbc
ManagedTransactionFactoryManagedTransactionorg.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类型说明
BATCHBatchExecutor支持批量执行的执行器
REUSEReuseExecutor支持重用预编译SQL语句的执行器
SIMPLESimpleExecutor简单执行器

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

MyBatis SqlSession事务与批量执行正确方式(默认不生效)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录