文章目录
- [1. SqlSession的差异](#1. SqlSession的差异)
- [2. Executor的差异](#2. Executor的差异)
-
- [2.1 SimpleExecutor流程说明](#2.1 SimpleExecutor流程说明)
- [2.2 ReuseExecutor流程说明](#2.2 ReuseExecutor流程说明)
- [2.3 BatchExecutor流程说明](#2.3 BatchExecutor流程说明)
- [3. Mybatis事务](#3. Mybatis事务)
- [4. Spring事务](#4. Spring事务)
- [5. 总结](#5. 总结)
本篇文章主要是由一次批量插入数据而引起的思考与探究,在这篇文章中将会分析不同的Executor和SqlSession的实现原理差异,同时也会分析Mybatis和Spring的事务处理差异并比较他们之间的优先级与关系。
1. SqlSession的差异
Mybatis依赖于SqlSession来完成sql的调用,Mybatis和Spring集成时,SqlSession常用的类型有两种:
- DefaultSqlSession:如果通过SqlSessionFactory的openSession方法获取SqlSession,其生成的类型就是DefaultSqlSession。使用该类时需要开发者执行完业务逻辑后自行commit、rollback或close;
- SqlSessionTemplate:如果使用的是@Mapper或@MapperScan等方式通过Spring自动扫描注册的方式注入Mapper类,Mapper类则会被SqlSessionTemplate代理。在代理切面中调完方法后会自动commit、rollback或close,无需开发者关心介入。但在切面中实际使用的SqlSession类型依然是DefaultSqlSession。
总结: DefaultSqlSession是Mybatis操作Mapper方法的唯一入口,而SqlSessionTemplate则是使用代理方式包装了一层,在代理方法中使用DefaultSqlSession完成了commit、rollback和close。
所以不难理解为什么SqlSessionTemplate的commit、rollback和close三个方法未实现,调用则报错,因为其只提供代理的模板方法,不提供真实逻辑操作。
DefaultSqlSession类有个autoCommit属性,很多人会误以为这个属性是真正控制事务自动提交的,实际上不是,这个属性只会控制Mybatis的事务管理器是否调用commit、rollback方法,和事务的自动提交没有关系。 SqlSession基本每次事务调用都会生成一个新的。
2. Executor的差异
Mapper的增删改调用的都是Executor的doUpdate方法,查则调用的doQuery方法。
Mybati的Executor可供选择的有三种:
- SIMPLE:默认的执行器,实现类SimpleExecutor,其实现非常简单,从Datssource中获取Connection,再用Connection获取Statement实现类,最后执行增删改查;
- REUSE:实现类ReuseExecutor,在SIMPLE的基础上加入了Statement对象的缓存,如果对应的sql有Statement对象缓存则直接使用,避免了Statement对象的频繁创建销毁;
- BATCH:实现类BatchExecutor,增删改时会把Statement对象缓存起来,并添加到批处理中,在进行查询或commit时将会批量执并清空Statement缓存。
Executor的生命周期和SqlSession基本等同,其中的Transaction对象则是实例化Executor时由SqlSession设置进来的。
2.1 SimpleExecutor流程说明
- 调用时会从Connection中初始化Statement对象,而Connection会由Transaction对象维护;
- 获取Statement之后直接调用执行方法,执行sql;
- 最后关闭Statement对象。
2.2 ReuseExecutor流程说明
- 用sql判断Statement是否已生成, 如果已生成则使用缓存的Statement对象,并重新设置超时时间;未生成则流程和SimpleExecutor一致;
- 获取Statement之后直接调用执行方法,执行sql;
- 调用commit/rollback后会调用doFlushStatements方法清空Statement缓存,相当于只在一次事务中Statement会反复使用,这个步骤没在图中标注。
2.3 BatchExecutor流程说明
- 判断生成sql和MappedStatement对象和上次调用是否相同,如果相同则只会重新设置Statement对象的超时时间,并更新缓存;否则会重新实例化Statement
- 调用时会从Connection中初始化Statement对象,而Connection会由Transaction对象维护;
- 获取Statement后记录当前sql和MappedStatement对象,随后添加到缓存statementList中,并实例化BatchResult添加到缓存batchResultList;
- 调用Statement的addBatch方法添加到批处理中;
注: BatchExecutor的增删改流程相当于只是在本地做了初始化操作并添加到了缓存中,没有实际调用数据库,因此如果是Mysql数据库,此时是拿不到自增主键的,因为还没有和数据库交互。
BatchExecutor的查询/commit流程图:
- 从statementList和batchResultList分别获取Statement和BatchResult对象;
- 执行Statement对象的executeBatch方法执行批量操作;
- 执行KeyGenerator生成主键并回填;
- 保存批量操作结果;
- 最后关闭Statement对象并清空缓存。
查询、commit或rollback时都会调用Executor的doFlushStatements方法,如果是rollback调用,则会直接返回空。只有查询和commit才会执行图片中的流程。
3. Mybatis事务
Mybatis的事务对象Transaction会在SqlSession中实例化Executor时由TransactionFactory实例化,并注入到Executor中。在Executor中实际操作的事务就是Transaction对象,因此Mybatis的事务实际提交与否和Transaction的实现类有关。
TransactionFactory和Transaction对象的对应关系表:
Transaction类型 | TransactionFactory类型 | 是否默认 | 设置autoCommit | commit/rollback | 备注说明 |
---|---|---|---|---|---|
JdbcTransaction | JdbcTransactionFactory | 否 | 可设置 | 可操作 | 非默认类型,需要开发者手动指定,可通过方法直接操作Connection对象 |
ManagedTransaction | ManagedTransactionFactory | 官方默认 | 不可设置 | 不可操作 | Mybatis未指定TransactionFactory时的默认类型,负责打开Connection连接,但不进行实际的commit/rollback操作 |
SpringManagedTransaction | SpringManagedTransactionFactory | Spring默认 | 不可设置 | 未在Spring事务可操作 | 对接Spring时指定的默认类型,和JdbcTransaction类似,但是不能设置Connection对象的autoCommit属性,同时会判断Connection是否处于Spring的事务管理中,如果是则不会进行commit/rollback操作 |
由上面的表格内容可反推:Mybatis操作Datasource的Connection是由Transaction来完成的,常用的有两种:
- 由ManagedTransactionFactory生成的ManagedTransaction来完成,不能使用ManagedTransaction来控制connection的commit/rollback,只能直接使用Connection;
- 由SpringManagedTransactionFactory生成的SpringManagedTransaction来完成,会获取Datasource的自动提交属性,如果autocommit属性为true,则commit/rollback无效,如果autocommit为false,则commit/rollback生效。在Spring的事务管理中commit/rollback也无效。
注意: Druid的defaultAutoCommit属性默认是true,因此Druid会自动提交。
4. Spring事务
Spring事务通常有两种实现方式:
- 使用TransactionTemplate显式编程处理;
- 使用@Transactional切面进行处理。
使用TransactionTemplate的好处是不会受到任何约束,只要注入了TransactionTemplate,就可以直接使用,而不像@Transactional一样必须要求触发切面逻辑。其实现原理就是帮我们把try-catch块的提交回滚逻辑固定了,开发者只需要关注事务内部的业务逻辑即可。
使用这两种方式都会通过PlatformTransactionManager获取Connection,此时会把autocommit设置为false,因此事务不会自动提交。在切面即将结束时才会调用Connection的commit方法提交事务。
Spring的具体事务操作类是交给PlatformTransactionManager的实现类,如果使用的是Datasource,实现类一般是DataSourceTransactionManager,其实际管理的是由Datasource生成的Connection类,commit/rollback都是使用Connection对应的方法,autoCommit也是Connection的属性。
5. 总结
- 事务提交回滚与Datasource生成的Connection对象是否调用commit/rollback或autoCommit属性有关,第三方框架管理事务通过Connection对象操作;
- Spring的事务管理优先级大于Mybatis的事务管理,实际事务commit/rollback与否以Spring为主;
- Mybatis对接Spring使用的是SpringManagedTransaction,内部使用ThreadLocal完成Spring是否管理事务判断;
- 只通过Mybatis操作数据库事务,commit与否的优先级数据源autoCommit>Mybatis;
- Spring容器的事务通常由DataSourceTransactionManager管理实现。