事务,是一组严密的操作集合,这一组操作要么全部成功,要么全部失败回滚。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。这篇总结下Spring事务。
事务描述
事务特性
事务有ACID四种特性,A是Atomic(原子性)、C是Consistency(一致性)、I是Isolation(隔离性)和D是Durability(持久性)的缩写。
(1)原子性
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个状态。事务如果在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。比如银行转账,成功后必须一个账户增加,一个账户减少。
(3)隔离性
隔离性指的是在并发环境中,不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
(4)持久性
持久性指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束后系统崩溃前的状态。
事务传播
所谓事务传播,就是程序之间调用有可能有的开启了事务有的没有开启事务,调用方与被调用方之间的互相影响。Spring定义了7中传播行为:
(1)propagation_required:如果当前没有使用事务,就开启一个新事务;如果已存在一个事务中,加入到这个事务中,
这是Spring默认的选择。
(2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
(3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
(4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
(5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
(7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
我们开发中最常用的就是propagation_required传播方式,其他的在一些特殊场景会用到。
事务隔离级别
事务有四种隔离级别:
(1)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
(2)read commited:一个事务提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
(3)repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
(4)serializable:是隔离粒度最严格且最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻读。
注意:脏读、不可重复读以及幻读概念如下:
I)脏读:一个事务正在修改数据,但是尚未提交到数据库,此时另外一个事务也能访问和使用这个数据,由于数据没有提交到 数据库,也就是未持久化的数据,那么另外一个事务处理会得到错误的结果。
II)不可重复读:在一个事务中,需要多次读取同一数据,该事务尚未结束,另外一个事务也访问相同数据并且做了修改,会导致第一个事务前后读取的数据不一致,这种情况就是不可重复读。
III)幻读:一个事务读取一定范围内的数据,但是前后两次读取的记录数不同,这种现象称为幻读。幻读一般发生在,一个事务需要多次读取指定范围内的数据,但是中间有另外一个事务做了插入操作。
Spring事务实现方式
按照实现方式的不同,spring对事务的实现大致分为:编程式事务、声明式事务和注解式事务。而声明式事务根据控制的粒度不同,分为单个bean代理式事务、多个bean代理式事务和声明式事务,接下来我们针对每一种方式做一下编码实现和测试。
1.单个bean代理
新建spring-jdbc.xml并填充内容:
<bean id="testDataSource" parent="parentDataSource">
<property name="url"
value="jdbc:mysql://${jdbc.test.database}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull"/>
<property name="username" value="${jdbc.test.user}"/>
<property name="password" value="${jdbc.test.password}"/>
<property name="maxActive" value="50"/>
</bean>
<bean id="testJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="testDataSource"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="testDataSource"/>
</bean>
<bean id = "user2DaoTarget" class="com.typhoon.spring_jdbctemplate.dao.User2Dao" >
<property name="testJdbcTemplate" ref="testJdbcTemplate" />
</bean>
<bean id = "user2Dao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="txManager"/>
<property name="target" ref="user2DaoTarget" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
dao层实现:
public class User2Dao {
private Random random = new Random();
private JdbcTemplate testJdbcTemplate;
public void setTestJdbcTemplate(JdbcTemplate testJdbcTemplate) {
this.testJdbcTemplate = testJdbcTemplate;
}
public int save(User user) {
int rs = testJdbcTemplate.update("insert into User values(null,?,now()) ", user.getName());
if( !random.nextBoolean()) {
throw new RuntimeException("更新失败回滚");
}
return rs;
}
}
service层实现:
@Service
public class UserManager {
@Autowired
private User2Dao user2Dao;
public void save(User user) {
int rs = this.user2Dao.save(user);
if(rs < 1) {
throw new RuntimeException("更细失败");
}
}
}
测试代码实现:
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-root.xml");
context.start();
UserManager userManager = context.getBean(UserManager.class);
User u = new User();
u.setName("chuanchuan1");
userManager.save(u);
context.stop();
}
运行测试代码debug:
保存数据后程序主动抛了异常,理论上事务会回滚,去数据库看一下有没有插入成功:
数据库中并没插入数据,也就是说我们使用单个bean代理的方式实现了spring事务管理。
2.多个bean使用一个代理基类
修改前边的spring-jdbc.xml文件:
<bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="txManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id = "user2DaoTarget" class="com.typhoon.spring_jdbctemplate.dao.User2Dao" >
<property name="testJdbcTemplate" ref="testJdbcTemplate" />
</bean>
<bean id = "user2Dao" parent="transactionBase">
<property name="target" ref="user2DaoTarget" />
</bean>
dao层、service层和测试代码不用修改,继续运行debug:
查询一下数据库有没有插入成功:
同样,数据没有插入成功,在执行完数据插入之后,如果程序抛异常事务会回滚,也验证了多个bean使用同一个事务代理类实现了事务管理。
3.拦截器方式
修改spring-jdbc.xml文件:
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="txManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>user2Dao</value>
<!--<value>*Dao</value>-->
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
其他代码不用修改,继续运行测试代码debug:
数据依然没有插入成功,dao报异常后事务发生了回滚,也说明使用spring事务拦截器实现了事务管理。
4.tx标签拦截器(声明式事务)
修改数据库操作配置文件:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.typhoon.spring_jdbctemplate.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
声明式事务用到aop,所以要引入如下依赖,否则报错:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
继续运行测试代码debug:
数据没有保存成功,dao报异常后事务发生了回滚,也证明我们使用spring编程式事务实现了事务管理。
5.注解式事务
修改spring-jdbc.xml配置文件:
<tx:annotation-driven transaction-manager="txManager"/>
修改dao层在方法上增加注解:
一般事务注解式加在service层,因为dao一般是单个更新操作,而service层实组合操作,此处方便测试暂不做纠结。运行测试代码debug:
新增数据失败,dao层抛运行异常后事务发生了回滚,我们使用全注解的方式也实现了spring事务管理。
总结
上边我们介绍了spring五种事务管理的方式,基于易用性和代码最小改动考虑,日常开发中只有最后两种事务管理方式经常被使用,我个人建议使用全注解的方式管理事务,因为和其他方式相比只要一行开启事务注解的配置,然后再使用的时候可以更灵活的使用注解管理事务,并且我们可以根据业务需要容易地控制事务管理的粒度(注解可以写到类上也可以写到方法上)。另外需要注意的是事务默认只捕获运行时异常(非受检异常)然后回滚,对于程序中主动抛出受检异常,程序会终止运行,但是执行完的更新不会回滚。可以根据业务需求和使用习惯自定义配置捕获受检异常后回滚。
基于spring实现事务管理暂且介绍到这里,希望给各位看官带来帮助,在日常开发中能够更熟练的使用spring事务。