【Spring】代码与事务的解耦——声明式事务控制

编程式事务控制(了解)

▍三大相关对象

1. PlatformTransactionManger 平台事务管理器 --------------------------------------------------

理解:
PlatformTransactionManger其实是一个接口,根据不同的平台对应使用不同的实现类(jdbc和mybatis对应org.springframework.jdbc.datasource.DataSourceTransactionManager,hibernate对应org.springframework.orm.hibernate5.HibernateTransactionManager)

提供的方法:
TransactionStatus getTransaction(TransactionDefinition definition) --> 获取事务状态信息
void commit(TransactionStatus status) --> 提交事务
void rollback(TransactionStatus status) --> 回滚事务



2. TransactionDefinition 事务定义对象 --------------------------------------------------------

理解:
事务定义对象包含某一个事务的具体定义信息

提供的方法:
int getIsolationLevel() --> 获取事务的隔离级别
int getPropagationBehavior() --> 获取事务的传播行为
int getTimeout() --> 获取超时时间
boolean isReadOnly() --> 是否只读



3. TransactionStatus 事务状态对象 --------------------------------------------------------

理解:
很简单,就是某一个事务当前的状态

提供的方法:
boolean hasSavapoint() --> 是否存储回滚点
boolean isCompleted() --> 是否完成
boolean isNewTransaction() --> 是否是新事务
boolean isRollbackOnly() --> 是否回滚




总结 >_< -----------------------------------------------------------------------------------

1)PlatformTransactionManger和TransactionDefinition需要我们主动配置,告诉Spring这些信息;
   TransactionStatus不需要我们配置,因为这是被动的信息。

2)重点在于理解这三个对象,其提供的方法并不是非要记住。

 
▍隔离级别(Isolation Level)

事务的并发性产生以下3个经典问题:

问题 描述
脏读 读取未提交的数据——A事务不知道B事务即将回滚,读取了脏数据
不可重复读 前后两次读取数据本身不一致——A事务比较大,在前后两次读取之间,数据被B事务修改
幻读 前后两次读取数据总量不一致——A事务比较大,在前后两次统计数据之间,B事务增加了几条数据,出现了“凭空多出几条数据”的幻觉

Spring提供5种隔离级别用以解决以上问题:

隔离级别 描述及可解决的问题
DEFAULT(默认) 不同种类的数据库,默认的事务隔离级别是不一样的
READ_UNCOMMITED(读未提交) 能读到未提交的数据,一点儿限制都没有,不能解决脏读、不可重复读、幻读的任何一种
READ_COMMITED(读已提交) 只能读已提交的数据,自然就防止了脏读,但不能解决不可重复读、幻读
REPEATABLE_READ(可重复读) 对于读出的数据加锁,但也只保护的已经读出的数据,因此能解决脏读、不可重复读,但无法解决幻读
SERLALIZABLE(串行化) 最高隔离级别,事务必须一个接一个串行执行,解决了所有问题,但并发效率极低

 
▍传播行为(Propagation Behavior)

听起来很高端,其实很简单。
传播行为就是 一个事务中的方法,调用了另一个事务中的方法,另一个事务中的方法会采取的行为。
通俗的说,就是methodA方法,调用了methodB方法,那么methodB是在methodA所在是事务中运行呢,还是另开一个新的事务运行呢?

REQUIRED 支持当前事务,假设当前没有事务,就新建一个事务
SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
REQUIRES_NEW 新建事务,假设当前存在事务,就把当前事务挂起
NOT_SUPPORTED 以非事务方式运行,假设当前存在事务,就把当前事务挂起
NEVER 以非事务方式运行,假设当前存在事务,就抛出异常
NESTED 在嵌套事务内执行,假设当前没有事务,则执行与REQUIRED类似的操作。

 
 
 

声明式事务控制(理解)

声明式事务控制的最大优点是,业务代码与事务管理的完全独立,即完全解耦。

你应该已经意识到了,声明式事务控制其实就是对原先业务代码的增强——即AOP编程思想。

那么,接着思考下面的问题:

  1. 谁是切点?业务代码就是切点。
  2. 谁是通知?事务管理就是通知。
  3. 如何配置织入?还是老生常谈的xml配置和注解配置。

 
 

声明式事务控制(基于xml)

固定模板三步走(重要模板):

<!-- 提前引入tx命名空间 -->
<!-- 提前配置DataSource数据源 -->


<!-- 1.配置平台事务管理器(第二步要用) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2.配置增强(即事务管理) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

<!-- 3.配置织入 -->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="execution(* com.samarua.service.impl.*.*(..))" />
</aop:config>

其中第二步配置增强的事务管理时,可以指定事务的相关属性,以及应用在哪个方法上:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="method1" isolation="READ_COMMITED" propagation="REQUIRED" timeout="-1" read-only="false" />
        <tx:method name="method2" isolation="READ_COMMITED" propagation="REQUIRED" timeout="-1" read-only="false" />
        <tx:method name="method3" isolation="READ_COMMITED" propagation="REQUIRED" timeout="-1" read-only="false" />
        <tx:method name="method4" isolation="READ_COMMITED" propagation="REQUIRED" timeout="-1" read-only="true" />
        <!-- 相关属性分别是:隔离等级 传播行为 超时时间 是否只读 -->
    </tx:attributes>
</tx:advice>

 
 

声明式事务控制(基于注解)

只用到了一个注解:@Transactional

注解在整个类上,表示整个类的所有方法得到增强;注解到某一个方法上,表示只增强该方法

① 事务的注解驱动

<!--事务的注解驱动-->
<tx:annotation-driven />

② 代码编写

public class AccountServiceImpl implements AccountService {
    
    

    @Autowired
    private AccountDao accountDao;
    
    @Transactional(isolation=Isolation.READ_COMMITTED, propagation=Propagation.REQUIRED, timeout=-1, read-only=true)
    public void transfer(String User1, String User2, double money) {
    
    
        accountDao.out(outMan,money);
        int i = 1/0;
        accountDao.in(inMan,money);
    }
}

 
 
 
 

 
 
 
 

 
 
 
 

More >_<

猜你喜欢

转载自blog.csdn.net/m0_46202073/article/details/114084473