Spring事务管理
在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
事务的四个特性(ACID)
- 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
- 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
- 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
- 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
我们进行事务管理时可以使用两种方法: 编程式事务管理和声明式事务管理
编程式事务管理
编程式事务管理就是我们之前用的使用原生的JDBC API进行事务管理
一般分为这几个步骤
- 获取数据库连接Connection对象
- 取消事务的自动提交
- 执行操作
- 正常完成操作时手动提交事务
- 执行失败时回滚事务
- 关闭相关资源
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余
声明式事务管理
大多数时候我们使用声明式事务管理,因为它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制
Spring提供的事务管理器
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
事务管理器的主要实现
- DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
- JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
- HibernateTransactionManager:用Hibernate框架存取数据库
不使用其他框架,我们先使用DataSourceTransactionManage
使用配置文件
首先要导入tx和aop的命名空间
导入的是:xmlns:tx="http://www.springframework.org/schema/tx"
的命名空间,不要导入错了
底下的网址是:http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
然后创建事务管理类,创建之前需要先创建数据库连接池,之前已经创建过,这里就不重复
将连接池赋值给参数
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
然后创建通知
<!--使用transaction-manager属性与上面定义的事务管理类关联-->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<tx:attributes>
<!--指定方法对应的事务策略-->
<tx:method name="addQuestion"/>
</tx:attributes>
</tx:advice>
表示如果执行addQuestion方法就对它进行事务管理,name里面是要进行监控的方法名
如果在tx:method
标签内没有其他参数就表示默认配置,还有以下配置可以选择
- isolation:事务隔离级别
- propagation:事务的传播机制
- read-only:只读事务
- timeout:事务超时时间
- no-rollback-for:不回滚的异常类型
- rollback-for:指定回滚的异常类型
默认的策略就可以满足大多数需求
通过aop切入事务通知,通过id与上面的通知结合
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.dao.impl.*.select*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pc"/>
</aop:config>
使用注解的方式
跟配置文件的方法一样,要创建事务管理类
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
然后再开启事务的注解支持,通过id与创建的事务管理类进行关联
<tx:annotation-driven transaction-manager="transactionManager"/>
然后就可以在要使用事务的地方加上注解,可以是类和方法@Transactional
事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为
事务传播属性可以在@Transactional注解的propagation属性中定义
事务的隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度
我们并发处理事务会引起很多的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致以下的问题
- 脏读(Dirty reads)
脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。 - 不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。 - 幻读(Phantom read)
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
我们的事务有四种隔离级别
隔离级别 | 含义 |
DEFAULT | 使用后端数据库默认的隔离级别 |
READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
各个隔离级别解决并发问题的能力见下表
隔离级别 | 脏读 | 不可重复读 | 幻读 |
READ UNCOMMITTED | ✓ | ✓ | ✓ |
READ COMMITTED | × | ✓ | ✓ |
REPEATABLE READ | × | × | ✓ |
SERIALIZABLE | × | × | × |
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别
在xml中可以在<tx:method>
元素中指定隔离级别