Mybatis事务

概述

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>

提示如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

这两种事务管理器类型都不需要任何属性。它们不过是类型别名,换句话说,你可以使用 TransactionFactory 接口的实现类的完全限定名或类型别名代替它们。

public interface TransactionFactory {
  void setProperties(Properties props);  
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);  
}

任何在 XML 中配置的属性在实例化之后将会被传递给 setProperties() 方法。你也需要创建一个 Transaction 接口的实现类,这个接口也很简单:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。

MyBatis事务遇到的问题

如果开启MyBatis事务管理,则需要手动进行事务提交,否则事务会回滚到原状态;

如果在具体操作执行完后不通过sqlSession.commit()方法提交事务,事务在sqlSession关闭时会自动回滚到原状态;只有执行了commit()事务提交方法才会真正完成操作;

如果不执行sqlSession.commit()操作,直接执行sqlSession.close(),则会在close()中进行事务回滚;

如果不执行sqlSession.commit()操作也不手动关闭sqlSession,在程序结束时关闭数据库连接时会进行事务回滚;

事务管理入口

在XML配置文件中定义事务工厂类型,JDBC或者MANAGED分别对应JdbcTransactionFactory.class和ManagedTransactionFactory.class;

如果type=”JDBC”则使用JdbcTransactionFactory事务工厂则MyBatis独立管理事务,直接使用JDK提供的JDBC来管理事务的各个环节:提交、回滚、关闭等操作;

如果type=”MANAGED”则使用ManagedTransactionFactory事务工厂则MyBatis不在ORM层管理事务而是将事务管理托付给其他框架,如Spring

事务对UPDATE操作的影响

事务的提交

在sqlSession中执行了UPDATE操作,需要执行sqlSession.commit()方法提交事务,不然在连接关闭时候会自动回滚;

exector.commit()事务提交方法归根到底是调用了transaction.commit()事务的提交方法;这里的transaction就是根据配置对应的JdbcTransaction或者ManagedTransaction;

如果是JdbcTransaction的commit()方法,通过调用connection.commit()方法通过数据库连接实现事务提交;

如果是ManagedTransaction的commit()方法,则为空方法不进行任何操作;

事务的回滚

sqlSession执行close()关闭操作时,如果close()操作之前进行了UPDATE操作未进行commit()事务提交则会进行事务回滚然后再关闭会话;如果update后执行了commit则直接关闭会话;

在DefaultSqlSession类中如果执行了UPDATE操作则会将标志位dirty赋值为true

在事务提交时会将dirty赋值为false;

在关闭会话时会判断dirty是否为true,如果为true则需要进行事务回滚操作,否则直接关闭会话

判断dirty标志是否为true

如果dirty为true则判定forceRollback为true,执行回滚操作;

事务对SELECT的影响

事务对select操作的影响主要体现在对缓存的影响上,主要包括一级缓存和二级缓存

一级缓存

因为一级缓存是Session级别的,事务的提交回滚对MyBatis的一级缓存没有影响;一级缓存放在BaseExector中的PerpectualCache类型的localCache中;

二级缓存

在Mapper.xml文件解析时会根据文件中的标签或者创建Cache实例,并将该实例放入每一个MappedStatement中,在MappedStatement执行select操作时候会获取该cache;

查询二级缓存

根据Statement所在的Mapper的cache缓存对象和根据statement生成的cacheKey,从正式缓存中取缓存数据

根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache,TransactionCache中保存这正式缓存delegate和临时未提交缓存entiryToAddOnCommit;

获取缓存时直接从正式缓存delegate中查询

存储二级缓存

如果从二级缓存中未命中缓存,则需要从数据库中查取,再将查询结果放入二级缓存中;查询结果首先放入二级缓存临时缓存中,只有执行了commit()事务提交才正式转移到正式缓存中;也就是说只有执行了commit()方法的缓存才被下次查询使用,不然仍会执行数据库查询任务并覆盖上次的临时缓存;

根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache

将数据放入TransactionCache中的临时缓存entiryToAddOnCommit中

提交二级缓存

执行事务提交时候会调用TransactionCacheManager内的commit()方法提交缓存,每一个cachingExecutor对应一个TransactionCacheManager,也就是一个SqlSeesion对应一个TransactionCacheManager,因此sqlSession.commit()是提交了当前会话的所以二级缓存的临时缓存;每个sqlSession对每一个Mapper的cache都有一个临时缓存,多个sqlSession共享一个Mapper的cache的正式缓存;

将临时缓存entiryToAddOnCommit中的数据转移到正式缓存delegate中

回滚二级缓存

  • 如果之前未执行UPDATE操作,dirty标志为false,在关闭会话之前没有进行事务提交则进行数据库回滚和缓存提交;

  • 如果执行了UPDATE操作,dirty标志为true,在关闭会话之前没有进行事务提交则进行数据库回滚和缓存回滚;

  • 可以调用session.close(true)关闭会话时强行回滚缓存;

总结

  • MyBatis可以通过XML配置是否独立处理事务,可以选择不单独处理事务,将事务托管给其他上层框架如spring等;

  • 如果MyBatis选择处理事务则事务会对数据库操作产生影响

  • 对UPDATE操作的影响主要表现在UPDATE操作后如果没有进行事务提交则会子啊会话关闭时进行数据库回滚;

  • 对SELECT操作的影响主要表现在二级缓存上,执行SELECT操作后如果未进行事务提交则缓存会被放在临时缓存中,后续的SELECT操作无法使用该缓存,直到进行commit()事务提交,临时缓存中的数据才会被转移到正式缓存中;

Last updated

Was this helpful?