1、数据库事务基础知识
1.1、数据库事务的概念
(1)数据库事物的4个特性(ACID):
- 原子性(Atomic):表示组成一个事务的多个数据库操作是一个不可分割的原子单元,所有的操作要么全部成功,要么全部失败。
- 一致性(Consistency):事务操作成功之后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。
- 隔离性(Isolation):在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。准确地说,并非要求做到完全无干扰,数据库规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
- 持久性(Durablity):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中。
在这些特性中,数据“一致性”是最终目标,其他属性都是为达到这一目标的方式手段。
(2)
重执行日志
- 数据库管理系统一般采用重执行日志保证原子性、一致性和持久性,重执行日志记录了数据库变化的每一个动作。
- 在一个事务中执行一部分操作发生错误退出,数据库可以根据重执行日志撤销已经执行的操作。
- 对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作。
(3)
隔离性
和Java程序采用对象锁机制进行线程同步类似,数据库管理系统采用数据库锁机制保证事务的隔离性。Oracle数据库还采用了数据版本的机制,在回滚段为数据的每个变化都保存一个版本,使数据的更改不影响数据的读取。
1.2、数据并发的问题
不可重复读和幻读可理解为多次访问的不幂等
- 脏读(dirty read):A事务读取B事务尚未提交的更改数据。
- 不可重复读(unrepeatable read):A事务读取了B事务已经提交的更改数据,即A事务在2次读取的数据不一致。
- 幻读(plantom read):A事务读取B事务提交的新增数据,幻读一般发生在计算统计数据的事务中。
- 第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。
- 第二类丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失。
1.3、数据库锁机制
(1)按
锁定的对象不同,分为
表锁定和
行锁定。
(2)从
并发事务锁定的关系上看,可以分为
共享锁定和
独占锁定:
- 共享锁定会防止独占锁定,但允许其他的共享锁定;
- 独占锁定既防止其他的独占锁定,也防止其他的共享锁定。
为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和select for update语句都会隐式采用必要的行锁定。
(3)下面介绍一下Oracle数据库常用的5种锁定:
1.4、事务隔离级别
尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。
只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源加上合适的锁。此外数据库还会维护这些锁,当一个资源上锁数目太多时,自动进行锁升级以提高系统的运行性能。
(1)ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别:
隔离级别
|
脏读
|
不可重复读
|
幻读
|
第一类更新丢失
|
第二类更新丢失
|
读未提交
|
√
|
√ |
√ |
×
|
√
|
读提交
|
×
|
√
|
√
|
×
|
√
|
可重复读
|
×
|
×
|
√
|
×
|
×
|
序列化
|
×
|
×
|
×
|
×
|
×
|
(2)我们一般采用读提交的方式
1.5、JDBC对事务支持
(1)不是所有的数据库都支持事务,也不是所有支持事务的数据库都支持所有的事务隔离级别。用户可以通过
Connection的
getMetaData()方法获取
DatabaseMetaData对象,并通过该对象的
supportsTransactions()方法和
supportsTransactionIsolationLevel(int level)方法查看底层数据库的事务支持情况。
(2)Connection默认情况是自动提交事务的,即每条sql都对应一个事务,为了将多条sql当成一个事务执行,必须通过
Connection#setAutoCommit(false)阻止Connection自动提交,并通过
Connection#setTransactionIsolation()设置事务的隔离级别,下面是典型的JDBC事务数据操作代码:
(3)在JDBC2.0中,事务只有2个操作:提交和回滚。JDBC3.0以后,引入了保存点的特性,Savepoint接口允许用户将事务分割为多个阶段,用户可以指定回滚到事务的特定保存点。一个使用的例子如下:
并非所有的数据库都支持保存点功能,用户可以通过
DatabaseMetaData#savePoints()方法方法查看是否支持。
2、ThreadLocal基础知识
前面我们知道,Spring通过各种模板类降低了开发者使用各种数据持久化技术的难度。这些模板类都是线程安全的,也就是说多个DAO可以复用同一个模板实例而不会发生冲突,Spring通过ThreadLocal解决了模板类绑定数据连接和会话资源时的并发问题。
2.1、ThreadLocal是什么
(1)ThreadLocal的接口方法
- void set(Object value):设置当前线程的线程局部变量。
- public Object get():返回当前线程的线程局部变量。
- public void remove():删除当前线程的线程局部变量,目的是为了减少内存占用。需要注意的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式地调用次方法不是必须的,但是可以加快内存回收的速度。
- protected Object initialValue():返回当前线程的线程局部变量的初始值,
jdk5.0以后,ThreadLocal已经支持泛型了,变为ThreadLocal<T>。
(2)ThreadLocal是如何做到为每一个线程维护一份独立的变量副本呢?
其实实现思路很简单:在ThreadLocal中有一个Map,用于存储每一个线程的变量副本,Map中元素的key为线程对象,而value就是对应线程的变量副本。
2.2、与Thread同步机制的比较
ThreadLocal采用“以空间换时间”的方式:访问并行化,对象独享化。
2.3、Spring使用ThreadLocal解决线程安全问题
在Spring中,绝大部分Bean都可以声明为singleton,就是因为Spring对一些Bean采用了ThreadLocal进行封装。一般情况下从接收请求到返回响应所经过的所有程序调用都同属于一个线程。
3、Spring对事务管理的支持
Spring为事务管理提供了一致的编程模板,在高层次建立了统一的事务抽象。像Spring DAO为不同的持久化技术实现提供模板类一样,Spring事务管理继承了这一风格,也提供了事务模板类
TransactionTemplate。通过TransactionTemplate并配合使用事务回调TransactionCallback指定具体的持久化操作就可以通过编程方式实现事务管理,而无须关注资源获取、复用、释放、事务同步和异常处理的操作。
3.1、事务管理关键抽象
(1)在Spring事务管理SPI的抽象层主要包括3个接口,分别是
PlatformTransactionManager、
TransactionDefinition和
TransactionStatus,它们位于org.springframework.transaction包中。3者关系如图:
(2)
TransactionDefinition
- 事务隔离:TransactionDefinition使用了java.sql.Connection接口中同名的4个隔离级别,此外,TransactionDefinition还定义了一个默认的隔离级别,它表示使用底层数据库的默认隔离级别。
- 事务传播:通常在一个事务中执行的所有代码都会同一事务的上下文中。但是Spring也提供了几个可选的事务传播类型,例如简单地参与到现有的事务中,或者挂起当前的事务,创建一个新事务。
- 事务超时:超过时间后,事务被回滚。有些事务管理器不支持事务过期的功能,这时如果设置TIMEOUT_DEFAULT等值时将抛出异常。
- 只读状态:只读事务不修改任何数据,主要用于优化,如果更改数据就会抛出异常。
(3)
TransactionStatus
①TransactionStatus代表一个事务的具体运行状态,事务管理器通过该接口获
取事务的运行期状态信息,也可以通过该接口
间接地回滚事务,它相比于在抛出异常时回滚事务的方式更具有可控性。该接口继承于
SavepointManager接口,SavepointManager接口基于JDBC3.0保存点的分段事务控制能力提供了嵌套事务的机制。
其中,SavepointManager接口拥有以下的方法:
- Object createSavepoint():创建一个保存点对象,以便在后面可以利用rollbackToSavepoint(Object savepoint)方法使事务回滚到特定的保存点上,也可以通过releaseSavepoint()释放一个已经不用的保存点。
- void rollbackToSavepoint(Object savepoint):将事务回滚到特定保存点上,被回滚的保存点将自动释放。
- void releaseSavepoint(Object savepoint):释放一个保存点,如果事务提交,所有保存点会被自动释放,无须手工清除。
这3个方法在底层资源不支持保存点时,都将抛出NestedTransactionNotSupportedException异常。
②TransactionStatus扩展了SavepointManager并提供了以下方法:
- boolean hasSavepoint():判断当前的事务是否在内部创建了一个保存点,保存点是为了支持Spring的嵌套事务而创建的。
- boolean isNewTransaction():判断当前事务是否是一个新的事务,如果返回false,表示当前事务是一个已经存在的事务,或者当前操作未运行在事务环境中。
- boolean isCompleted():判断当前事务是否已经结束(已经提交或回滚)。
- boolean isRollbackOnly():判断当前事务是否已经被标识为rollback-only。
- void setRollbackOnly():将当前的事务设置为rollback-only,通过该标识通知事务管理器只能将事务回滚,事务管理器将显式调用回滚命令或以抛出异常的方式回滚事务。
(4)
PlatformTransactionManager
PlatformTransactionManager是事务的最高层抽象,它提供了3个接口方法:
- TransactionStatus getTransaction(TransactionDefinition definition):该方法根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStatus描述这个事务的状态。
- commit(TransactionStatus status):根据事务的状态提交事务,如果事务状态已经被标识为rollback-only,该方法将执行一个回滚事务的操作。
- rollback(TransactionStatus status):回滚事务,当提交事务抛出异常时,回滚会被隐式执行。
3.2、Spring的事务管理器实现类
(1)Spring将事务管理委托给底层具体的持久化实现框架完成,因此Spring为不同的持久化框架提供了PlatformTransactionManager接口的实现类,如下图:
这些事务管理器都是对特定事务实现框架的代理,这样我们就可以通过spring的高级抽象,对不同种类的事务实现使用相同的方式进行管理。
(2)Spring JDBC和iBatis
配置如下:
<bean id="
dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destory-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://数据库服务器ip:端口/数据库实例名"
p:username="数据库用户名"
p:password="密码"/
>
<!--
基于数据源的事务管理器
-->
<bean id="
transactionManager" class="
org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="
dataSource"/>
在幕后,DataSourceTransactionManager使用DataSource的Connection的commit()、rollback()等方法管理事务。
(3)JPA
JPA通过javax.persistence.EntityTransaction管理JPA的事务,EntityTransaction对象可以通过javax.persistence.EntityManager#getTransaction()获得,而EntityManager又通过一个工厂类方法获取javax.persistence.EntityManagerFactory#createEntityManager()。
在底层,JPA依然通过JDBC的Connection的事务方法完成最终的控制,因此要配置一个JPA事务管理器,还要先提供一个DataSource,然后配置一个EntityManagerFactory,最后才配置JpaTransactionManager。
配置文件如下:
......
<bean id="
entityManagerFactory" class="
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="
dataSource"/>
<bean id="
transactionManager" class="
org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="
entityManagerFactory"/>
(4)Hibernate
Spring3.x取消了对Hibernate 2.x的支持,只支持Hibernate 3.x。
配置如下:
如果希望在Java EE容器里使用JTA,我们将通过JNDI和Spring的JtaTransactionManager获取一个容器管理的DataSource。JtaTransactionManager不需要知道DataSource和其他特定的资源,因为它引用容器提供的全局事务管理。
配置如下:
3.3、事务同步管理器
Spring将JDBC的Connection、Hibernate的Session等访问数据库的连接或会话对象统称为资源。这些资源在同一时刻是不能多线程共享的,为了让DAO、Service能做到singleton,Spring的事务同步管理器类org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。
Spring框架为不同的持久化技术提供了一套从TransactionSynchronizationManager中获取对应线程绑定资源的工具类,如图所示:
- 当需要脱离模板类,手工操作底层持久化技术的原生API时,就需要通过这些工具类获取线程绑定的资源,而不应该直接从DataSource或SessionFactory中获取。因为后者不能获得和本线程相关的资源。
- 这些工具类还有另外一个重要的用途:将特定异常转换为Spring的DAO异常。
3.4、事务传播行为
(1)在一个service接口中可能会调用另一个service接口的方法,以共同完成一个完整的业务操作,Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,如下图:
4、编程式的事务管理
Spring为编程式事务管理提供了模板类org.springframework.transaction.support.TransactionTemplate,和那些持久化模板类一样,TransactionTemplate也是线程安全的。TransactionTemplate有2个重要的方法:
- void setTransactionManager(PlatformTransactionManager transactionManager):设置事务管理器。
- Object execute(TransactionCallback action):在TransactionCallback回调接口中定义需要以事务的方式组织的数据访问逻辑。
TransactionCallback接口只有一个方法:Object doInTransaction(TransactionStatus status)。如果操作不会返回结果,可以使用TransactionCallback的子接口TransactionCallbackWithoutResult。
5、使用XML配置声明式事务
5.1、使用原始的TransactionProxyFactoryBean
在早期的版本中,spring要求用户通过TransactionProxyFactoryBean对需要事务管理的业务类进行代理。现在已经不推荐使用,但是仍然可用。
(1)
声明式事务配置
使用TransactionProxyFactoryBean配置事务如下:
(2)
异常回滚/提交规则
上图<prop/>内的值为事务属性信息,其配置格式如下:
用户可以通过显式指定回滚规则:通过指定带正号(+)或负号(-)的异常类名(或异常名匹配片段),并且Exception可以配置多个。
5.2、基于tx/aop命名空间的配置
下面是通过tx/aop配置事务的示例:
<tx:method/>替代了之前TransactionProxyFactoryBean使用逗号分隔的方式配置事务属性,如果需要为不同的业务Bean采取不同的事务属性配置,则可以在<aop:config/>中定义多个切面。<tx:method/>属性表如下:
使用aop/tx配置的声明式事务管理是使用最广泛的,因为它的表达能力最强,且使用灵活。
6、使用注解配置声明式事务
6.1、使用@Transactional注解
(1)@Transactional可以用于标注类和类的
public方法,当然,注解只是元数据,它本身并不能完成事务切面织入的功能,因此我们还要在Spring配置文件中,通过一行配置“通知”Spring对标注@Transactional的Bean进行加工处理。如下:
。。。
<!-- 对标注@Transactional的Bean进行加工处理,以织入事务管理切面 -->
<tx:annotation-driven transaction-manager="txManager"/>
(2)<tx:annotation-driven/>会
自动使用名为
transactionManager的事务管理器,所以如果用户的事务管理器id为transactionManager,可以进一步将上面的配置
简化为<tx:annotation-driven/>。
(3)<tx:annotation-driven/>还要另外2个属性:
- proxy-target-class:true表示使用cglib创建代理类,false表示使用jdk创建代理类。
- order:如果业务类除事务切面外,还需要织入其他的切面,通过该属性可以控制事务切面在目标连接点的织入顺序。
(4)@Transactional属性说明如图:
- 事务传播行为:PROPAGATION_REQUIRED;
- 事务隔离级别:ISOLATION_DEFAULT;
- 读写事务属性:读/写事务;
- 超时时间:依赖于底层的事务系统的默认值;
- 回滚设置:任何运行期异常引发回滚,任何检查型异常不会引发回滚。
有2点需要注意:
- 如果不设置rollbackFor,那么只有运行期异常会被回滚,检查型异常不会回滚;
- rollbackForClassName是通配符匹配,即只要有几个字符匹配就行;
- 就算指定了rollbackForClassName的检查型异常,程序遇到运行期异常,依然会引发回滚。
(5)在何处标注@Transactional注解
@Transactional注解可以被应用于接口和接口方法,类和类的
public方法上(
类级的注解适用于类中的所有public方法,方法处的注解会覆盖类定义处的注解)。但Spring建议在业务实现类上使用@Transactional注解,因为注解不能被继承,所以用在接口上会有隐患。即使<tx:annotation-driven/>的proxy-target-class属性设置为true,业务类照样工作在非事务的环境下。
(6)使用不同的事务管理器
6.2、通过引入AspectJ LTW在类加载期引入事务切面
7、集成特定的应用服务器
一般来说Spring事务抽象与应用服务器是无关的,如果用户希望事务管理器使用特定的UserTransaction和TransactionManager对象,以获取更多的事务控制功能,这时可以采用Spring为集成这些应用服务器所提供的适配器。