1.数据库事务基础知识
1.1.何为数据库事务
数据库事务的4个特性
- 原子性:组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交。
- 一致性:事务操作成功后,数据库所处的状态和业务规则是一致的,即数据不会被破坏。
- 隔离性:在并发操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰,准确的说,并非要求做到完全无干扰。
- 持久性:一旦事务提交成功之后,事务中所有的数据操作都必须持久化到数据库中。
数据库管理系统一般采用重执日志来保证原子性、一致性和持久性。重执日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退出,数据库即可根据重执行日志撤销已经执行的操作。此外,对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作
和Java程序采用对象锁机制进行线程同步类似,数据库管理系统采用数据库锁机制保证事务的隔离性。当多个事务试图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据进行操作。Oracle数据库还使用了数据版本的机制,在回滚段位数据的每个变化都保存一个版本,使数据的更改不影响数据的读取。
1.2.数据的并发问题
一个数据库可能拥有多个访问客户端,这些客户端都可用并发的方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性
- 脏读:A事务读取B事务尚未提交的更改数据,并在这个数据基础上进行操作
- 不可重复读:A事务读取了B事务已经提交的更改数据
- 幻象读:A事务读取了B事务提交的新增数据,这时A事务将出现幻象读的问题
两类丢失事件
- A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了
- A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失
1.3.数据库锁机制
数据库通过锁机制解决并发访问的问题,虽然不同的数据库在实现细节上存在差别,但原理基本上是一样的。按锁定的对象不一样,一般可以分为表锁定和行锁定。前者对整张表锁定,后者对表中特定行进行锁定。从并发事务锁定的关系上看,可以分为共享锁和独占锁。共享锁定会防止独占锁定,但允许其他的共享锁定。而独占锁定既防止其他的独占锁定,也防止其他的共享锁定。为了更改数据,数据库必须在进行更改的行上施加独占锁定,select、update、delete和select for update 语句都会隐式采用必要的行锁定。oracle数据库常用的5种锁定
行共享锁定:一般是通过select for update语句隐式获得行共享锁定,行共享锁定并
不防止对数据进行更新操作,但是可以防止其他会话获取独占性数据表锁定行独占锁定:通过insert,update和delete语句隐式获取,这种锁定可以防止其他会话获取一个共享锁定,共享行独占锁或独占锁定
表共享锁定:这种锁定可以防止其他会话获取行独占锁定,或者防止其他表共享行独占锁定或表独占锁定,但它允许在表中拥有多个行共享和表共享锁定。该锁定可以让会话具有对表事务级一致性访问,因为其他会话在用户提交或者回滚该事务并释放对该表的锁定之前不能更改这张被锁定的表
表共享行独占锁:这种锁定可以防止其他会话获取一个表共享、行独占或者表独占锁定,但允许其他行共享锁定。这种锁定类似于表共享锁定,只是一次只能对一张表放置一个表共享行独占锁定。
表独占锁定:这种锁定可以防止其他会话对该表的任何其他锁定
1.4.事务隔离级别
数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加合适的锁。此外,数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的
隔离级别 | 脏读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
1.5.JDBC对事务的支持
并不是所有的数据库都支持事务,即使支持事务的数据库也并非支持所有的事务隔离级别。用户可以根据Connection.getMetaData()方法获取DatabaseMetaData对象,并通过该对象的supportsTransactions()、supportsTransactionIsolationLevel(int level)
方法查看底层数据库的事务支持情况
Connection默认情况下是自动提交的,即每条执行的SQL语句都对应一个事务。为了将多条SQL语句当成一个事务执行,必须先通过Connection.setAutoCommit(false)阻止Connection自动提交,并通过Connection.setTransactionIsolation()设置事务的隔离级别。
2.ThreadLocal基础知识
2.1.ThreadLocal是什么
ThreadLocal,不是一个线程,而是保存线程本地化对象的容器,当运行多线程环境的某个对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像线程专有的本地变量
InheritableThreadLocal继承与ThreadLocal,它自动为子线程复制一份从父线程那里继承而来的本地变量:在创建子线程时,子线程会接收所有可继承的线程本地变量的初始值。当必须将本地线程变量自动传送给所有创建的子线程时,应尽可能地使用InheritableThreadLocal,而非ThreadLocal
2.2.ThreadLocal的接口方法
- void set(Object value):设置当前线程的线程局部变量的值
- public Object get():返回当前线程所对应的线程局部变量
- public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用。该方法时Java 5.0新增的,需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显示调用该方法清除线程的局部变量并不是必需的操作,但它可以加快内存回收的速度
- protected Object initialValue():返回该线程局部变量的初始值,该方法时一个protected方法,显示是为了让子类覆盖而设计的
2.3.一个ThreadLocal实例
public class SequenceNumber {
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
//获取下一个序列值
public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args) {
SequenceNumber sn =new SequenceNumber();
TestClient t1=new TestClient(sn);
TestClient t2=new TestClient(sn);
TestClient t3=new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
this.sn=sn;
}
public void run(){
for (int i=0;i<3;i++){//每个线程输出三个序列值
System.out.println("thread["+Thread.currentThread().getName()+"] sn["+sn.getNextNum()+"]");
}
}
}
}
//输出内容
thread[Thread-0] sn[1]
thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
thread[Thread-2] sn[1]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]
考查输出结果信息,发现每个线程所产生的序号虽然都是共享同一个SequenceNumber实例,但它们并没有相互干扰,而是各自产生独立的序列号,这是因为通过ThreadLocal为每个线程提供了单独的副本
2.4.与Thread同步机制的比较
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读/写、什么时候需要锁定某个对象、什么时候释放对象锁等繁杂的问题,程序设计和编写难度较大
而ThreadLocal从另一个角度来解决多线程的并发访问。ThreadLocal为每个线程提供了一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每个线程都拥有自己的变量副本,因而也就没有必要对该变量进行同步,ThreadLocal提供了线程安全的对象封装,在编写多线程时,可以把不安全的变量封装进ThreadLocal
2.5.Spring使用ThreadLocal解决线程安全问题
一般情况下,只有无状态的Bean才可以在多线程环境下共享。在Spring中,绝大部分Bean都可以声明为singleton作用域。正是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中正常工作
web应用一般分为控制层,业务层和持久层,在不同测层次编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求返回响应所经过的所有程序调用都属于同一个线程,这样用户就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定。
3.Spring对事务管理的支持
Spring为事务管理提供了一致的编程模板,在高层次建立了统一的事务抽象,像Spring DAO为不同的持久化类提供了模板类一样,Spring也提供了事务模板类TransactionTemplate。通过TransactionTemplate并配合使用事务回调TransactionCallback指定具体的持久化操作,就可以通过编程方式实现事务管理,而无须关注资源获取、复用、释放、事务同步和异常处理等操作。
3.1.事务管理关键抽象
在事务管理SPI的抽象层主要包括3个接口,分别是PlatformTransactionManager、TransactionDefinition和TransactionStatus
-
TransactionDefinition:定义了Spring兼容的事务属性
- 事务隔离:当前事务和其它事务的隔离程度
- 事务传播:通常在一个事务中执行的所有代码都会运行于同一事务上下文中
- 事务超时:事务在超时前能运行多久,超过时间后,事务被回滚
- 只读状态:只读事务不修改任何数据,资源事务管理者可以针对可读事务应用一些优化措施,提高运行性能
-
TransactionStatus:代表一个事务的具体运行状态。事务管理器可以通过该接口获取事务运行期的状态信息,也可以通该接口间接地回滚事务,它相比在抛出异常时回滚事务的方式更具有可控性。SavepointManager接口拥有以下几个方法
- Object createSavepoint():创建一个保存点对象,以便在后面可以利用rollbackToSavepoint方法使事务回滚到特定的保存点上
- void rollbackSavepoint(Object savepoint):将事务回滚到特定的保存点上,
被回滚的保存点将自动释放 - void releaseSavepoint(Object savepoint):释放一个保存点。如果事务提交,
则所有的保存点会被自动释放,无须手工清除
PlatformTransactionManager:通过JDBC的事务管理知识可以知道,事务只能被提交或回滚
3.2.Spring的事务管理器实现类
Spring将事务管理委托给底层具体的持久化实现框架来完成。因此,Spring为不同的持久化框架提供了PlatformTransactionManager接口的实现类。
- Spring JDBC和MyBatis:如果使用Spring JDBC或MyBatis,由于它们都基于数据源的
Connection访问数据库,所以可以使用DataSourceTransactionManager,只要在Spring中进行以下配置就可以了
<!--配置一个dataSource的数据源-->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<!--基于数据源的事务管理器-->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/><!--引用数据源-->
- jpa:jpa通过javax.persistence.EntityTransaction管理JPA事务,EntityTransaction对象可以通过EntityTransaction.getTransaction()方法获得,在底层,JPA依然通过JDBC的Connection的事务方法完成最终的控制,因此要配置一个EntityManagerFactory,最后才配置JpaTransactionManager,
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.datasource.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"/><!--指定一个数据源-->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/><!--指定实体管理器-->
- Hibernate:Spring 4.0已取消了对Hibernate3.6之前版本的支持,并全面支持Hibernate5.0;因此,只为Hibernate 3.6+提供了事务管理器。
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
p:dataSource-ref="dataSource"<!--指定一个数据源-->
p:mappingResource="classpath:bbtFoum.hbm.xml"><!--指定Hibernate的配置文件-->
<property name="annotatedClasses"><!--Hibernate其它属性-->
<list>
<value>com.smart.User</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/><!--注入会话工厂-->
- JTA:如果希望在JavaEE容器里使用JTA,则将通过JNDI和Spring的JtaTransactionManager获取一个容器管理的DataSource。JtaTransactionManager不需要知道DataSource和其他特定的资源,因为它引用容器提供的全局事务管理。
<jee:jndi-lookup id="accountDs" jndi-name="java:comp/env/jdbc/account"/>
<jee:jndi-lookup id="orderDs" jndi-name="java:comp/env/jdbc/order"/>
<!--通过jee命名空间获取Java EE应用服务器容器中的数据源-->
<!--指定JTA事务管理器-->
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"
</bean>
3.3.事务同步管理器
Spring将JDBC的Connection、Hibernate的Session等访问数据库的连接或会话对象统称为资源,这些资源在同一时刻是不能多线程共享的。为了让DAO、Service类可能做到singleton,Spring的事务同步管理器类SynchronizationManager使用ThreadLocal为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。事务同步管理器是Spring事务管理的基石,不管用户使用的是编程式事务管理,还是声明式事务管理,都离不开事务同步管理器
3.4.事务传播行为
当我们调用一个基于Spring的Service接口方法时,它将运行于Spring管理的事务环境中,Service接口方法可能会在内部调用其他的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况,Spring通过事务传播行为控制当前的事务如何被传播到被嵌套调用的目标服务接口方法中
- 事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到这个事务中。这是最常见的选择 |
PROPAGATION_SUPPORTS | 支持当前事务,如果没有当前事务,则以非事务方式执行 |
PROPAGATION_MANDATORY | 使用当前事务,如果当前没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
4、编程式的事务管理
Spring还是为编程式事务管理提供了模板类TransactionTemplate,以满足一些特殊场合的需要。TransactionTemplate有两个主要方法:
- void setTransactionManager(PlatformTransactionManager transactionManager):
设置事务管理器 - Object execute(TransactionCallback action):在TransactionCallback回调接口中
定义需要以事务方式组织的数据访问逻辑
@Service
public class ForumService{
public ForumDao forumDao;
public TransactionTemplate template;
@Autowired
public void setTemplate(TransactionTemplate template){//通过aop注入
this.template =template;
}
public void addForum(final Forum forum){
template.execute(new TransactionCallbackWithoutResult(){
protected void doInTransactionWithoutResult(TransactionStatus status){
forumDao.addForum(forum);//需要在事务环境中执行的代码
}
})
}
}
由于Spring事务管理基于TransactionSynchronizationManager进行工作,所以,如果在回调接口方法中需要显示访问数据库底层数据连接,则必须通过资源获取工具类得到线程绑定的数据连接。这是Spring事务管理的底层协议,不容违反。如果FourmDao是基于Spring提供模板类构建的,由于模板类已经在内部使用了资源获取工具类获取数据连接,所以用户就不必关心底层数据库连接的获取问题。
5、使用XML配置声明式事务
大多数Spring用户选择声明式事务管理的功能,这种方式对代码的侵入性最小,可以让事务管理代码完全从业务代码中移除,非常符合非侵入式轻量级容器的理念。
Spring的声明式事务管理是通过Spring AOP实现的,通过事务中声明性信息,Spring
负责将事务管理增强逻辑动态织入业务方法的相应连接点中。这些逻辑包括获取线程绑定资源、开始事务、提交/回滚事务、进行异常转换和处理等工作。
5.1.一个将被实施事务增强的服务接口
BbtForum拥有4个方法,我们希望addTopic()和updateForum()方法拥有写事务的能力,而其他两个方法只需要拥有读事务的能力就可以了,
@Service
@Transactional
public class BbtForum{
public ForumDao forumDao;
public TopicDao topicDao;
public PostDao postDao;
public void addTopic(Topic topic){
topicDao.addTopic(topic);
postDao.addPost(topic.getPost());
}
public Forum getForum(int forumId){
return forumDao.getForum(forumId);
}
public void updateForum(Forum forum){
forumDao.updateForum(forum);
}
public int getForumNum(){
return forumDao.getForumNum();
}
}
BbtForum是一个POJO,只是简单的使用持久层的多个DAO类,通过它们的协作实现业务功能。在这里,我们看不到任何事务操作的代码,所以如果直接使用BbtForum,那么这些方法都将以无事务的方式运行。现在,我们的任务是通过Spring声明式事务配置让这些业务方法拥有适合的事务功能。
5.2.使用原始的TransactionProxyFactoryBean
- 声明式事务配置
<!--1、引入DAO和DataSource的配置文件-->
<import resource="classpath:applicationContext-dao.xml"/>
<!--2、声明事务管理-->
<bean id="txManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataDource"/>
</bean>
<!--3、需要实施事务增强的目标业务Bean-->
<bean id="bbtForumTarget" class="com.example.SequenceNumber"
p:forumDao-ref="forumDao"
p:topicDao-ref="topicDao"
p:postDao-ref="postDao"
/>
<!--4、使用事务代理工厂类为目标业务Bean提供事务增强-->
<bean id="bbtForum" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
p:transactionManager-ref="txManager"//4、1指定事务管理器
p:target-ref="bbtForumTarget">//4、2指定目标业务
<property name="transactionAttributes">//4、3事务属性
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
//4、4只读事务
<prop key="*">PROPAGATION_REQUIRED</prop>
//4、5可写事务
</props>
</property>
</bean>
按照约定的习惯,需要事务增强的业务类一般将id取名为xxxTarget,如3处所示,这可以在字面上表示该Bean是要被代理的目标Bean
通过TransactionProxyFactoryBean对业务类进行代理,织入事务增强功能,如4处所示,首先需要为该代理类指定事务管理器,这些事务管理器实现了PlatformTransactionManager接口,其次,通过target属性指定需要代理的目标Bean;最后,为业务Bean的不同方法配置事务属性。Spring允许通过键值配置业务方法的事务属性信息,键可以使用通配符,如get代表目标类中所有以get为前缀的方法,它匹配BbtForum的getForum(int forumId)和getForumNum()方法;而key=""匹配BbtForum接口的所有方法
-
异常回滚/提交规则
<prop>内的值为事务属性信息,其配置格式如下图
PROPAGATION(传播行为),ISOLATION(隔离级别(可选)),readOnly(是否为只读事务(可选)),-Exception(发生这些异常时回滚事务(可选)),+Exception(发生这些异常时照样提交事务(可选))
用户可以通过配置显示回滚规则:指定带正号(+)或负号(-)的异常类名(或异常匹配片段)。当抛出负号行异常时,将触发事务回滚;将抛出正号型异常时,即使这个异常时检查型异常,事务也会提交。抛出的异常或该异常的祖先类的类名匹配规则中指定了异常类名(或异常名片段),规则就会生效,如下面的设置:
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
只要业务方法运行时抛出的异常或其父类异常的类名包括“Exception”,事务都回滚,以下异常都符合这条规则:SQLException、ParseException。正因为Spring采用名称字符串包含的比较方式,所以用户甚至可以将回滚规则设置为“-tion”。
因为Spring默认的事务回滚规则为“运行期异常回滚,检查型异常不回滚”,所以带负号的异常设置仅对检查型异常有意义,此外,用户可以指定多个带正号或负号的事务回滚/提交规则
<prop key="add*">
PROPAGATION_REQUIRED,-XxxException,-YyyException
</prop>
5.3.基于aop/tx命名空间的配置
<!--引入DAO和DataSource的配置文件-->
<import resource="classpath:applicationContext-dao.xml"/>
<!--声明事务管理-->
<bean id="txManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataDource"/>
</bean>
<!--使用强大的切点表达式语言轻松定义目标方法-->
<aop:config>
<!--2.1.通过aop定义事务增强切面-->
<aop:pointcut id="serviceMethod" expression="execution(* com.example.SequenceNumber.*(..))"/>
<!--2.2.引用事务增强-->
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
</aop:config>
<!--3.事务增强-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--3.1.事务属性定义-->
<tx:attributes>
<tx:method name="get*" read-only="false"/>
<tx:method name="add*" rollback-for="Exception"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
6.使用主机配置声明式事务
除了基于XML的事务配置,Spring还提供了基于注解的事务配置,即通过@Transactional对需要事务增强的Bean接口、实现类或方法进行标注;在容器中配置基于注解的事务增强驱动,即可启用基于注解的声明式事务,笔者在实际项目中一般采用这种配置
6.1.使用@Transactional注解
使用@Transactional对基于aop/tx的命名空间的事务配置进行改造
@Service
@Transactional
public class BbtForum{
public ForumDao forumDao;
public TopicDao topicDao;
public PostDao postDao;
public void addTopic(Topic topic){
topicDao.addTopic(topic);
postDao.addPost(topic.getPost());
}
public Forum getForum(int forumId){
return forumDao.getForum(forumId);
}
public void updateForum(Forum forum){
forumDao.updateForum(forum);
}
public int getForumNum(){
return forumDao.getForumNum();
}
}
因为注解本身具有一组普适性的默认事务属性,所以往往是要在需要事务管理的业务类中添加一个@Transactional注解,就完成了业务类事务属性的配置
<!--对标注@Transactional注解的Bean进行加工处理,以织入事务管理切面-->
<tx:annotation-driven transaction-manager="txManager>/
<tx:annotation-driven>还有两个属性
- proxy-target-class:如果为true,则Spring将通过创建子类来代理业务类;如果为false,则使用基于接口的代理。如果使用子类代理,则需要在类路径中添加CGLib.jar类库
- order:如果业务类除事务切面外,还需要织入其它的切面,则通过该属性可以控制事务切面在目标连接点的织入顺序
- 关于@Transactional的属性
- 事务传播行为
- 事务隔离级别
- 读写事务属性
- 超时时间
- 回滚设置
- 在何处标注@Transactional注解:Spring建议在业务实现类上使用@Transaction注解
- 在方法处使用注解:方法处的主机会覆盖定义处的注解
- 使用不同的事务管理器:一般情况下,一个应用仅需使用一个事务管理器,如果希望在不同的地方使用不同的事务管理器
@Service
public class MultiForumService{
//1、使用名为topic的事务管理器
@Transactional("topic")
public void addTopic(Topic topic){
...
}
//2、使用名为forum的事务管理器
@Transactional("forum")
public void updateForum(Forum forum){
...
}
}
<bean id="forumTxManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="forumDataSource">
<qualifier value="forum"/>
</bean>
<bean id="topicTxManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="topicDataSource">
<qualifier value="topic"/>
</bean>
<tx:annotation-driven>
在1处,为事务管理器指定了一个数据源,每个事务管理器都可以绑定一个独立的数据源,在2处,指定了一个可被@Transactional注解引用的事务管理器标识,如果到处都使用代标识的注解,可以自定义一个绑定到特定事务管理器的注解,然后直接使用这个自定义的注解(感觉没什么意义,都是使用注解)
@Target(ElementType.METHOD,ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Transactional("forum")//绑定到forum的事务管理器中
public @interface ForumTransaction{
}
7.集成特定的应用服务器
一般来说,Spring的事务抽象与应用服务器是无关的,不过,如果用户希望事务管理器使用特定的UserTransaction和TransactionManager对象(可以被自动探测),以获取更多的事务控制功能(如事务挂起等),则可以采用Spring为集成这些应用服务器所提供的适配器
7.1.BEA WebLogic
<bean id="txManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>
7.2.WebSphere
<bean id="txManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager">