到底什么是事务管理?
场景:
下订单,需要对数据的操作有
- 检查库存
- 扣除库存
- 生成订单记录
三次数据操作的集合就是一个事务。
对于这样的事务,报错情况有
- 库存不满足 抛出 NotEnoughException
- 扣除库存时失败 抛出 RemoveInventoryException
- 生成订单时失败 抛出 CreateOrderException
如果第一步和第二步出错还好,可以直接在业务逻辑里处理Exception就好,但是如果在第三步的时候除了错,就需要把第二步扣除掉的库存恢复。这是最原始的逻辑。
数据库提供了事务操作的语句
begin
commit
rollback
对应Java 封装操作
Connection cnn=null;
conn=DriverManager.getConnection(url,user,password);
conn.setAutoCommit(false);
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
//这里执行语句
//如果成功的话
conn.commit();
//如果失败的话
conn.rollback();
如果你愿意,可以把所有的业务逻辑这么封装。但是如果借助AOP+Spring的话,一切都变得异常简单了!
从配置开始
Maven中添加tx依赖库(其余Spring系列、Mybatis 等参照前文配置)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
依然是Java Config,只需要两步,第一步配置DataSourceTransactionManager
成Bean
。第二步声明使用注解事务@EnableTransactionManagement
因为我将DataSource
配置在了 MyBatisConfig.java
内,所以和数据库关联的一起配置在内了(亦可新建.java
,注解@Configuration
,在WebMVCIniatializer.java
内的getRootConfigClasses()
里添加这个配置类)
完整类如下。
@Configuration
@MapperScan("jufou.info.mapper")
@EnableTransactionManagement
public class MyBatisConfig {
@Bean
public DataSource dataSource(){
BasicDataSource basicDataSource=new BasicDataSource();
basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
basicDataSource.setUsername("root");
basicDataSource.setPassword("Nieve7658");
basicDataSource.setUrl("jdbc:mysql://localhost:3306/test");
return basicDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(){
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/jufou/info/mybatis/mappers.xml"));
} catch (IOException e) {
e.printStackTrace();
}
return sqlSessionFactoryBean;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
public SqlSession sqlSession(){
SqlSessionTemplate sqlSessionTemplate= null;
try {
sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactoryBean().getObject());
} catch (Exception e) {
e.printStackTrace();
}
finally {
return sqlSessionTemplate;
}
}
}
使用注解事务管理
在你需要事务管理函数上使用@Transactional
即可。如果在类上注解,则该类所有函数都会有事务管理
事务管理的具体配置
事务管理配置分为
传播行为
REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
隔离级别(来自百度百科)
未授权读取也称为读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
授权读取也称为读提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
可重复读取(Repeatable Read):禁止[不可重复读取]和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
设定对特定Exception生效
默认对所有Exception生效
使用rollbackFor
与notRollbackFor
完成
Sample
@Transactional(isolation = Isolation.READ_COMMITTED,rollbackFor = Exception.class,propagation = Propagation.REQUIRED)