Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交。
在没有Spring帮我们管理事务之前,service层的代码:
Connection conn = DriverManager.getConnection();
try {
conn.setAutoCommit(false); //将自动提交设置为false
执行CRUD操作
conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,所有操作都不成功
e.printStackTrace();
} finally {
conn.colse();
}
事务是一系列的动作,一旦其中有一个动作出现错误,必须全部回滚,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态,避免出现由于数据不一致而导致的接下来一系列的错误。事务的出现是为了确保数据的完整性和一致性,在目前企业级应用开发中,事务管理是必不可少的。
Spring配置事务管理器
例如我在spring-mybatis中配置的:
<!-- 配置事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" /></bean>
配置了事务管理器后,Spring提供了两种事务管理的方式:编程式事务管理和声明式事务管理。
编程式事务管理
编程式事务管理我们可以通过PlatformTransactionManager实现来进行事务管理。Spring提供了模板类TransactionTemplate进行事务管理,我们需要在配置文件中配置
<!--配置事务管理的模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
<!--定义事务隔离级别,-1表示使用数据库默认级别-->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
</bean>
测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
@Resource
private TransactionTemplate transactionTemplate;
@Autowired
private BaseSevice bs;
@Test
public void transTest() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try{
//需要进行事务处理的方法
} catch (Exception e){
status.setRollbackOnly();
e.printStackTrace();
}
}
});
}
}
声明式事务管理
声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解,随着Spring和Java的版本越来越高,大家越趋向于使用注解的方式。
1.基于tx和aop命名空间的xml配置文件
配置文件:
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointCut" expression="execution (* com.gray.service.*.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
</aop:config>
2.基于@Transactional注解
这种方式最简单,也是最为常用的,只需要在配置文件中开启对注解事务管理的支持。
<!-- 声明式事务管理 配置事物的注解方式注入-->
<tx:annotation-driven transaction-manager="transactionManager"/>
然后在需要事务管理的地方加上@Transactional注解。
使用@Transactional注解管理事务分两步。第一步开启事务管理,可以在xml中配置,也可以通过@EnableTransactionManagement注解开启;第二步将@Transactional注解添加到方法上,设置属性。
name 当在配置文件有多个TransactionManager,可以指定该属性指定选择哪个事务管理器
propagation 事务的传播行为,默认为REQUIRED
isolation 事务的隔离级别,默认DEFAULT
timeout 事务的超时时间,默认-1.如果超过该时间限制事务还没完成,则自动回滚事务
read-only 指定事务是否为只读事务,默认为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间通过逗号分隔
no-rollback-for 抛出no-rollback-for指定的异常类型,不回滚事务
除此之外@Transactional也可以添加到类级别上,表示所有该类的公共方法都配置相同的事务属性信息。方法级别的配置会覆盖类级别的相关配置信息
Spring事务传播属性(Propagation):
- REQUIRED(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
- MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
- NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
- NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
- SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
- NESTED
新建事务,支持当前事,与当前事务同步提交或回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。 使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
事务隔离级别(Isolation Level):
事务并发引起的三种情况:
- Dirty Reads 脏读
一个事务正在对数据进行更新操作,但是更新还未提交,另一个事务这时也来操作这组数据,并且读取了前一个事务还未提交的数据,而前一个事务如果操作失败进行了回滚,后一个事务读取的就是错误数据,这样就造成了脏读。
- Non-Repeatable Reads 不可重复读
一个事务多次读取同一数据,在该事务还未结束时,另一个事务也对该数据进行了操作,而且在第一个事务两次次读取之间,第二个事务对数据进行了更新,那么第一个事务前后两次读取到的数据是不同的,这样就造成了不可重复读。
- Phantom Reads 幻像读
第一个数据正在查询符合某一条件的数据,这时,另一个事务又插入了一条符合条件的数据,第一个事务在第二次查询符合同一条件的数据时,发现多了一条前一次查询时没有的数据,仿佛幻觉一样,这就是幻像读。
非重复度和幻像读的区别:
非重复读是指同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。
表面上看,区别就在于非重复读能看见其他事务提交的修改和删除,而幻像能看见其他事务提交的插入。
2.隔离级别:
- DEFAULT (默认)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
- READ_UNCOMMITTED (读未提交)
这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- READ_COMMITTED (读已提交)
保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
- REPEATABLE_READ (可重复读)
这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
- SERIALIZABLE(串行化)
这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。