原文出处: 张开涛
9.1 数据库事务概述
事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务。
事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:
- 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
- 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。
- 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
- 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。
在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:
- 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
- 脏读:一个事务看到了另一个事务未提交的更新数据;
- 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
- 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。
为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:
- 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
- 提交读(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
- 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
- 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。
9.1.1 事务类型
数据库事务类型有本地事务和分布式事务:
- 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上;
- 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
Java事务类型有JDBC事务和JTA事务:
- JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务;
- JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务。
Java EE事务类型有本地事务和全局事务:
- 本地事务:使用JDBC编程实现事务;
- 全局事务:由应用程序服务器提供,使用JTA事务;
按是否通过编程实现事务有声明式事务和编程式事务;
- 声明式事务: 通过注解或XML配置文件指定事务信息;
- 编程式事务:通过编写代码实现事务。
9.1.2 Spring提供的事务管理
Spring框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们:
- 提供一致的编程式事务管理API,不管使用Spring JDBC框架还是集成第三方框架使用该API进行事务编程;
- 无侵入式的声明式事务支持。
Spring支持声明式事务和编程式事务事务类型。
spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
9.2.1 概述
Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager接口定义如下:
1
2
3
4
5
|
public
interface
PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws
TransactionException;
void
commit(TransactionStatus status)
throws
TransactionException;
void
rollback(TransactionStatus status)
throws
TransactionException;
}
|
- getTransaction():返回一个已经激活的事务或创建一个新的事务(根据给定的TransactionDefinition类型参数定义的事务属性),返回的是TransactionStatus对象代表了当前事务的状态,其中该方法抛出TransactionException(未检查异常)表示事务由于某种原因失败。
- commit():用于提交TransactionStatus参数代表的事务,具体语义请参考Spring Javadoc;
- rollback():用于回滚TransactionStatus参数代表的事务,具体语义请参考Spring Javadoc。
TransactionDefinition接口定义如下:
1
2
3
4
5
6
7
|
public
interface
TransactionDefinition {
int
getPropagationBehavior();
int
getIsolationLevel();
int
getTimeout();
boolean
isReadOnly();
String getName();
}
|
- getPropagationBehavior():返回定义的事务传播行为;
- getIsolationLevel():返回定义的事务隔离级别;
- getTimeout():返回定义的事务超时时间;
- isReadOnly():返回定义的事务是否是只读的;
- getName():返回定义的事务名字。
TransactionStatus接口定义如下:
1
2
3
4
5
6
7
8
|
public
interface
TransactionStatus
extends
SavepointManager {
boolean
isNewTransaction();
boolean
hasSavepoint();
void
setRollbackOnly();
boolean
isRollbackOnly();
void
flush();
boolean
isCompleted();
}
|
- isNewTransaction():返回当前事务状态是否是新事务;
- hasSavepoint():返回当前事务是否有保存点;
- setRollbackOnly():设置当前事务应该回滚;
- isRollbackOnly(():返回当前事务是否应该回滚;
- flush():用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响;
- isCompleted():当前事务否已经完成。
9.2.2 内置事务管理器实现
Spring提供了许多内置事务管理器实现:
- DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理;
- JdoTransactionManager:位于org.springframework.orm.jdo包中,提供对单个javax.jdo.PersistenceManagerFactory事务管理,用于集成JDO框架时的事务管理;
- JpaTransactionManager:位于org.springframework.orm.jpa包中,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理;
- HibernateTransactionManager:位于org.springframework.orm.hibernate3包中,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
- JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;
- OC4JjtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对OC4J10.1.3+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
- WebSphereUowTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebSphere 6.0+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
- WebLogicJtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebLogic 8.1+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
Spring不仅提供这些事务管理器,还提供对如JMS事务管理的管理器等,Spring提供一致的事务抽象如图9-1所示。
图9-1 Spring事务管理器
接下来让我们学习一下如何在Spring配置文件中定义事务管理器:
一、声明对本地事务的支持:
a)JDBC及iBATIS、MyBatis框架事务管理器
1
2
3
|
<bean id=
"txManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property name=
"dataSource"
ref=
"dataSource"
/>
</bean>
|
通过dataSource属性指定需要事务管理的单个javax.sql.DataSource对象。
b)Jdo事务管理器
1
2
3
|
<bean id=
"txManager"
class
=
"org.springframework.orm.jdo.JdoTransactionManager"
>
<property name=
"persistenceManagerFactory"
ref=
"persistenceManagerFactory"
/>
</bean>
|
通过persistenceManagerFactory属性指定需要事务管理的javax.jdo.PersistenceManagerFactory对象。
c)Jpa事务管理器
1
2
3
|
<bean id=
"txManager"
class
=
"org.springframework.orm.jpa.JpaTransactionManager"
>
<property name=
"entityManagerFactory"
ref=
"entityManagerFactory"
/>
</bean>
|
通过entityManagerFactory属性指定需要事务管理的javax.persistence.EntityManagerFactory对象。
还需要为entityManagerFactory对象指定jpaDialect属性,该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。
1
2
3
4
5
|
<bean id=
"entityManagerFactory"
class
=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
>
……
<property name=
"jpaDialect"
ref=
"jpaDialect"
/>
</bean>
<bean id=
"jpaDialect"
class
=
"org.springframework.orm.jpa.vendor.HibernateJpaDialect"
/>
|
d)Hibernate事务管理器
1
2
3
|
<bean id=
"txManager"
class
=
"org.springframework.orm.hibernate3.HibernateTransactionManager"
>
<property name=
"sessionFactory"
ref=
"sessionFactory"
/>
</bean>
|
通过entityManagerFactory属性指定需要事务管理的org.hibernate.SessionFactory对象。
9.4 声明式事务
9.4.1 声明式事务概述
从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。
本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。
接下来先来看看声明式事务如何实现吧。
9.4.2 声明式实现事务管理
1、定义业务逻辑实现,此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl:
2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):
2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx=
"http://www.springframework.org/schema/tx"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http:
//www.springframework.org/schema/beans
http:
//www.springframework.org/schema/beans/spring-beans-3.0.xsd
http:
//www.springframework.org/schema/tx
http:
//www.springframework.org/schema/tx/spring-tx-3.0.xsd
http:
//www.springframework.org/schema/aop
http:
//www.springframework.org/schema/aop/spring-aop-3.0.xsd">
|
2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
1
2
3
4
5
6
7
|
<bean id=
"userService"
class
=
"cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"
>
<property name=
"userDao"
ref=
"userDao"
/>
<property name=
"addressService"
ref=
"addressService"
/>
</bean>
<bean id=
"addressService"
class
=
"cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"
>
<property name=
"addressDao"
ref=
"addressDao"
/>
</bean>
|
2.3、事务相关配置:
1
2
3
4
5
6
|
<tx:advice id=
"txAdvice"
transaction-manager=
"txManager"
>
<tx:attributes>
<tx:method name=
"save*"
propagation=
"REQUIRED"
isolation=
"READ_COMMITTED"
/>
<tx:method name=
"*"
propagation=
"REQUIRED"
isolation=
"READ_COMMITTED"
read-only=
"true"
/>
</tx:attributes>
</tx:advice>
|
1
2
3
4
|
<aop:config>
<aop:pointcut id=
"serviceMethod"
expression=
"execution(* cn..chapter9.service..*.*(..))"
/>
<aop:advisor pointcut-ref=
"serviceMethod"
advice-ref=
"txAdvice"
/>
</aop:config>
|
- <tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过< tx:attributes >指定具体需要拦截的方法;
- <tx:method name=”save*”>:表示将拦截以save开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读;
- <tx:method name=”*”>:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读;
- <aop:config>:AOP相关配置:
- <aop:pointcut/>:切入点定义,定义名为”serviceMethod”的aspectj切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截cn包及子包下的chapter9. service包及子包下的任何类的任何方法;
- <aop:advisor>:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。
从配置中可以看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
9.4 声明式事务
9.4.1 声明式事务概述
从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。
本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。
接下来先来看看声明式事务如何实现吧。
9.4.2 声明式实现事务管理
1、定义业务逻辑实现,此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl:
2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):
2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx=
"http://www.springframework.org/schema/tx"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http:
//www.springframework.org/schema/beans
http:
//www.springframework.org/schema/beans/spring-beans-3.0.xsd
http:
//www.springframework.org/schema/tx
http:
//www.springframework.org/schema/tx/spring-tx-3.0.xsd
http:
//www.springframework.org/schema/aop
http:
//www.springframework.org/schema/aop/spring-aop-3.0.xsd">
|
2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
1
2
3
4
5
6
7
|
<bean id=
"userService"
class
=
"cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"
>
<property name=
"userDao"
ref=
"userDao"
/>
<property name=
"addressService"
ref=
"addressService"
/>
</bean>
<bean id=
"addressService"
class
=
"cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"
>
<property name=
"addressDao"
ref=
"addressDao"
/>
</bean>
|
2.3、事务相关配置:
1
2
3
4
5
6
|
<tx:advice id=
"txAdvice"
transaction-manager=
"txManager"
>
<tx:attributes>
<tx:method name=
"save*"
propagation=
"REQUIRED"
isolation=
"READ_COMMITTED"
/>
<tx:method name=
"*"
propagation=
"REQUIRED"
isolation=
"READ_COMMITTED"
read-only=
"true"
/>
</tx:attributes>
</tx:advice>
|
1
2
3
4
|
<aop:config>
<aop:pointcut id=
"serviceMethod"
expression=
"execution(* cn..chapter9.service..*.*(..))"
/>
<aop:advisor pointcut-ref=
"serviceMethod"
advice-ref=
"txAdvice"
/>
</aop:config>
|
- <tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过< tx:attributes >指定具体需要拦截的方法;
- <tx:method name=”save*”>:表示将拦截以save开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读;
- <tx:method name=”*”>:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读;
- <aop:config>:AOP相关配置:
- <aop:pointcut/>:切入点定义,定义名为”serviceMethod”的aspectj切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截cn包及子包下的chapter9. service包及子包下的任何类的任何方法;
- <aop:advisor>:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。
从配置中可以看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
3、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction:
并在testDeclareTransaction测试方法内将:
1
|
classpath:chapter9/service/applicationContext-service.xml"
|
替换为:
1
|
classpath:chapter9/service/applicationContext-service-declare.xml"
|
4、执行测试,测试正常通过,说明该方式能正常工作,当调用save方法时将匹配到事务通知中定义的“<tx:method name=”save*”>”中指定的事务属性,而调用countAll方法时将匹配到事务通知中定义的“<tx:method name=”*”>”中指定的事务属性。
声明式事务是如何实现事务管理的呢?还记不记得TransactionProxyFactoryBean实现配置式事务管理,配置式事务管理是通过代理方式实现,而声明式事务管理同样是通过AOP代理方式实现。
声明式事务通过AOP代理方式实现事务管理,利用环绕通知TransactionInterceptor实现事务的开启及关闭,而TransactionProxyFactoryBean内部也是通过该环绕通知实现的,因此可以认为是<tx:tags/>帮你定义了TransactionProxyFactoryBean,从而简化事务管理。
了解了实现方式后,接下来详细学习一下配置吧:
9.4.4 <tx:advice/>配置详解
声明式事务管理通过配置<tx:advice/>来定义事务属性,配置方式如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
|
<tx:advice id=
"……"
transaction-manager=
"……"
>
|