Spring事务管理
事务回顾
事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。事务操作能够保证数据操作的安全性。
以一个简单的银行转账案例说明,张三和李四账户都有两千元,现在张三要给李四转账1000元,那么需要两步操作,第一修改张三账户扣除1000元,第二修改李四账户增加1000元。这个转账操作不应该出现张三转账时由于某种原因钱转出去了但李四没收到。所以对于这样一组操作我们应该用事务来进行管理。那么一旦这组操作加入到了事务管理之中,它们就必须一起成功或者一起失败。
事务的特性(ACID特性)
- 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生要么都不发生 - 一致性(Consistency)
一致性指事务前后数据的完整性必须保持一致 - 隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离 - 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响
Spring事务接口
Spring事务管理高层抽象主要包括3个接口:PlatformTransactionManager
平台事务管理器,TransactionDefinition
事务定义信息(隔离、传播、超时、只读),TransactionStatus
事务具体运行状态。
Spring在进行事务管理的时候首先会根据事务定义信息,由事务管理器真正进行事务管理操作(事务提交、回滚等),在进行事务管理过程中事务会产生一些相应的状态,这些状态就保存在TransactionStatus
中。
PlatformTransactionManager接口
Spring为不同的持久化框架提供了不同PlatformTransactionManager
接口实现,通常我们都是使用前两种。
TransactionDefinition定义事务隔离级别
如果不考虑隔离性,会引发安全问题如下:脏读、不可重复读、幻读,隔离级别就是用来解决这几种读的问题的。
正常情况下数据库为我们提供了四种隔离级别,而default是spring提供的选择项。
TransactionDefinition定义事务传播行为
事务传播行为主要解决业务层方法之间的相互调用而产生的事务应该如何传递的问题。比如在a方法中调用了b方法
对于第一种类型来说如果a里面有事务,b方法就使用a的事务,如果a没有事务,b方法就新建事务,并把a内容包裹进来,意思是a和b这两个操作是在同一个事务之间的。类型2/3表示a没有事务,b方法就不使用事务/抛出异常。
后三种类型作为一类,表示a和b这两个操作没有在同一个事务中。
最后一种类型是嵌套事务,当a执行完成以后可以设置一个保存点,如果b发生异常之后可以回滚到保存点位置,或者最初始状态。
TransactionDefinition接口方法
Timeout:
事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制事务还没有完成,则自动回滚事务。
ReadOnly:
默认为false,也就是读写事务,如果设置只读事务则不允许对数据进行增删改,只读事务用于特定情景下的优化。
TransactionStatus接口
Spring事务管理
Spring支持两种方式事务管理方式,编程式事务管理和声明式事务管理。
编程式事务管理通过TransactionTemplate手动管理事务,在实际应用中很少使用。声明式事务管理是通过AOP实现的,开发中推荐使用(代码侵入性小)。
编程式事务管理
这种方式不推荐实际使用,仅提供实现思路供参考。
1.配置事务管理器TransactionManager
,并在事务管理器中配置连接池dataSource
属性。
2.配置事务管理的模版TransactionTemplate
,并在事务管理模版中配置事务管理器属性。
3.在业务类中注入模版类,并调用其方法进行事务控制。
声明式事务管理
声明式事务管理共有3种实现方式,分别是基于TransactionProxyFactoryBean
的方式、基于AspectJ
的XML方式以及基于注解的方式。
由于第一种方式配置较为繁琐(需要为每个进行事务管理的类配置一个这样的Bean进行增强),通常实际开发不采用,这里我们只介绍后两种方式,也是实际开发常用的两种方式。
基于AspectJ的XML方式
基于这种方式我们只需要进行相关的配置即可,业务代码不需要做任何处理,注意在配置文件的头部需要添加aop和tx的命名空间。
<!-- 配置连接池,这里以druid为例,连接池相关参数省略 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://xxx:3306/xxx"></property>
<property name="username" value="root"></property>
<property name="password" value="root123"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知:(事务的增强) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- propagation:事务传播行为,isolation:事务隔离级别,read-only:只读 -->
<!-- rollback-for:发生哪些异常回滚,no-rollback-for:发生哪些异常不回滚,timeout:超时时间 -->
<tx:method name="delet*" propagation="REQUIRED" read-only="false" />
<tx:method name="save*" propagation="REQUIRED" read-only="false" />
<tx:method name="updat*" propagation="REQUIRED" read-only="false" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- AOP配置 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.rxy.*.service.*Service.*(..))"" id="pointcut1" />
<!-- 配置切面 -->
<aop:advisor pointcut-ref="pointcut1" advice-ref="txAdvice" />
</aop:config>
基于注解的方式
- 添加配置
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
- 使用注解@Transactional
@Transactional
可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
我们更推荐将注解使用在类以及类方法上,而不是接口。并且@Transactional
注解应该只被应用到 public 方法上。
//如果注解不定义任何属性,会使用其默认值,属性的含义参考xml方式的配置说明
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public class AccountServiceImpl {
//方法注解会覆盖类注解上的相同属性
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_UNCOMMITTED)
public void transfer(final String out, final String in, final Double money){
//业务逻辑,调用dao方法
}
}