Spring事务

159 阅读5分钟

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()方法会创建一个事务,并在其中执行。

required.png

2、PROPAGATION_REQUIRES_NEW

总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

requirednew.png

3、PROPAGATION_SUPPORTS

指定的方法支持当前事务,但若当前没有事务,也可以以非事务

supports.png

默认事务传播超时时限

常量 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);
}