事务是一个完整的逻辑单元,在执行的时候要嘛同时成功,要嘛同时失败,中间不允许被中断,也就是说事务是一个全有或全无的过程。
事务拥有4个特性:
原子性:事务是由一个或者多个活动单元组成的。原子性确保了事务中所有操作全部发生或者全部不发生。如果所有活动都成功了,事务也就成功了。如果任意一个活动失败了,整个事务也就失败并回滚。
一致性:一旦事务完成(不管成功或者失败),事务保证业务处于一致的状态,数据不允许被破坏(所有活动要嘛成功一致,要嘛失败一致)。
隔离性:事务允许多个用户对相同数据进行操作,每个用户的操作不会与其他用户纠缠在一起。因此,事务会被隔离,避免发生同步读写数据的事情(隔离往往会涉及到锁定数据库的行或者表)
永久性:一旦事务完成,事务的结果应该被持久化,这样就能从任意的系统崩溃中恢复过来。
Spring对编程式和声明式事务进行支持,编程式事务允许用户进行精确的事务控制,声明式(方法级别)以用户透明的形式对目标对象应用事务管理,无论采用何种方式,spring自己没有事务控制,而是提供了自己的事务管理器,将事务的职责委托给了其他的持久化平台进行实现,例如hibernate,JPA,JTA等等,对于在spring中使用事务,基本都可以不用关心持久化平台(框架)对事务的实现。
所有的事务管理器都实现了PlatformTransactionManager。
JDBC事务:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
hibernate事务:
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean>
注意:如果使用hibernate2.0,必须使用spring2.0用于集成,不然会出现错误
JPA事务:
<bean id="transactionManager3" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="emf"></property> </bean>
让JPA事务支持高级特性:
<bean id="transactionManager3" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="emf"></property> <property name="jpaDialect" ref="jpaDialect"></property> </bean> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
这里配置了JpaDialect,JpaDialect支持使用特定的事务语义(例如用户自定义的事务隔离级别和事务超时),获取具备事务功能的Connection对象(暴露给基于JDBC的DAO),从 PersistenceExceptions
到Spring的 DataAccessExceptions
高级转化。
Spring编程式事务:
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean>
@Inject
private TransactionTemplate txTemplate;
public void test(){
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
//do your business code
return null;
}
});
}
调用execute方法,构造匿名内部类,在doInTransaction方法中编写的代码就位于事务当中
Spring声明式事务:
spring对声明式事务的支持是通过spring AOP实现的,在过去的日子里,spring采用TransactionProxyFacotyBean来代理需要进行事务的对象,进行声明式事务的管理,目前这种方式已经被淘汰,取而代之的是spring2.0之后的命名空间配置和@Transactional注解。
声明式事务通过事务属性来进行定义,通过配置事务属性来进行事务管理策略
以下是事务属性:
传播行为:定义了客户端与被调用方法之间的事务边界
传播行为 | 含义 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行,嵌套事务可以独立于当前事务进行单独的提交或者回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样,各厂商对其支持不同,参考文档是否支持嵌套事务 |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中,如果当前有一个事务正在运行,则会抛出异常 |
PROPAGATION_NOT_SUPPORTED | 表示当前方法不应该运行在事务上下文当中,如果存在当前事务,该方法在运行期间,当前事务将会被挂起,如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务当中,如果当前事务存在,方法会在当前事务中运行,否则,会启动一个新的事务 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须运行在它自己的事务当中,一个新的事务将会被启动,如果当前存在事务,在该方法运行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是存在当前事务,那么该方法会运行在当前事务当中 |
隔离级别:定义事务与其他事务之间相互的影响程度
事务隔离级引发的现象:
脏读:一个事务改写了数据,但是没有提交,另外一个事务可以读到之前事务改写但是未提交的数据
不可重复读:一个事务针对某一个数据执行相同的两次查询,另外一个事务在此期间对该数据进行了修改和删除,那么之前的事务查询出来的两次结果会不同
幻读:一个事务针对某一个数据执行了相同的两次查询,另外一个事务在此期间对该数据进行了新增,那么之前的事务查询出来的第二次结果会出现新数据
理想状态下,两个事务之间是完全隔离的,但是完全隔离之后会造成性能问题(表和行锁),这叫造就了用户需要灵活的根据情况在程序中自定义隔离级别:
隔离级别 | 含义 |
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的数据变更,可能会影响脏读,不可重读以及幻读 |
ISOLATION_READ_COMMITED | 允许读取并发事务中已经提交的数据,可以组织脏读,但是幻读和不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果是一致的,除非是被本事务修改,可以组织脏读和不可重复读,但幻读仍有可能产生(因为幻读是新增操作) |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保阻止脏读,不可重复读,以及幻读,这是性能最慢的事务隔离级别,因为通常是完全锁定表来进行实现的 |
并不是所有的数据库都支持以上表格中的隔离级别,详细情况请参照数据库文档
只读:
只读是由后端的数据库进行的,数据库可以利用事务的只读操作进行特定的优化,只读优化只有在事务启动的时候由数据库实施的,所以配置PROPAGATION_NESTED,PROPAGATION_REQUIRES_NEW,PROPAGATION_REQUIRED才有意义。
事务超时:
为了使程序运行良好,事务不能占用太长时间,设置事务超时来管理事务。超时时钟会在事务启动的时候开始计时,所以只针对启动一个新事务的传播行为事务才有意义(PROPAGATION_NESTED,PROPAGATION_REQUIRES_NEW,PROPAGATION_REQUIRED)
回滚规则:
定义了事务如果运行失败的回滚策略,默认情况事务只有遇到运行期异常的时候才会回滚,遇到检查期异常不会回滚,但是通过设置回滚规则可以自定义回滚的策略
在XML定义事务:
增加命名空间配置:
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* org.robbie.test.spring.beans.MyDao.*(..))"/> </aop:config>配置事务通知txAdvice,这个通知会拦截以save开头的方法,并启动 REQUIRED 事务,其余的所有方法指定 SUPPORTS事务, 值得注意的是tx:method中定义的事务属性说明如下:
事务属性 | 说明 |
isolation | 指定事务的隔离级别 |
propagation | 指定事务的传播行为 |
read-only | 指定事务为只读 |
rollback-for no-rollback-for | rollback-for指定事务对于哪些检查异常应该回滚而不提交,no-rollbackfor-for指定事务对于哪些异常应该继续运行而不回滚 |
timeout | 指定超时时间 |
基于注解的事务配置:
<tx:annotation-driven/>
@Repository
@Transactional
public class MyService{
@Transactional
public void saveObject(){
}
}
在XML上增加<tx:annotation-driven/> 需要指定事务管理器,本例中默认不配会引用id为transactionManager的事务管理器,如果spring配置中没有id为transacManager的事务管理器,需要手动指定transaction-manager属性。 在需要事务的类或者方法上标注@Transactional。配置上注解的类或者方法就被spring事务管理器管理了
关于JTA的配置,JTA通常是通过第三方厂商实现的,需要配置分布式的数据源,已经JTA的事务管理器,配置完成之后能够跨数据源进行事务提交和回滚,具体详细配置可以参见其他技术文档。