做东西遇到了Spring事务的问题,google了一下,找了些资料,可惜没时间细看,更没有时间整理,先把链接放到这里吧,以后再整理:)
在spring中使用声明型事务
邢红瑞 发表于 2006-1-8 18:34:39 | |
|
http://blogger.org.cn/blog/more.asp?name=hongrui&id=11162
Spring事务传播机制解惑
概述
当我们调用一个基于Spring的Service接口方法(如UserService#addUser())时,它将运行于Spring管理的事务 环境中,Service接口方法可能会在内部调用其它的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情 况,Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。
事务传播是Spring进行事务管理的重要概念,其重要性怎么强调都不为过。但是事务传播行为也是被误解最多的地方,在本文里,我们将详细分析不同事务传播行为的表现形式,掌握它们之间的区别。
事务传播行为种类
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:
表 1事务传播行为类型
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。
几种容易引起误解的组合事务传播行为
当服务接口方法分别使用表1中不同的事务传播行为,且这些接口方法又发生相互调用的情况下,大部分组合都是一目了然,容易理解的。但是,也存在一些容易引起误解的组合事务传播方式。
下面,我们通过两个具体的服务接口的组合调用行为来破解这一难点。这两个服务接口分别是UserService和 ForumService,UserSerice有一个addCredits()方法,ForumSerivce#addTopic()方法调用了 UserSerice#addCredits()方法,发生关联性服务方法的调用:
public class ForumService {
private UserService userService;
public void addTopic() {①调用其它服务接口的方法
//add Topic…
userService.addCredits() ;②被关联调用的业务方法
}
}
嵌套调用的事务方法
对Spring事务传播行为最常见的一个误解是:当服务接口方法发生嵌套调用时,被调用的服务方法只能声明为 PROPAGATION_NESTED。这种观点犯了望文生义的错误,误认为PROPAGATION_NESTED是专为方法嵌套准备的。这种误解遗害不 浅,执有这种误解的开发者错误地认为:应尽量不让Service类的业务方法发生相互的调用,Service类只能调用DAO层的DAO类,以避免产生嵌 套事务。
其实,这种顾虑是完全没有必要的,PROPAGATION_REQUIRED已经清楚地告诉我们:事务的方法会足够“聪明”地判断上下文是否已经存在一个事务中,如果已经存在,就加入到这个事务中,否则创建一个新的事务。
依照上面的例子,假设我们将ForumService#addTopic()和UserSerice#addCredits()方法的事务传播行为都设置为PROPAGATION_REQUIRED,这两个方法将运行于同一个事务中。
为了清楚地说明这点,可以将Log4J的日志设置为DEBUG级别,以观察Spring事务管理器内部的运行情况。下面将两个业务方法都设置为PROPAGATION_REQUIRED,Spring所输出的日志信息如下:
Using transaction object
[org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@e3849c]
①为 ForumService#addTopic() 新建一个事务
Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] for JDBC transaction
Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] to manual commit
Bound value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] to thread [main]
Initializing transaction synchronization
Getting transaction for [com.baobaotao.service.ForumService.addTopic]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] bound to thread [main]
Using transaction object [org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@8b8a47]
② UserService#addCredits() 简单地加入到已存在的事务中(即①处创建的事务)
Participating in existing transaction
Getting transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.ForumService.addTopic]
Triggering beforeCommit synchronization
Triggering beforeCompletion synchronization
Initiating transaction commit
③调用底层 Connection#commit() 方法提交事务
Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5]
Triggering afterCommit synchronization
Triggering afterCompletion synchronization
Clearing transaction synchronization
嵌套事务
将ForumService#addTopic()设置为PROPAGATION_REQUIRED 时,UserSerice#addCredits()设置为PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY时,运行的效果都是一致的(当然,如果单独调用addCredits()就另当别论了)。
当addTopic()运行在一个事务下(如设置为PROPAGATION_REQUIRED),而addCredits()设置为 PROPAGATION_NESTED时,如果底层数据源支持保存点,Spring将为内部的addCredits()方法产生的一个内嵌的事务。如果 addCredits()对应的内嵌事务执行失败,事务将回滚到addCredits()方法执行前的点,并不会将整个事务回滚。内嵌事务是内层事务的一 部分,所以只有外层事务提交时,嵌套事务才能一并提交。
嵌套事务不能够提交,它必须通过外层事务来完成提交的动作,外层事务的回滚也会造成内部事务的回滚。
嵌套事务和新事务
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的两个传播行为。PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:PROPAGATION_REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,而 PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。
其它需要注意问题
以下几个问题值得注意:
1) 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。如将 ForumService#addTopic()设置为PROPAGATION_MANDATORY,如果展现层的Action直接调用 addTopic()方法,将引发一个异常。正确的情况是:addTopic()方法必须被另一个带事务的业务方法调用(如 ForumService#otherMethod())。所以PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。
2) 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设 UserService#addCredits()设置为PROPAGATION_NEVER,当ForumService# addTopic()拥有一个事务时,addCredits()方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的。
3)当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。
小结
在Spring声明式事务管理的配置中,事务传播行为是最容易被误解的配置项,原因在于事务传播行为名称(如 PROPAGATION_NESTED:嵌套式事务)和代码结构的类似性上(业务类方法嵌套调用另一个业务类方法)。这种误解在很多Spring开发者中 广泛存在,本文深入讲解了Spring事务传播行为对业务方法嵌套调用的真实影响,希望能帮助读者化解对事务传播行为的困惑。
============================================
Spring 声明式事务管理源码解读之事务提交
简介: 上次说到spring 声明式事务管理的事务开始部分,按流程来讲,下面应该提交事务了, spring 的声明式事务管理其实是比较复杂的,事实上这种复杂性正是由于事务本身的复杂性导致的,如果能用两三句话就把这部分内容说清楚是不现实的,也是不成熟的,而我对这部分的理解也可能是不全面的,还是那句话,希望大家和我一起把本贴的质量提交起来。
在下面的文章中,我讲会多次提到第一篇文章,第一篇文章的地址是:http://www.javaeye.com/topic/87426
如果要理解事务提交的话,理解事务开始是一个前提条件,所以请先看第一篇文章,再来看这篇
如果你仔细看下去,我想肯定是有很多收获,因为我们确实能从spring 的代码和思想中学到很多东西。
正文:
其实俺的感觉就是事务提交要比事务开始复杂,看事务是否提交我们还是要回到TransactionInterceptor类的invoke方法
- public Object invoke(MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be < code > null </ code > .
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface
- Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
- // Create transaction if necessary.
- TransactionInfo txInfo = createTransactionIfNecessary (invocation.getMethod(), targetClass);
- Object retVal = null ;
- try {
- // This is an around advice.
- // Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation .proceed();
- }
- catch (Throwable ex) {
- // target invocation exception
- doCloseTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- doFinally(txInfo);//业务方法出栈后必须先执行的一个方法
- }
- doCommitTransactionAfterReturning(txInfo);
- return retVal;
- }
其中的doFinally(txInfo)那一行很重要,也就是说不管如何,这个doFinally方法都是要被调用的,为什么它这么重要呢,举个例子:
我们还是以propregation_ required 来举例子吧,假设情况是这样的,AService中有一个方法调用了BService中的,这两个方法都处在事务体之中,他们的传播途径都是required 。 那么调用开始了,AService的方法首先入方法栈,并创建了TransactionInfo的实例,接着BService的方法入栈,又创建了一个 TransactionInfo的实例,而重点要说明的是TransactionInfo是一个自身关联的内部类,第二个方法入栈时,会给新创建的 TransactionInfo的实例设置一个属性,就是TransactionInfo对象中的private TransactionInfo oldTransactionInfo;属性,这个属性表明BService方法的创建的TransactionInfo对象是有一个old的 transactionInfo对象的,这个oldTransactionInfo对象就是AService方法入栈时创建的 TransactionInfo对象,我们还记得在createTransactionIfNecessary方法里有这样一个方法吧:
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- // We always bind the TransactionInfo to the thread, even if we didn't create
- // a new transaction here. This guarantees that the TransactionInfo stack
- // will be managed correctly even if no transaction was created by this aspect.
- txInfo.bindToThread();
- return txInfo;
- }
- 就是这个bindToThread()方法在作怪:
- private void bindToThread() {
- // Expose current TransactionStatus, preserving any existing transactionStatus for
- // restoration after this transaction is complete.
- oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();
- currentTransactionInfo.set(this );
- }
如果当前线程中已经有了一个TransactionInfo,则拿出来放到新建的transactionInfo对象的oldTransactionInfo属性中,然后再把新建的TransactionInfo设置到当前线程中。
这里有一个概念要搞清楚,就是TransactionInfo对象并不是表明事务状态的对象,表明事务状态的对象是TransactionStatus对象,这个对象同样是TransactionInfo的一个属性(这一点,我在前面一篇文章中并没有讲清楚)。
接下来BService中的那个方法返回,那么该它退栈了,它退栈后要做的就是doFinally方法,即把它的 oldTransactionInfo设置到当前线程中(这个TransactionInfo对象显然就是AService方法入栈时创建的,怎么现在又 要设置到线程中去呢,原因就是BService的方法出栈时并不提交事务,因为BService的传播途径是required , 所以要把栈顶的方法所创建transactioninfo给设置到当前线程中),即调用AService的方法时所创建的TransactionInfo 对象。那么在AServie的方法出栈时同样会设置TransactionInfo对象的oldTransactionInfo到当前线程,这时候显然 oldTransactionInfo是空的,但AService中的方法会提交事务,所以它的oldTransactionInfo也应该是空了。
在这个小插曲之后,么接下来就应该是到提交事务了,之前在AService的方法出栈时,我们拿到了它入栈时创建的TransactionInfo 对象,这个对象中包含了AService的方法事务状态。即TransactionStatus对象,很显然,太显然了,事务提交中的任何属性都和事务开 始时的创建的对象息息相关,这个TransactionStatus对象哪里来的,我们再回头看看createTransactionIfNessary 方法吧:
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- txInfo.newTransactionStatus(this .transactionManager.getTransaction(txAttr));
- }
再看看transactionManager.getTransaction(txAttr)方法吧:
- public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
- else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (debugEnabled) {
- logger.debug("Creating new transaction with name [" + definition.getName() + "]" );
- }
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true , newSynchronization, debugEnabled, null ); //注意这里的返回值,返回的就是一个TransactionStatus对象,这个对象表明了一个事务的状态,比如说是否是一个新的事务,事务是否已经结束,等等,这个对象是非常重要的,在事务提交的时候还是会用到它的。 }
- }
- }
还有一点需要说明的是,AService的方法在执行之前创建的transactionstatus确实是通过这个方法创建的,但是,BService的方法在执行之前创建transactionstatus的方法就与这个不一样了,下面会有详解。
回顾了事务开始时所调用的方法之后,是不是觉得现在对spring 如何处理事务越来越清晰了呢。由于这么几个方法的调用,每个方法入栈之前它的事务状态就已经被设置好了。这个事务状态就是为了在方法出栈时被调用而准备的。
让我们再次回到BService中的方法出栈的那个时间段,看看spring 都 做了些什么,我们知道,后入栈的肯定是先出栈,BService中的方法后入栈,拿它肯定要先出栈了,它出栈的时候是要判断是否要提交事务,释放资源的, 让我们来看看TransactionInterceptor的invoke的最后那个方法 doCommitTransactionAfterReturning:
- protected void doCommitTransactionAfterReturning(TransactionInfo txInfo) {
- if (txInfo != null && txInfo.hasTransaction()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
- }
- this .transactionManager.commit(txInfo.getTransactionStatus());
- //瞧:提交事务时用到了表明事务状态的那个TransactionStatus对象了。
- }
- }
看这个方法的名字就知道spring 是要在业务方法出栈时提交事务,貌似很简单,但是事实是这样的吗? 我们接着往下看。
- public final void commit(TransactionStatus status) throws TransactionException {
- DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
- if (defStatus.isCompleted()) {
- throw new IllegalTransactionStateException(
- "Transaction is already completed - do not call commit or rollback more than once per transaction" );
- }
- if (defStatus.isLocalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Transactional code has requested rollback" );
- }
- processRollback(defStatus);
- return ;
- }
- if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Global transaction is marked as rollback-only but transactional code requested commit" );
- }
- processRollback(defStatus);
- throw new UnexpectedRollbackException(
- "Transaction has been rolled back because it has been marked as rollback-only" );
- }
- processCommit(defStatus);
- }
上面这段代码就是transactionmanager中的commit,但是看上去,它又把自己的职责分配给别人了,从代码里我们看到, 如果事务已经结束了就抛异常,如果事务是rollbackonly的,那么就rollback吧,但是按照正常流程,我们还是想来看一下,事务的提交,就 是processCommit(status)这个方法吧。
- private void processCommit(DefaultTransactionStatus status) throws TransactionException {
- try {
- boolean beforeCompletionInvoked = false ;
- try {
- triggerBeforeCommit(status);
- triggerBeforeCompletion(status);
- beforeCompletionInvoked = true ;
- if (status.hasSavepoint()) {
- if (status.isDebug()) {
- logger.debug("Releasing transaction savepoint" );
- }
- status.releaseHeldSavepoint();
- }
- else if (status.isNewTransaction()) { //这个判断非常重要,下面会详细讲解这个判断的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- // Throw UnexpectedRollbackException if we have a global rollback-only
- // marker but still didn't get a corresponding exception from commit.
- `````````````````````
- }
我们注意到,在判断一个事务是否是新事务之前还有一个status.hasSavepoint()的判断,我认为这个判断事实上就是嵌套事 务的判断,即判断这个事务是否是嵌套事务,如果不是嵌套事务,则再判断它是否是一个新事务,下面这段话就非常重要了,BService的中的方法是先出栈 的,也就是说在调用BService之前的创建的那个事务状态对象在这里要先被判断,但是由于在调用BService的方法之前已经创建了一个 Transaction 和Session(假设我们使用的是hibernate3),这时候在创建第二个TransactionInfo(再强调一下吧,TransactionInfo并不是Transaction ,Transaction 是 真正的事务对象,TransactionInfo只不过是一个辅助类而已,用来记录一系列状态的辅助类)的TransactionStatus的时候就会 进入下面这个方法(当然在这之前会判断一下当前线程中是否已经有了一个SessionHolder对象,不清楚SessionHolder作用的同学情况 第一篇文章),这个方法其实应该放到第一篇文章中讲的,但是想到如果不讲事务提交就讲这个方法好像没有这么贴切,废话少说,我们来看一下吧:
- private TransactionStatus handleExistingTransaction(
- TransactionDefinition definition, Object transaction, boolean debugEnabled)
- throws TransactionException {
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
- throw new IllegalTransactionStateException(
- "Transaction propagation 'never' but existing transaction found" );
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction" );
- }
- Object suspendedResources = suspend(transaction);
- boolean newSynchronization = ( this .transactionSynchronization == SYNCHRONIZATION_ALWAYS);
- return newTransactionStatus(
- definition, null , false , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction, creating new transaction with name [" +
- definition.getName() + "]" );
- }
- Object suspendedResources = suspend(transaction);
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(
- definition, transaction, true , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (!isNestedTransactionAllowed()) {
- throw new NestedTransactionNotSupportedException(
- "Transaction manager does not allow nested transactions by default - " +
- "specify 'nestedTransactionAllowed' property with value 'true'" );
- }
- if (debugEnabled) {
- logger.debug("Creating nested transaction with name [" + definition.getName() + "]" );
- }
- if (useSavepointForNestedTransaction()) {
- // Create savepoint within existing Spring-managed transaction,
- // through the SavepointManager API implemented by TransactionStatus.
- // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
- DefaultTransactionStatus status =
- newTransactionStatus(definition, transaction, false , false , debugEnabled, null );
- status.createAndHoldSavepoint();
- return status;
- }
- else {
- // Nested transaction through nested begin and commit/rollback calls.
- // Usually only for JTA: Spring synchronization might get activated here
- // in case of a pre-existing JTA transaction.
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true , newSynchronization, debugEnabled, null );
- }
- }
- // Assumably PROPAGATION_SUPPORTS.
- if (debugEnabled) {
- logger.debug("Participating in existing transaction" );
- }
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, false , newSynchronization, debugEnabled, null );
- }
我们看到这个方法其实很明了,就是什么样的传播途径就创建什么样的transactionstatus,这个方法是在事务开始时被调用的, 拿到我们之前举的例子中来看下,我们就恍然大悟了,原来,如果之前已经创建过事务,那个这个新建的transactionstauts就不应该是属于一个 newTransaction了,所以第3个参数就是false了。
也就是说,在BService的方法出栈要要执行processcommit,但是由于BService的那个TransactionStatus不是一个newTransaction,所以它根本不会触发这个动作:
- else if (status.isNewTransaction()) { //这个判断非常重要,下面会详细讲解这个判断的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- }
也就是说在BService的方法出栈后,事务是不会提交的。这完全符合propragation_required 的模型。
而 在AService的方法出栈后,AService的方法所对应的那个TransactionStatus对象的newTransaction属性是为 true的,即它会触发上面这段代码,进行真正的事务提交。让我们回想一下AService方法入栈之前创建TransactionStatus对象的情 形吧:
newTransactionStatus(definition, transaction , true, newSynchronization, debugEnabled, null);看到第3个参数为true没有。
那么事务该提交了吧,事务的提交我想使用过hibernate的人都知道怎么提交了:
txObject.getSessionHolder().getTransaction().commit();
从当前线程中拿到SessionHolder,再拿到开始事务的那个Transaction 对象,然后再commit事务。在没有用spring 之前,我们经常这么做。呵呵。
好吧,我已经说到了spring 声 明式事务管理的70%到80%的内容了,这70%到80%的内容看上去还是非常容易理解的,如果把这两篇文章认真看过,我相信会有所收获的,剩下的内容需 要靠大家自己去挖掘了,因为另剩下的内容可是需要花费很多时间的,因为牵扯的东西实在是太多了,呵呵。最后祝大家阅读愉快,因为我的文笔实在是让大家的眼 睛受罪了。
http://www.javaeye.com/topic/89072
=================================================
用 Spring 框架指定自定义隔离级别
http://www.ibm.com/developerworks/cn/java/j-isolation/
在 Java EE 应用程序的分布式事务中使用自定义隔离级别 |
级别: 中级 Ricardo Olivieri (roliv@us.ibm.com ), 软件工程师, IBM 2006 年 11 月 20 日 如果您正在构建一个应用程序,该应用程序要求在执行用例时在全局事务中具有自定义隔离级别,您可能已经发现这是一件困难的事,因 为 Java™ Transaction API 并不提供对自定义隔离级别的支持。幸运地是,Spring 框架允许您设计在全局事务中使用自定义隔离级别的 Web 和企业应用程序,但这却不是一件容易的事。在本文中,Ricardo Olivieri 用 7 个详细的步骤演示了这一过程。 许多 Java Enterprise Edition(EE)应用程序在执行用户请求时都会访问多处资源。例如,应用程序也许需要将一条消息放到一个面向消息的中间件队列中,并在相同的事务上 下文中更新数据库行。可以通过使用应用服务器提供的 Java Transaction API(JTA)事务管理器和兼容 XA 的驱动程序连接到数据资源来实现这一任务。但应用程序的需求也许会在执行一个用例时调用全局事务中的自定义隔离级别(custom isolation level) —— JTA 事务管理器并不支持自定义隔离级别。如果正在使用 Spring 框架,出这个原因,如果为 Spring 配置文件中的全局事务指定一个自定义隔离级别,将会抛出一个异常。 本文展示了一种能够 使用 Spring 来指定全局事务中的自定义隔离级别的方法。如果您部署应用程序的应用服务器,允许在定义数据源的位置指定作为数据库访问的隔离级别值,那么该方法都是有效 的。为从本文中获益,您应该熟悉 Spring 框架并理解如何在 Spring 配置文件中定义事务代理及面向方面的 advice。在对应用服务器熟悉的前提下,也假设您熟悉 Java EE 设计模式和全局/分布式事务的概念。 软件应用程序的需求也许做了这样的规定(这里的许多技术超出了本文讨论范围),即在执行一个给定用例的过程中,必须将相同的隔离级别使用到所有的数 据访问中。需求也许还这样规定,在一个用例实现中只要访问了两项或超过两项的外部资源,该应用程序就应该使用全局事务。例如,作为用例实现的一部分,应用 程序也许会查询两个不同的数据库表并将一条消息放到消息队列中。针对这个用例的设计也许需要使用 “已提交读” 隔离级别来执行两个数据库 您可以分别为两个
使用 JTA 事务管理器的新手或只对它了解一点的开发人员也许想要为服务对象(如 清单 1. 使用 JTA 事务管理器的事务代理的错误定义
清单 1 中定义了两个 bean。第一个 bean 的定义指定了应用程序将使用的事务管理器。正如您能看到的那样,这个 bean 依赖于另一个叫做
清单 1 中第二个 bean(称为
出现这个错误是因为 JTA 事务管理器不支持自定义隔离级别。当使用 JTA 事务管理器时,事务代理的 bean 定义会和清单 2 中的类似: 清单 2. 使用 JTA 事务管理器的事务代理的正确定义
请注意,和 清单 1 惟一的区别是,现在所有的隔离级别都被设置为 图 1 中的序列图说明了在执行 图 1. OrderService 实现的 save() 方法的序列图 从 这里 看全图。 在执行
该解决方案是一个由 7 个步骤组成的过程,在此过程中利用了名为
您也许会回想起之前用 Spring 编写数据访问代码的经历,可以轻易地通过将一个
清单 3. JdbcOperationsImpl 实例的定义
第二步是要确保所有的数据访问对象使用
请将
现在, 清单 4. JdbcOperationsImpl 类中 getJdbcTemplate() 方法的实现
在这段代码中, 清单 5. JdbcOperationsImpl 类的构造函数
从清单 6 中可见,只要要求应用程序的上下文返回标识为 清单 6. jdbcTemplate bean 的定义
第三步是用 清单 6 显示的定义更新 Spring 配置文件,并定义 清单 7. IsolationLevelUtil 类的实现
如果您正在思考哪个组件将相应的 清单 8. IsolationLevelAdvice 类的实现
在该应用程序中,每个服务对象实现都需要此类的实例。
第五步是要在 Spring 配置文件中定义这个类的一个 bean 定义,该 bean 将和 清单 9.针对 OrderService 实现的隔离 advice bean 的定义
清单 9 中 bean 的定义显示了
第六步是要指定 清单 10. 针对 OrderService 实现的 AOP 代理 bean 的定义
第七步也是最后的一步是要定义应用程序所需的 清单 11. JdbcTemplate 和数据源对象的定义
图 2 中的类图撷取了这些类中存在的关系,定义这些类是为了实现我所描述过的解决方案: 图 2. 本文解决方案的类图 在 这里 查看全图。 在这个类图中显示的大多数关系并没有定义在 Java 源代码中,而是在 Spring 配置文件中。(这对 Spring 用户来说并不奇怪。)同样,如果将我探讨过的 Spring bean 的定义和该类图中的实体作比较,很容易看出,在 图 2 中被标识为 下载 这些类的完整的源代码,您需要这些类来实现我在本文中演示的解决方案。
如果在技术需求中声明了在执行使用分布式事务的用例过程中应该使用相同的隔离级别,尽管 JTA 事务管理器不支持自定义隔离级别,但您的应用程序能够满足此项需求。本文提供了实现此目标的一种方法,即使用 Spring 的依赖项-注入功能来保持类的解耦。第一眼看去,该实现似乎有点复杂,但您会意识到它很直白且相当简单。在执行用例时的任何时刻访问数据库,它让您在一种 可配置的方式下使用相同的隔离级别。处理业务逻辑和持久性逻辑的 Java 代码并未改变。相反,使用在 Spring 配置文件中的设置,所有 “神奇的事情” 都在运行时发生。这就是该解决方案的设计中的主要优点之一:使实现应用程序业务逻辑和持久性逻辑的类从确保在执行用例过程中使用的相同隔离级别的类和组件 中解耦出来。 作者要感谢 Coraly Romero-Principe,有了她的帮助,这篇文章才可能面世。
学习
|