简介
数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成;在企业级开发应用中,事务管理是必不可少的技术,它被用来保证数据的完整性和一致性
事务的四大特性(ACID)
-
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
-
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败,在现实中的数据不应该被破坏
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来,通常情况下,事务的结果被写到持久化存储器中
核心接口
Spring 事务管理涉及的接口的联系如下
事务管理器
Spring 并不直接管理事务,而是提供了多种事务管理器,将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务实现
Spring 事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring 为各个平台如 JDBC、Hibernate 等提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了;此接口内容如下
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
Spring 支持 4 大事务:JDBC transaction,Hibernate transaction,JPA transaction,JTA transaction
- JDBC transaction
如果应用程序中直接使用 JDBC 来进行持久化,DataSourceTransactionManager 会为你处理事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager 是通过调用 java.sql.Connection 来管理事务,而后者是通过 DataSource 获取到的; 通过调用连接的 commit() 方法来提交事务,同样,事务失败则通过调用 rollback() 方法进行回滚
- Hibernate transaction
如果应用程序的持久化是通过 Hibernate 实现的,那么你需要使用 HibernateTransactionManager
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- JPA transaction(Java 持久化 API 事务)
Hibernate 一直是事实上的 Java 持久化标准,但是现在 Java 持久化 API 作为真正的 Java 持久化标准,如果使用 JPA 的话,那需要使用 Spring 的 JpaTransactionManager 处理事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager 只需装配一个 JPA 实体管理工厂(javax.persistence.EntityManagerFactory 接口的任意实现) JpaTransactionManager 将与由工厂所产生的 JPA EntityManager 合作构建事务
- JTA transaction(Java 原生 API 事务)
如果没有使用以上所述的事务管理,或是跨越了多个事务管理源(比如两个或者是多个不同的数据源),需要使用 JtaTransactionManager
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager 将事务管理的责任委托给 javax.transaction.UserTransaction和javax.transaction.TransactionManager 对象,其中事务成功完成通过 UserTransaction.commit() 方法提交,事务失败通过 UserTransaction.rollback() 方法回滚
基本事务属性定义
事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法得到事务,这个方法里面的参数是 TransactionDefinition 类,这个类定义了一些基本的事务属性;事务属性可以理解为事务的一些基本配置,描述了事务策略如何应用到方法上,事务属性包含了 5 个方面,如图所示
TransactionDefinition 接口内容如下
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别, 事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读, 事务管理器能够根据这个返回值进行优化, 确保事务是只读的
}
事务的传播行为
事务的第一个方面是传播行为(propagation behavior)当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行,Spring 定义了七种传播行为
事务隔离级别
隔离级别(isolation level) 定义了一个事务可能受其他并发事务影响的程度
并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务;并发虽然是必须的,但可能会导致以下问题
-
脏读(Dirty reads),发生在一个事务读取了另一个事务改写但尚未提交的数据时;如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的
-
不可重复读(Nonrepeatable read),发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新;不可重复读的重点是修改: 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
-
幻读(Phantom read),幻读与不可重复读类似,发生在一个事务(T1) 读取了几行数据,接着另一个并发事务(T2) 插入了一些数据时;在随后的查询中,第一个事务(T1)就会发现多了原本不存在的记录;幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样
从总的结果来看, 似乎 不可重复读 和 幻读 都表现为两次读取的结果不一致;但如果从控制的角度来看, 两者的区别就比较大:
对于前者, 只需要锁住满足条件的记录
对于后者, 要锁住满足条件及其相近的记录
只读
事务的第三个特性是它是否为只读事务,如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化,通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间;因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源;事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会;默认情况下,事务只有遇到运行期异常(RuntimeException)时才会回滚,而在遇到检查型异常(CheckedException) 时不会回滚(这一行为与EJB的回滚行为是一致的) 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚;同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常
事务状态
PlatformTransactionManager 接口的 getTransaction() 的方法得到的是 TransactionStatus 接口的一个实现,这个接口的内容如下
public interface TransactionStatus {
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态
编程式事务
Spring 提供两种方式的编程式事务管理,分别是:使用 TransactionTemplate 和直接使用 PlatformTransactionManager
使用 TransactionTemplate
采用 TransactionTemplate 和采用其他 Spring 模板,如 JdbcTempalte 和 HibernateTemplate 是一样的方法;它使用回调方法,把应用程序从处理取得和释放资源中解脱出来;如同其他模板,TransactionTemplate 是线程安全的;如下代码片段
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
Object result = tt.execute(
new TransactionCallback() {
public Object doTransaction(TransactionStatus status) {
updateOperation();
return resultOfUpdateOperation();
}
}); // 执行execute方法进行事务管理
使用 TransactionCallback() 可以返回一个值;使用 TransactionCallbackWithoutResult 则没有返回值
使用 PlatformTransactionManager
示例代码如下
//定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
// 数据库操作
dataSourceTransactionManager.commit(status); // 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status); // 回滚
}
声明式事务
根据代理机制的不同,总结了五种 Spring 事务的配置方式,配置文件如下
- 每个 Bean 都有一个代理
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userDaoTarget" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
<prop key="select*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
- 所有Bean共享一个代理基类
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
- 使用拦截器
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<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>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- 使用 tx 标签配置的拦截器
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<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.bluesky.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" />
</aop:config>
- 使用注解
<!-- 打开注解模式 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
然后在 Service 层上需加上 @Transactional 注解
注意
(1) Spring 使用事务的话,会默认关闭自动提交功能,提交和回滚将被托管给事务
(2) Spring 事务中的回滚定义的异常一定是运行时异常,虽然可以定义遇到异常不回滚
(3) Spring 事务最好定义在 Service 层(实现类)上,也就是业务逻辑层,不要定义在接口上