1. 事务概述
事务(Transaction)是访问并可能更新数据库中数据的程序执行单元,通常事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。例如我们平时的转账操作,从开始执行转账,到收款用户确认收款,最终金额增加,这就是一个事务。
为什么我们要加入事务管理?借助以上的转账实例来进行说明,假设A向B账户转账100元,那么最基本的两个操作就是A账户的金额减少100元,然后B账户的金额增加100元,存在一定的先后顺序。倘若在A完成转账之后,A的金额减少了100元,但是信息传输途中发生了某些异常,导致B没有接受到转账信息,那么B的金额没有增加。最终的结果是A的金额减少了100元,而B的维持不变。这样的场景不符合我们日常生活的实际,因此我们引入事务管理,一旦在事务发生中断,事务立即回滚到开始之前的状态,也就是A减少的那100元金额会退还到A的账户。
事务作为一个不可分割的逻辑工作单元,一般具备ACID四大特性:
1) 原子性(一个事务中的多个操作要么都成功要么都失败)
2) 一致性(一个事务必须让数据库从一个一致性状态到另一个一致性状态,简单地举例就是转账前后两个人的总金额应该不变)
3) 隔离性(事务与事务之间应该互不干扰)
4) 持久性(事务一旦提交,它对数据库的改变是永久性的)
2. Spring事务管理
Spring为我们提供了两种事务管理方式。一种是通过编码实现的编程式事务,另一种是基于Spring AOP的声明式事务。声明式事务管理通过切面实现将具体业务逻辑与事务处理解耦,使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。
Spring中声明式事务处理有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。
2.1注解模式的事务管理
首先,我们在spring容器中配置一个事物管理的bean对象,DataSourceTransactionManager对象,这里也有两种方式,一种是在配置类中配置(全注解模式),另一种是在spring配置文件中配置(半注解模式)。
配置类中配置事物管理类,首先要开启spring注解驱动的事务管理,可以通过在类名上方添加@ EnableTransactionManagement注解的方式实现,然后在这个类中配置事务管理对象,这里的DataSource要与数据库连接的数据源保持一致:
@Bean("transactionManager")
public DataSourceTransactionManager getDataSourceTransactionManager(
DataSource ds){
DataSourceTransactionManager transactionManager=
new DataSourceTransactionManager();
transactionManager.setDataSource(ds);
return transactionManager;
}
Xml配置文件中配置管理类并开启事物管理模式:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--设置注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
配置好事务管理注解以后,就可以在类或者方法上使用@Transactional进行事务管理。当把@Transactional注解加在类上时,表示类中的所有public修饰的方法全都配置相同的事物管理信息。当@Transactional修饰方法时,表示方法对方法进行事物管理。这里需要注意两点:一是@Transactional只能用来修饰public权限的方法,其余的不起作用;二是如果类和类里的方法都是用了@Transactional注解,方法上的事无属性优先级别较高,会覆盖类上的相关设置。
@Transactional注解后表示启动spring默认的事务管理模式,也可以添加一些属性来对方法采取不同的事务管理模式,下面总结了一些常用的属性:
Value:当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
Propagation:事务的传播行为,默认值为 REQUIRED。
Isolation:事务的隔离度,默认值采用 DEFAULT。
Timeout:事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only 为 true。
rollback-for:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for:抛出 no-rollback-for 指定的异常类型,不回滚事务。
2.2spring配置文件中的事务管理
配置文件中配置事物管理可以分为三步。
1)在spring配置文件中配置事务管理器:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2)定义事物策略,这里是对所有的public方法进行相关事务管理设置:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
也可以针对某一方法进行特殊设置:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"
propagation="REQUIRED"
isolation="READ_COMMITTED"
timeout="-1"
read-only="false"
rollback-for="java.lang.Throwable"
no-rollback-for=”NoTransactionException”/>
</tx:attributes>
</tx:advice>
3)配置事物切面,这里设置的一种细粒度的事物管理,对service中的所有方法进行管理,还有一种粗粒度的事务管理,expression=”within("com.jt.manage.service..*")”管理的是service包中的所有类。
<aop:config>
<aop:pointcut expression="execution(* com.jt.manage.service..*.*(..))" id=pointCut"/>
<aop:advisor advice-ref="txAdvice" ref="pointCut"/>
</aop:config>
3.spring事物拓展
3.1spring事物传播特性
事务传播特性(propagation):事务方法之间相互调用时,事务的传播方式。
@Transactional(propagation=Propagation.REQUIRED) 如果没有事务创建新事务, 如果当前有事务参与当前事务,这是事物管理的默认方式,也是需要重点掌握的。
@Transactional(propagation=Propagation.SUPPORTS)支持事务, 如果没有事务也不会创建新事务,也是比较常用的,一般用在查询方法中。
@Transactional(propagation=Propagation.REQUIRES_NEW)必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务。
@Transactional(propagation=Propagation.MANDATORY)必须有事务, 如果当前没有事务就抛异常。
@Transactional(propagation=Propagation.NEVER)绝对不能有事务, 如果在事务中调用则抛出异常。
@Transactional(propagation=Propagation.NESTED)必须被嵌套到其他事务中。
@Transactional(propagation=Propagation.NOT_SUPPORTED)不支持事务。
3.2事物隔离级别
总所周知,并发访问时程序需要解决的一个重要问题,当多个事物并发执行时,可能会引发脏读、不可重复读和幻读等问题。脏读是一个事务中读取到另一个事务没有提交的数据。不可重复读是在一个事务中两次查询的结果不一样(针对update操作)。幻读是在一个事务中两次查询的结果不一样(针对insert操作)。
当多个事务并发执行时,可通过设置事务的隔离级别保证事务的完整性,一致性。
事务的隔离级别从低到高有如下几种方式:
1)READ_UNCOMMITTED (此级别可能会出现脏读)
2)READ_COMMITTED(此级别可能会出现不可重复读)
3)REPEATABLE_READ(此级别可能会出现幻读)
4)SERIALIZABLE(多事务串行执行)
spring中一般采用@Transactional(isolation=Isolation.READ_COMMITTED) 方式声明级别, 这种方式是并发性能和安全性折中的选择,是大多数软件项目采用的隔离级别。