Spring 的事务管理
事务原本是数据库的概念,在 Dao 层。
一般情况下,需要将事务提升至业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务
在 Spring 中通常可以使用以下两种方式来实现对事务的管理
- 使用 Spring 的事务注解开管理事务
- 使用 AspectJ 的 AOP 配置来管理事务
Spring 事务管理 API
Spring 的事务管理,主要用到两个事务相关的接口
事务管理器接口 PlatformTransactionManager
事务管理器,就是 PlatformTransactionManager 接口对象。它的主要功能是完成事务的提交、回滚、获取事务状态信息
常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类
- DataSourceTransectionManager:使用 JDBC 或 Mybatis 进行数据库操作时使用
- HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用
Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
RuntimeException 及其子类以外的异常,均属于受查异常
事务定义接口 TransectionDefinition
事务定义接口 TransectionDefinition 中定义了事务描述相关的三类常量,以及对它们的操作
这三类常量为:
- 事务隔离级别
- 事务传播行为
- 事务默认超时时限
隔离级别
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_DEFAULT
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
READ_UNCOMMITTED:读未提交。未解决任何并发问题。
READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
SERIALIZABLE:串行化。不存在并发问题。
事务传播行为
以该传播行为加在 doOther 方法上为例
1、PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。
这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。
若 doSome()方法在调用doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
2、PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
3、PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务
默认事务传播超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句执行时长。
注意:事务的超时时限起作用的条件比较多,而且超时的时间计算点比较复杂。所以这个属性值一般使用默认值即可!
使用 Spring 的事务注解管理事务(掌握)
通过 @Transactional 注解方式,可以将事务植入到相应 public 方法中,实现事务管理
@Transactional 注解的所有可选属性如下所示(属性与版本有关,更新了可能会有新的属性)
propagation:用于设置事务的传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED
isolation:用于设置事务的隔离级别。该属性的类型为 Isolation 枚举,默认值为 Isolation.DEFAULT
readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false
timeout:用于设置本次操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1 ,即没有时限
rollbackFor:指定需要回滚的异常类,类型为 Class[] ,默认值为空数组。当然,若只有一个异常类时,可以不使用数组
rollbackForClassName:指定需要回滚的异常类类名。类型为 String[] ,默认值为空数组。当然,若只有一个异常类时,可以不使用数组
noRollbackFor:指定不需要回滚的异常类。类型为 Class[] ,默认值为空数组。当然,若只有一个异常类时,可以不使用数组
noRollbackForName:指定不需要回滚的异常类类名,类型为 String[] ,默认值为空数组。当然,若只有一个异常类时,可以不使用数组
注意
- @Transactional 若在方法上,只能用于 public 方法。对于其他不是 public 的方法,如果加上了注解 @Transactional ,虽然不会报错,但不会将指定事务植入该方法中。因为 Spring 会忽略掉所有非 public 方法上的 @Transactional 注解
- 若 @Transactional 注解在类上,则表示该类上所有的方法均将在执行时植入事务
实现步骤
1、声明事务管理器
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
2、开启注解驱动
<!-- 声明事务注解驱动 -->
<!-- 其中transaction-manager属性:值是事务管理器bean的id -->
<tx:annotation-driven transaction-manager="transactionManager"/>
3、业务层 public 方法加入事务属性
@Transactional(propagation = Propagation.REQUIRED,
rollbackFor = { NotEnoughException.class,
NullPointerException.class })
public void buy(Integer goodsId,Integer amount){
Sale sale = new Sale(goodsId,amount);
saleDao.insertSale(sale);
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NullPointerException("无此商品");
}
if(goods.getAmount() < amount){
throw new NotEnoughException("库存不足");//自定义异常:NotEnoughException
}
goods = new Goods(amount,goodsId);
goodsDao.updateGoods(goods);
}
使用 AspectJ 的 AOP 配置管理事务(掌握)
使用 XML 配置事务代理的方式的不足是:每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需要将前面代码中关于事务代理的配置删除,再替换为如下内容即可
需要配置 maven 依赖
<!-- spring框架 联合aspects切面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
在容器中添加事务管理器
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置事务通知
为事务通知设置相关属性,用于指定要将事务以什么方式植入给哪些方法
例如:应用到 buy 方法上的事务要求是必须的,并且当 buy 方法发生异常后要回滚业务
<!-- 事务通知(增强) -->
<tx:advice id="buyAdvice" transaction-manager="transactionManager">
<!-- 配置事务属性 -->
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.gg.exceptions.NotEnoughException"/>
<tx:method name="add" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.Exception"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
配置增强器
指定将配置好的事务通知,植入给谁
advisor:顾问
<!-- aop配置:通知应用的切入点以及切面 -->
<aop:config>
<aop:pointcut expression="execution(* *..service*.*(..))" id="servicePt" />
<!-- 把事务的通知设置到切面方法上 -->
<aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt" />
</aop:config>
修改测试类
测试类中要从容器中获取的是目标对象
@Test
public void testBuy(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");
service.bug(1001,20);
}