转载

《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...


9.3.5  事务属性
事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。
Spring提供TransactionDefinition接口默认实现DefaultTransactionDefinition,可以通过该实现类指定这些事务属性。
事务隔离级别:用来解决并发事务时出现的问题,其使用TransactionDefinition中的静态变量来指定:
ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;
ISOLATION_READ_UNCOMMITTED:未提交读;
ISOLATION_READ_COMMITTED:提交读,一般情况下我们使用这个;
ISOLATION_REPEATABLE_READ:可重复读;
ISOLATION_SERIALIZABLE:序列化。
 
可以使用DefaultTransactionDefinition类的setIsolationLevel(TransactionDefinition. ISOLATION_READ_COMMITTED)来指定隔离级别,其中此处表示隔离级别为提交读,也可以使用或setIsolationLevelName(“ISOLATION_READ_COMMITTED”)方式指定,其中参数就是隔离级别静态变量的名字,但不推荐这种方式。

事务传播行为:Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为:
 
Required:必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务,如图9-2和9-3所示;
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-2 Required传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-3 Required传播行为抛出异常情况
在前边示例中就是使用的Required传播行为:
一、在调用userService对象的save方法时,此方法用的是Required传播行为且此时Spring事务管理器发现还没开启逻辑事务,因此Spring管理器觉得开启逻辑事务,
二、在此逻辑事务中调用了addressService对象的save方法,而在save方法中发现同样用的是Required传播行为,因此使用该已经存在的逻辑事务;
三、在返回到addressService对象的save方法,当事务模板类执行完毕,此时提交并关闭事务。
因此userService对象的save方法和addressService的save方法属于同一个物理事务,如果发生回滚,则两者都回滚。
 
 
接下来测试一下该传播行为如何执行吧:
一、正确提交测试,如上一节的测试,在此不再演示;
二、回滚测试,修改AddressServiceImpl的save方法片段:

addressDao.save(address);  
 

addressDao.save(address);  
//抛出异常,将标识当前事务需要回滚  
throw new RuntimeException();  
 
二、修改UserServiceImpl的save方法片段:

addressService.save(user.getAddress());  
 

try {  
    addressService.save(user.getAddress());//将在同一个事务内执行  
} catch (RuntimeException e) {  
}  
   
 
如果该业务方法执行时事务被标记为回滚,则不管在此是否捕获该异常都将发生回滚,因为处于同一逻辑事务。
 
三、修改测试方法片段:

userService.save(user);  
Assert.assertEquals(1, userService.countAll());  
Assert.assertEquals(1, addressService.countAll());  
 
为如下形式:
 

try {  
    userService.save(user);  
    Assert.fail();  
} catch (RuntimeException e) {  
}  
Assert.assertEquals(0, userService.countAll());  
Assert.assertEquals(0, addressService.countAll());  
 
Assert断言中countAll方法都返回0,说明事务回滚了,即说明两个业务方法属于同一个物理事务,即使在userService对象的save方法中将异常捕获,由于addressService对象的save方法抛出异常,即事务管理器将自动标识当前事务为需要回滚。 
 
 
 
RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)如图9-4和9-5所示:
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-4 RequiresNew传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-5 RequiresNew传播行为并抛出异常
接下来测试一个该传播行为如何执行吧:
1、将如下获取事务模板方式

TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);  
 
       替换为如下形式,表示传播行为为RequiresNew:

TransactionTemplate transactionTemplate = TransactionTemplateUtils.getTransactionTemplate(  
        txManager,   
        TransactionDefinition.PROPAGATION_REQUIRES_NEW,   
        TransactionDefinition.ISOLATION_READ_COMMITTED);  
 
2、执行如下测试,发现执行结果是正确的:

userService.save(user);  
Assert.assertEquals(1, userService.countAll());  
Assert.assertEquals(1, addressService.countAll());  
 
3、修改UserServiceImpl的save方法片段

userDao.save(user);
user.getAddress().setUserId(user.getId());  
addressService.save(user.getAddress());  
 
为如下形式,表示userServiceImpl类的save方法将发生回滚,而AddressServiceImpl类的方法由于在抛出异常前执行,将成功提交事务到数据库:
 

userDao.save(user);         
user.getAddress().setUserId(user.getId());  
addressService.save(user.getAddress());  
throw new RuntimeException();  
 
4、修改测试方法片段:

userService.save(user);  
Assert.assertEquals(1, userService.countAll());  
Assert.assertEquals(1, addressService.countAll());  
 
为如下形式:
 

try {  
    userService.save(user);  
    Assert.fail();  
} catch (RuntimeException e) {  
}  
Assert.assertEquals(0, userService.countAll());  
Assert.assertEquals(1, addressService.countAll());  
 
Assert断言中调用userService对象countAll方法返回0,说明该逻辑事务作用域回滚,而调用addressService对象的countAll方法返回1,说明该逻辑事务作用域正确提交。因此这是不正确的行为,因为用户和地址应该是一一对应的,不应该发生这种情况,因此此处正确的传播行为应该是Required。
      
该传播行为执行流程(正确提交情况):
一、当执行userService对象的save方法时,由于传播行为是RequiresNew,因此创建一个新的逻辑事务(物理事务也是不同的);
二、当执行到addressService对象的save方法时,由于传播行为是RequiresNew,因此首先暂停上一个逻辑事务并创建一个新的逻辑事务(物理事务也是不同的);
三、addressService对象的save方法执行完毕后,提交逻辑事务(并提交物理事务)并重新恢复上一个逻辑事务,继续执行userService对象的save方法内的操作;
四、最后userService对象的save方法执行完毕,提交逻辑事务(并提交物理事务);
五、userService对象的save方法和addressService对象的save方法不属于同一个逻辑事务且也不属于同一个物理事务。 
 
 
 
Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行,如图9-6和9-7所示:
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-6 Required+Supports传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-7 Supports+Supports传播行为
 
 
 
 
NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行,如图9-8和9-9所示:
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-8 Required+NotSupported传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-9 Supports+NotSupported传播行为
 
 
 
 
Mandatory:必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException),如图9-10和9-11所示:

《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...

图9-10 Required+Mandatory传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-11 Supports+Mandatory传播行为
 
 
 
 
Never:不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException),如图9-12和9-13所示:

《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...

图9-12 Required+Never传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-13 Supports+Never传播行为
 
 
 
 
 
Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚,如图9-14和9-15所示:
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-14 Required+Nested传播行为
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-15 Nested+Nested传播行为
 
 
 
Nested和RequiresNew的区别:
1、  RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
2、  Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
3、  Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。
使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要我们来开启。
对于事务传播行为我们只演示了Required和RequiresNew,其他传播行为类似,如果对这些事务传播行为不太会使用,请参考chapter9包下的TransactionTest测试类中的testPropagation方法,方法内有详细示例。
 
事务超时:设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;
使用如setTimeout(100)来设置超时时间,如果事务超时将抛出org.springframework.transaction.TransactionTimedOutException异常并将当前事务标记为应该回滚,即超时后事务被自动回滚;
可以使用具体事务管理器实现的defaultTimeout属性设置默认的事务超时时间,如DataSourceTransactionManager. setDefaultTimeout(10)。
 
事务只读:将事务标识为只读,只读事务不修改任何数据;
对于JDBC只是简单的将连接设置为只读模式,对于更新将抛出异常;
而对于一些其他ORM框架有一些优化作用,如在Hibernate中,Spring事务管理器将执行“session.setFlushMode(FlushMode.MANUAL)”即指定Hibernate会话在只读事务模式下不用尝试检测和同步持久对象的状态的更新。
如果使用设置具体事务管理的validateExistingTransaction属性为true(默认false),将确保整个事务传播链都是只读或都不是只读,如图9-16是正确的事务只读设置,而图9-17是错误的事务只读设置:
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-16 正确的事务只读设置
 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
 图9-17 错误的事务只读设置

如图10-17,对于错误的事务只读设置将抛出IllegalTransactionStateException异常,并伴随“Participating transaction with definition [……] is not marked as read-only……”信息,表示参与的事务只读属性设置错误。
 
 大家有没有感觉到编程式实现事务管理是不是很繁琐冗长,重复,而且是侵入式的,因此发展到这Spring决定使用配置方式实现事务管理。
 
 
9.3.6  配置方式实现事务管理
在Spring2.x之前为了解决编程式事务管理的各种不好问题,Spring提出使用配置方式实现事务管理,配置方式利用代理机制实现,即使有TransactionProxyFactoryBean类来为目标类代理事务管理。
 
接下来演示一下具体使用吧:
1、重新定义业务类实现,在业务类中无需显示的事务管理代码:

package cn.javass.spring.chapter9.service.impl;  
//省略import  
public class ConfigAddressServiceImpl implements IAddressService {  
    private IAddressDao addressDao;  
    public void setAddressDao(IAddressDao addressDao) {  
        this.addressDao = addressDao;  
    }  
    @Override  
    public void save(final AddressModel address) {  
        addressDao.save(address);  
    }  
    //countAll方法实现不变  
}  
 

package cn.javass.spring.chapter9.service.impl;  
//省略import  
public class ConfigUserServiceImpl implements IUserService {  
    private IUserDao userDao;  
    private IAddressService addressService;  
    public void setUserDao(IUserDao userDao) {  
        this.userDao = userDao;  
    }  
    public void setAddressService(IAddressService addressService) {  
        this.addressService = addressService;  
    }  
    @Override  
    public void save(final UserModel user) {  
        userDao.save(user);  
        user.getAddress().setUserId(user.getId());  
        addressService.save(user.getAddress());  
    }  
    //countAll方法实现不变  
}  
 
 
从以上业务类中可以看出,没有事务管理的代码,即没有侵入式的代码。
 
2、在chapter9/service/applicationContext-service.xml配置文件中添加如下配置:
2.1、首先添加目标类定义:

<bean id="targetUserService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">  
    <property name="userDao" ref="userDao"/>  
    <property name="addressService" ref="targetAddressService"/>  
</bean>  
<bean id="targetAddressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">  
    <property name="addressDao" ref="addressDao"/>  
</bean>  
 
2.2、配置TransactionProxyFactoryBean类:

<bean id="transactionProxyParent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"  abstract="true">  
    <property name="transactionManager" ref="txManager"/>  
    <property name="transactionAttributes">  
    <props>  
            <prop key="save*">  
                      PROPAGATION_REQUIRED,  
                      ISOLATION_READ_COMMITTED,  
                      timeout_10,  
                      -Exception,  
                      +NoRollBackException  
           </prop>  
           <prop key="*">  
                      PROPAGATION_REQUIRED,  
                      ISOLATION_READ_COMMITTED,  
                      readOnly  
           </prop>  
        </props>  
</property>  
</bean>  
   
TransactionProxyFactoryBean:用于为目标业务类创建代理的Bean;
abstract="true":表示该Bean是抽象的,用于去除重复配置;
transactionManager:事务管理器定义;
transactionAttributes:表示事务属性定义:
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,timeout_10,-Exception,+NoRollBackException:事务属性定义,Required传播行为,提交读隔离级别,事务超时时间为10秒,将对所有Exception异常回滚,而对于抛出NoRollBackException异常将不发生回滚而是提交;
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,readOnly:事务属性定义,Required传播行为,提交读隔离级别,事务是只读的,且只对默认的RuntimeException异常回滚;
<prop key="save*">:表示将代理以save开头的方法,即当执行到该方法时会为该方法根据事务属性配置来开启/关闭事务;
<prop key="*">:表示将代理其他所有方法,但需要注意代理方式,默认是JDK代理,只有public方法能代理;
 
注:事务属性的传播行为和隔离级别使用TransactionDefinition静态变量名指定;事务超时使用“timeout_超时时间”指定,事务只读使用“readOnly”指定,需要回滚的异常使用“-异常”指定,不需要回滚的异常使用“+异常”指定,默认只对RuntimeException异常回滚。
 
需要特别注意“-异常”和“+异常”中“异常”只是真实异常的部分名,内部使用如下方式判断:

//真实抛出的异常.name.indexOf(配置中指定的需要回滚/不回滚的异常名)  
exceptionClass.getName().indexOf(this.exceptionName)  
   
因此异常定义时需要特别注意,配置中定义的异常只是真实异常的部分名。
 
 
2.3、定义代理Bean:

<bean id="proxyUserService" parent="transactionProxyParent">  
    <property name="target" ref="targetUserService"/>  
</bean>  
<bean id="proxyAddressService" parent="transactionProxyParent">  
    <property name="target" ref="targetAddressService"/>  
</bean>  
 
代理Bean通过集成抽象Bean“transactionProxyParent”,并通过target属性设置目标Bean,在实际使用中应该使用该代理Bean。
 
 
3、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testConfigTransaction:
并在testConfigTransaction测试方法内将:
IUserService userService =  
ctx2.getBean("userService", IUserService.class);  
IAddressService addressService =  
ctx2.getBean("addressService", IAddressService.class);  
 
替换为:
IUserService userService =  
ctx2.getBean("proxyUserService ", IUserService.class);  
IAddressService addressService =  
ctx2.getBean("proxyAddressService ", IAddressService.class);  
 

4、执行测试,测试正常通过,说明该方式能正常工作,当调用save方法时将匹配到“<prop key="save*">”定义,而countAll将匹配到“<prop key="save*">”定义,底层代理会应用相应定义中的事务属性来创建或关闭事务。

 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
 
图9-18 代理方式实现事务管理
如图9-18,代理方式实现事务管理只是将硬编码的事务管理代码转移到代理中去由代理实现,在代理中实现事务管理。
 
注:在代理模式下,默认只有通过代理对象调用的方法才能应用相应的事务属性,而在目标方法内的“自我调用”是不会应用相应的事务属性的,即被调用方法不会应用相应的事务属性,而是使用调用方法的事务属性。
 
如图9-19所示,在目标对象targetUserService的save方法内调用事务方法“this.otherTransactionMethod()”将不会应用配置的传播行为RequriesNew,开启新事务,而是使用save方法的已开启事务,如果非要这样使用如下方式实现:
 
1、  修改TransactionProxyFactoryBean配置定义,添加exposeProxy属性为true;
2、  在业务方法内通过代理对象调用相应的事务方放,如 “((IUserService)AopContext.currentProxy()).otherTransactionMethod()”即可应用配置的事务属性。
3、  使用这种方式属于侵入式,不推荐使用,除非必要。

 《跟我学Spring3 》【第九章】 Spring的事务 之 9.3 编程式事务(2) ... ...
图9-19 代理方式下的自我调用
 
配置方式也好麻烦啊,每个业务实现都需要配置一个事务代理,发展到这,Spring想出更好的解决方案,Spring2.0及之后版本提出使用新的“<tx:tags/>”方式配置事务,从而无需为每个业务实现配置一个代理。

本教程是ajava.org会员hellospring的原创作品,转载请注明出处。

作者博客:http://sishuok.com/forum/blogCategory/showByCategory/20/49.html?user_id=2

作者ajava空间:http://ajava.org/space-uid-2358.html

正文到此结束
Loading...