9.3编程式事务
9.3.1编程式事务概述
所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。
Spring框架提供一致的事务抽象,因此对于JDBC还是JTA事务都是采用相同的API进行编程。
- 连接康恩= 空 ;
- UserTransaction的TX = 空 ;
- 尝试 {
- TX = getUserTransaction(); // 1获取事务。
- tx.begin(); // 2开启的JTA事务
- 。CONN =了getDataSource()的getConnection(); // 3获取JDBC。
- // 4。声明SQL
- 字符串SQL = “选择INFORMATION_SCHEMA.SYSTEM_TABLES *” ;
- 数PreparedStatement pstmt = conn.prepareStatement(SQL); // 5预编译SQL。
- 结果集RS = pstmt.executeQuery(); // 6执行SQL
- 流程(RS); // 7处理结果集。
- closeResultSet(RS); // 8释放结果集。
- tx.commit(); // 7提交事务。
- } 赶上 (例外五){
- tx.rollback(); // 8回滚事务。
- 扔 é;
- } 终于 {
- conn.close(); //关闭连接
- }
此处可以看到使用UserTransaction而不是Connection连接进行控制事务,从而对于JDBC事务和JTA事务是采用不同API进行编程控制的,并且JTA和JDBC事务管理的异常也是不一样的。
具体如何使用JTA编程进行事务管理请参考cn.javass.spring.chapter9包下的TranditionalTransactionTest类。
而在Spring中将采用一致的事务抽象进行控制和一致的异常控制,即面向PlatformTransactionManager接口编程来控制事务。
9.3.1春节对编程式事务的支持
春节中的事务分为物理事务和逻辑事务;
- 物理事务:就是底层数据库提供的事务支持,如JDBC或JTA提供的事务;
- 逻辑事务:是Spring管理的事务,不同于物理事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务;
逻辑事务即支持非常低级别的控制,也有高级别解决方案:
- 低级别解决方案:
工具类:使用工具类获取连接(会话)和释放连接(会话),如使用org.springframework.jdbc.datasource包中的 DataSourceUtils 类来获取和释放具有逻辑事务功能的连接。当然对集成第三方ORM框架也提供了类似的工具类,如对Hibernate提供了SessionFactoryUtils工具类,JPA的EntityManagerFactoryUtils等,其他工具类都是使用类似***Utils命名;
- //获取具有春天事务(逻辑事务)管理功能的连接
- DataSourceUtils。的getConnection(数据源数据源)
- //释放具有春天事务(逻辑事务)管理功能的连接
- DataSourceUtils。releaseConnection(CON连接,数据源数据源)
TransactionAwareDataSourceProxy :使用该数据源代理类包装需要Spring事务管理支持的数据源,该包装类必须位于最外层,主要用于遗留项目中可能直接使用数据源获取连接和释放连接支持或希望在Spring中进行混合使用各种持久化框架时使用,其内部实际使用 DataSourceUtils 工具类获取和释放真正连接;
- <! - 使用该方式包装数据源,必须在最外层,targetDataSource知道目标数据源 - >
- <bean的ID = “dataSourceProxy”
- 类 =“org.springframework.jdbc.datasource。
- TransactionAwareDataSourceProxy类“>
- <属性名= “targetDataSource” 参考= “数据源” />
- </豆>
通过如上方式包装数据源后,可以在项目中使用物理事务编码的方式来获得逻辑事务的支持,即支持直接从DataSource获取连接和释放连接,且这些连接自动支持Spring逻辑事务;
- 高级别解决方案:
模板类:使用Spring提供的模板类,如JdbcTemplate、HibernateTemplate和JpaTemplate模板类等,而这些模板类内部其实是使用了低级别解决方案中的工具类来管理连接或会话;
Spring提供两种编程式事务支持:直接使用PlatformTransactionManager实现和使用TransactionTemplate模板类,用于支持逻辑事务管理。
如果采用编程式事务推荐使用TransactionTemplate的模板类和高级别解决方案。
9.3.3使用的PlatformTransactionManager
首先让我们看下如何使用的PlatformTransactionManager实现来进行事务管理:
1 ,数据源定义,此处使用第7 章的配置文件,即“第7章/的applicationContext-resources.xml中“文件。
2 ,事务管理器定义(第9章/的applicationContext-jdbc.xml中):
- <bean的ID = “transactionManager的” 类 = “org.springframework.jdbc.datasource.DataSourceTransactionManager” >
- <属性名= “数据源” 参考= “数据源” />
- </豆>
3, 准备测试环境:
3.1 ,首先准备测试时使用的SQL :
- 包 cn.javass.spring.chapter9;
- //省略进口
- 公共 类 TransactionTest {
- // ID自增主键从0开始
- 私人 静态 最后 弦乐CREATE_TABLE_SQL = “创建表测试” +
- “(ID INT生成的DEFAULT AS IDENTITY PRIMARY KEY,” +
- “名VARCHAR(100))” ;
- 私人 静态 最后 弦乐DROP_TABLE_SQL = “删除表测试” ;
- 私人 静态 最后 弦乐INSERT_SQL = “插入测试(名称)值(?)” ;
- 私人 静态 最后 弦乐COUNT_SQL = “从测试SELECT COUNT(*)” ;
- ......
- }
3.2 ,初始化春季容器
- 包 cn.javass.spring.chapter9;
- //省略进口
- 公共 类 TransactionTest {
- 私人 静态 的ApplicationContext CTX;
- 私人 静态 的PlatformTransactionManager txManager;
- 私人 静态 数据源数据源;
- 私人 静态 的JdbcTemplate JdbcTemplate的;
- ......
- @BeforeClass
- 公共 静态 无效 setUpClass(){
- 的String [] configLocations = 新 的String [] {
- “类路径:第7章/的applicationContext-resources.xml中”,
- “类路径:第9章/的applicationContext-jdbc.xml中” };
- CTX = 新 的ClassPathXmlApplicationContext(configLocations);
- txManager = ctx.getBean(PlatformTransactionManager的类);
- 数据源= ctx.getBean(数据源。类);
- 的JdbcTemplate = 新 的JdbcTemplate(数据源);
- }
- ......
- }
3.3 ,使用高级别方案的JdbcTemplate 来进行事务管理器测试:
- @Test
- 公共 无效 testPlatformTransactionManager(){
- DefaultTransactionDefinition高清= 新 DefaultTransactionDefinition();
- def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- TransactionStatus对象状态= txManager.getTransaction(DEF);
- jdbcTemplate.execute(CREATE_TABLE_SQL);
- 尝试 {
- jdbcTemplate.update(INSERT_SQL, “ 测试”);
- txManager.commit(状态);
- } 赶上 (RuntimeException的E){
- txManager.rollback(状态);
- }
- jdbcTemplate.execute(DROP_TABLE_SQL);
- }
- DefaultTransactionDefinition :事务定义,定义如隔离级别、传播行为等,即在本示例中隔离级别为ISOLATION_READ_COMMITTED(提交读),传播行为为PROPAGATION_REQUIRED(必须有事务支持,即如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中)。
- TransactionStatus :事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务;
- JdbcTemplate:通过JdbcTemplate对象执行相应的SQL操作,且自动享受到事务支持,注意事务是线程绑定的,因此事务管理器可以运行在多线程环境;
- txManager.commit(状态):提交状态对象绑定的事务;
- txManager.rollback(状态):当遇到异常时回滚状态对象绑定的事务。
3.4 ,使用低级别解决方案来进行事务管理器测试:
- @Test
- 公共 无效 testPlatformTransactionManagerForLowLevel1(){
- DefaultTransactionDefinition高清= 新 DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- TransactionStatus对象状态= txManager.getTransaction(DEF);
- 连接康恩= DataSourceUtils.getConnection(数据源);
- 尝试 {
- conn.prepareStatement(CREATE_TABLE_SQL).execute();
- 数PreparedStatement pstmt = conn.prepareStatement(INSERT_SQL);
- pstmt.setString(1, “测试”);
- pstmt.execute();
- conn.prepareStatement(DROP_TABLE_SQL).execute();
- txManager.commit(状态);
- } 赶上 (例外五){
- status.setRollbackOnly();
- txManager.rollback(状态);
- } 终于 {
- DataSourceUtils.releaseConnection(CONN,数据源);
- }
- }
低级别方案中使用DataSourceUtils获取和释放连接,使用txManager开管理事务,而且面向JDBC编程,比起模板类方式来繁琐和复杂的多,因此不推荐使用该方式。在此就不介绍数据源代理类使用了,需要请参考platformTransactionManagerForLowLevelTest2测试方法。
到此事务管理是不是还很繁琐?必须手工提交或回滚事务,有没有更好的解决方案呢?Spring提供了TransactionTemplate模板类来简化事务管理。
9.3.4使用TransactionTemplate的
TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate的模板类使用的回调接口:
- TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus对象状态)”方法来定义需要事务管理的操作代码;
- TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“无效doInTransactionWithoutResult(TransactionStatus对象状态)”便利接口用于方便那些不需要返回值的事务操作代码。
1 ,接下来演示一下TransactionTemplate的模板类如何使用:
- @Test
- 公共 无效 testTransactionTemplate(){ //位于TransactionTest类中
- jdbcTemplate.execute(CREATE_TABLE_SQL);
- TransactionTemplate的TransactionTemplate的= 新 TransactionTemplate的(txManager);
- transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- transactionTemplate.execute(新 TransactionCallbackWithoutResult(){
- @覆盖
- 保护 无效 doInTransactionWithoutResult(TransactionStatus对象的状态){
- jdbcTemplate.update(INSERT_SQL, “ 测试”);
- }});
- jdbcTemplate.execute(DROP_TABLE_SQL);
- }
- TransactionTemplate的 :通过新TransactionTemplate(txManager)创建事务模板类,其中构造器参数为PlatformTransactionManager实现,并通过其相应方法设置事务定义,如事务隔离级别、传播行为等,此处未指定传播行为,其默认为PROPAGATION_REQUIRED;
- TransactionCallbackWithoutResult:此处使用不带返回的回调实现,其doInTransactionWithoutResult方法实现中定义了需要事务管理的操作;
- transactionTemplate.execute():通过该方法执行需要事务管理的回调。
这样是不是简单多了,没有事务管理代码,而是由模板类来完成事务管理。
注:对于抛出Exception 类型的异常且需要回滚时,需要捕获异常并通过调用status 对象的setRollbackOnly() 方法告知事务管理器当前事务需要回滚,如下所示:
- 尝试 {
- //业务操作
- } 赶上 (例外五){ //可使用具体业务异常代替
- status.setRollbackOnly();
- }
2 ,前边已经演示了JDBC 事务管理,接下来演示一下JTA 分布式事务管理:
- @Test
- 公共 无效 testJtaTransactionTemplate(){
- 的String [] configLocations = 新 的String [] {
- “类路径:第9章/的applicationContext-JTA-derby.xml” };
- CTX = 新 的ClassPathXmlApplicationContext(configLocations);
- 最后 的PlatformTransactionManager jtaTXManager = ctx.getBean(PlatformTransactionManager的。类);
- 最终的 数据源derbyDataSource1 = ctx.getBean(“dataSource1” ,数据源。类);
- 最终的 数据源derbyDataSource2 = ctx.getBean(“dataSource2” ,数据源。类);
- 最后 的JdbcTemplate jdbcTemplate1 = 新 的JdbcTemplate(derbyDataSource1);
- 最后 的JdbcTemplate jdbcTemplate2 = 新 的JdbcTemplate(derbyDataSource2);
- TransactionTemplate的TransactionTemplate的= 新 TransactionTemplate的(jtaTXManager);
- transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- jdbcTemplate1.update(CREATE_TABLE_SQL);
- INT originalCount = jdbcTemplate1.queryForInt(COUNT_SQL);
- 尝试 {
- transactionTemplate.execute(新 TransactionCallbackWithoutResult(){
- @覆盖
- 保护 无效 doInTransactionWithoutResult(TransactionStatus对象的状态){
- jdbcTemplate1.update(INSERT_SQL, “ 测试”);
- //因为数据库2没有创建数据库表因此会回滚事务
- jdbcTemplate2.update(INSERT_SQL, “ 测试”);
- }});
- } 赶上 (RuntimeException的E){
- INT 计数= jdbcTemplate1.queryForInt(COUNT_SQL);
- Assert.assertEquals(originalCount,计数);
- }
- jdbcTemplate1.update(DROP_TABLE_SQL);
- }
- 配置文件:使用此前定义的第九章/的applicationContext-JTA-derby.xml;
- jtaTXManager:JTA事务管理器;
- derbyDataSource1 和derbyDataSource2:德比数据源1和德比数据源2;
- jdbcTemplate1 和jdbcTemplate2:分别使用derbyDataSource1和derbyDataSource2构造的JDBC模板类;
- transactionTemplate:使用jtaTXManager事务管理器的事务管理模板类,其隔离级别为提交读,传播行为默认为PROPAGATION_REQUIRED(必须有事务支持,即如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中);
- jdbcTemplate1.update(CREATE_TABLE_SQL):此处只有derbyDataSource1所代表的数据库创建了“test”表,而derbyDataSource2所代表的数据库没有此表;
- TransactionCallbackWithoutResult:在此接口实现中定义了需要事务支持的操作:
jdbcTemplate1.update(INSERT_SQL,“测试”) :表示向数据库1中的测试表中插入数据;
jdbcTemplate2.update(INSERT_SQL, "test"):表示向数据库2中的test表中插入数据,但数据库2没有此表将抛出异常,且JTA分布式事务将回滚;
- Assert.assertEquals(originalCount, count):用来验证事务是否回滚,验证结果返回为true,说明分布式事务回滚了。
到此我们已经会使用PlatformTransactionManager和TransactionTemplate进行简单事务处理了,那如何应用到实际项目中去呢?接下来让我们看下如何在实际项目中应用Spring管理事务。
接下来看一下如何将Spring管理事务应用到实际项目中,为简化演示,此处只定义最简单的模型对象和不完整的Dao层接口和Service层接口:
1, 首先定义项目中的模型对象,本示例使用用户模型和用户地址模型:
模型对象一般放在项目中的模型包里。
- 包 cn.javass.spring.chapter9.model;
- 公共 类 的usermodel {
- 私人 INT ID;
- 私人 字符串名称;
- 私人 AddressModel地址;
- //省略的getter和setter方法
- }
- 包 cn.javass.spring.chapter9.model;
- 公共 类 AddressModel {
- 私人 INT ID;
- 私人 字符串省;
- 私人 字符串城市;
- privateString街道;
- 私人 INT 用户id;
- //省略的getter和setter方法
- }
2.1 ,定义道层接口:
- 包 cn.javass.spring.chapter9.service;
- 进口 cn.javass.spring.chapter9.model.UserModel;
- 公共 接口 IUserService {
- 公共 无效 保存(用户的usermodel);
- 公共 INT countAll();
- }
- 包 cn.javass.spring.chapter9.service;
- 进口 cn.javass.spring.chapter9.model.AddressModel;
- 公共 接口 IAddressService {
- 公共 无效 保存(AddressModel地址);
- 公共 INT countAll();
- }
2.2,定义道层实现:
- 包 cn.javass.spring.chapter9.dao.jdbc;
- //省略进口,注意模型要引用章包里的
- 公共 类 UserJdbcDaoImpl 扩展 NamedParameterJdbcDaoSupport 实现 IUserDao {
- 私人 最终 字符串INSERT_SQL = “插入到用户(名)值(:名称)” ;
- 私人 最终 字符串COUNT_ALL_SQL = “从用户的SELECT COUNT(*)” ;
- @覆盖
- 公共 无效 保存(用户的usermodel){
- 匙扣generatedKeyHolder = 新 GeneratedKeyHolder();
- 一个SqlParameterSource paramSource = 新 BeanPropertySqlParameterSource(用户);
- 。getNamedParameterJdbcTemplate()更新(INSERT_SQL,paramSource,generatedKeyHolder);
- user.setId(generatedKeyHolder.getKey()的intValue());
- }
- @覆盖
- 公共 INT countAll(){
- 返回 getJdbcTemplate()queryForInt(COUNT_ALL_SQL)。
- }
- }
- 包 cn.javass.spring.chapter9.dao.jdbc;
- //省略进口,注意模型要引用章包里的
- 公共 类 AddressJdbcDaoImpl 扩展 NamedParameterJdbcDaoSupport 实现 IAddressDao {
- 私人 最终 字符串INSERT_SQL = “插入地址(省,市,街道,user_ID的)” + “值(:省:市:街:用户id)” ;
- 私人 最终 字符串COUNT_ALL_SQL = “从地址SELECT COUNT(*)” ;
- @覆盖
- 公共 无效 保存(AddressModel地址){
- 匙扣generatedKeyHolder = 新 GeneratedKeyHolder();
- 一个SqlParameterSource paramSource = 新 BeanPropertySqlParameterSource(地址);
- 。getNamedParameterJdbcTemplate()更新(INSERT_SQL,paramSource,generatedKeyHolder);
- address.setId(generatedKeyHolder.getKey()的intValue());
- }
- @覆盖
- 公共 INT countAll(){
- 返回 getJdbcTemplate()queryForInt(COUNT_ALL_SQL)。
- }
- }
3.1 ,定义服务层接口,一般使用“我×××服务“命名:
- 包 cn.javass.spring.chapter9.service;
- 进口 cn.javass.spring.chapter9.model.UserModel;
- 公共 接口 IUserService {
- 公共 无效 保存(用户的usermodel);
- 公共 INT countAll();
- }
- 包 cn.javass.spring.chapter9.service;
- 进口 cn.javass.spring.chapter9.model.AddressModel;
- 公共 接口 IAddressService {
- 公共 无效 保存(AddressModel地址);
- 公共 INT countAll();
- }
3.2 ,定义服务层实现,一般使用“×××ServiceImpl “或”×××服务“命名:
- 包 cn.javass.spring.chapter9.service.impl;
- //省略进口,注意模型要引用章包里的
- 公共 类 AddressServiceImpl 实现 IAddressService {
- 私人 IAddressDao addressDao;
- 私人 PlatformTransactionManager的txManager;
- 公共 无效 setAddressDao(IAddressDao addressDao){
- 这 .addressDao = addressDao;
- }
- 公共 无效 setTxManager(PlatformTransactionManager的txManager){
- 这 .txManager = txManager;
- }
- @覆盖
- 公共 无效 保存(最终 AddressModel地址){
- TransactionTemplate的TransactionTemplate的= TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
- transactionTemplate.execute(新 TransactionCallbackWithoutResult(){
- @覆盖
- 保护 无效 doInTransactionWithoutResult(TransactionStatus对象的状态){
- addressDao.save(地址);
- }
- });
- }
- @覆盖
- 公共 INT countAll(){
- 返回 addressDao.countAll();
- }
- }
- 包 cn.javass.spring.chapter9.service.impl;
- //省略进口,注意模型要引用章包里的
- 公共 类 UserServiceImpl 实现 IUserService {
- 私人 IUserDao userDAO的;
- 私人 IAddressService addressService;
- 私人 PlatformTransactionManager的txManager;
- 公共 无效 setUserDao(IUserDao userDAO的){
- 这 .userDao = UserDAO的;
- }
- 公共 无效 setTxManager(PlatformTransactionManager的txManager){
- 这 .txManager = txManager;
- }
- 公共 无效 setAddressService(IAddressService addressService){
- 这 .addressService = addressService;
- }
- @覆盖
- 公共 无效 保存(最终 用户的usermodel){
- TransactionTemplate的TransactionTemplate的=
- TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
- transactionTemplate.execute(新 TransactionCallbackWithoutResult(){
- @覆盖
- 保护 无效 doInTransactionWithoutResult(TransactionStatus对象的状态){
- userDao.save(用户);
- 。user.getAddress()setUserId(user.getId());
- addressService.save(user.getAddress());
- }
- });
- }
- @覆盖
- 公共 INT countAll(){
- 返回 userDao.countAll();
- }
- }
Service实现中需要Spring事务管理的部分应该使用TransactionTemplate模板类来包装执行。
4 、定义TransactionTemplateUtils ,用于简化获取TransactionTemplate 模板类,工具类一般放在util 包中:
- 包 cn.javass.spring.chapter9.util;
- //省略进口
- 公共 类 TransactionTemplateUtils {
- 公共 静态 TransactionTemplate的getTransactionTemplate(
- PlatformTransactionManager的txManager,
- INT propagationBehavior,
- INT 的IsolationLevel){
- TransactionTemplate的TransactionTemplate的= 新 TransactionTemplate的(txManager);
- transactionTemplate.setPropagationBehavior(propagationBehavior);
- transactionTemplate.setIsolationLevel(的IsolationLevel);
- 返回 TransactionTemplate的;
- }
- 公共 静态 TransactionTemplate的getDefaultTransactionTemplate(PlatformTransactionManager的txManager){
- 返回 getTransactionTemplate(
- txManager,
- TransactionDefinition.PROPAGATION_REQUIRED,
- TransactionDefinition.ISOLATION_READ_COMMITTED);
- }
- }
getDefaultTransactionTemplate用于获取传播行为为PROPAGATION_REQUIRED,隔离级别为ISOLATION_READ_COMMITTED的模板类。
5 ,数据源配置定义,此处使用第7 章的配置文件,即“第7章/的applicationContext-resources.xml中“文件。
6 ,道层配置定义(第9章/道/的applicationContext-jdbc.xml中):
- <bean的ID = “txManager” 级 = “org.springframework.jdbc.datasource.DataSourceTransactionManager” >
- <属性名= “数据源” 参考= “数据源” />
- </豆>
- <bean的ID = “AbstractDao的” 抽象 = “真” >
- <属性名= “数据源” 参考= “数据源” />
- </豆>
- <bean的ID = “userDAO的” 类 = “cn.javass.spring.chapter9.dao.jdbc.UserJdbcDaoImpl” 父= “AbstractDao的” />
- <bean的ID = “addressDao” 级 = “cn.javass.spring.chapter9.dao.jdbc.AddressJdbcDaoImpl” 父= “AbstractDao的” />
7 ,服务层配置定义(第9章/服务/的applicationContext-service.xml中):
- <bean的ID = “UserService的” 类 = “cn.javass.spring.chapter9.service.impl.UserServiceImpl” >
- <属性名= “userDAO的” 参考= “userDAO的” />
- <属性名= “txManager” 参考= “txManager” />
- <属性名= “addressService” 参考= “addressService” />
- </豆>
- <bean的ID = “addressService” 级 = “cn.javass.spring.chapter9.service.impl.AddressServiceImpl” >
- <属性名= “addressDao” 参考= “addressDao” />
- <属性名= “txManager” 参考= “txManager” />
- </豆>
8 ,准备测试需要的表创建语句,在TransactionTest 测试类中添加如下静态变量:
- 私人 静态 最后 弦乐CREATE_USER_TABLE_SQL =
- “创建表的用户” +
- “(ID INT生成的DEFAULT AS IDENTITY PRIMARY KEY,” +
- “名VARCHAR(100))” ;
- 私人 静态 最后 弦乐DROP_USER_TABLE_SQL = “删除表的用户” ;
- 私人 静态 最后 弦乐CREATE_ADDRESS_TABLE_SQL =
- “创建表的地址” +
- “(ID INT生成的DEFAULT AS IDENTITY PRIMARY KEY,” +
- “省VARCHAR(100),城市为varchar(100),街道为varchar(100),user_ID的INT)” ;
- 私人 静态 最后 弦乐DROP_ADDRESS_TABLE_SQL = “删除表的地址” ;
9, 测试一下吧:
- @Test
- 公共 无效 testServiceTransaction(){
- 的String [] configLocations = 新 的String [] {
- “类路径:第7章/的applicationContext-resources.xml中”,
- “类路径:第9章/道/的applicationContext-jdbc.xml中”,
- “类路径:第9章/服务/的applicationContext-service.xml的” };
- ApplicationContext的ctx2 = 新 的ClassPathXmlApplicationContext(configLocations);
- 数据源dataSource2 = ctx2.getBean(数据源。类);
- JdbcTemplate的jdbcTemplate2 = 新 的JdbcTemplate(dataSource2);
- jdbcTemplate2.update(CREATE_USER_TABLE_SQL);
- jdbcTemplate2.update(CREATE_ADDRESS_TABLE_SQL);
- IUserService UserService的= ctx2.getBean(“UserService的”,IUserService。类);
- IAddressService addressService = ctx2.getBean(“addressService”,IAddressService。类);
- 的usermodel用户= createDefaultUserModel();
- userService.save(用户);
- Assert.assertEquals(1,userService.countAll());
- Assert.assertEquals(1,addressService.countAll());
- jdbcTemplate2.update(DROP_USER_TABLE_SQL);
- jdbcTemplate2.update(DROP_ADDRESS_TABLE_SQL);
- }
- 私人 的usermodel createDefaultUserModel(){
- 用户的usermodel = 新 的usermodel();
- user.setName( “ 测试”);
- AddressModel地址= 新 AddressModel();
- address.setProvince( “ 北京”);
- address.setCity( “ 北京”);
- address.setStreet( “ 海淀”);
- user.setAddress(地址);
- 返回 用户;
- }
从Spring容器中获取Service层对象,调用Service层对象持久化对象,大家有没有注意到Spring事务全部在Service层定义,为什么会在Service层定义,而不是Dao层定义呢?这是因为在服务层可能牵扯到业务逻辑,而每个业务逻辑可能调用多个Dao层方法,为保证这些操作的原子性,必须在Service层定义事务。
还有大家有没有注意到如果Service层的事务管理相当令人头疼,而且是侵入式的,有没有办法消除这些冗长的事务管理代码呢?这就需要Spring声明式事务支持,下一节将介绍无侵入式的声明式事务。
可能大家对事务定义中的各种属性有点困惑,如传播行为到底干什么用的?接下来将详细讲解一下事务属性。
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所示;
图9-2所需的传播行为
图9-3所需的传播行为抛出异常情况
在前边示例中就是使用的要求传播行为:
一、在调用userService对象的save方法时,此方法用的是Required传播行为且此时Spring事务管理器发现还没开启逻辑事务,因此Spring管理器觉得开启逻辑事务,
二、在此逻辑事务中调用了addressService对象的save方法,而在save方法中发现同样用的是Required传播行为,因此使用该已经存在的逻辑事务;
三、在返回到addressService对象的save方法,当事务模板类执行完毕,此时提交并关闭事务。
因此userService对象的save方法和addressService的save方法属于同一个物理事务,如果发生回滚,则两者都回滚。
接下来测试一下该传播行为如何执行吧:
一,正确提交测试,如上一节的测试,在此不再演示;
二,回滚测试,修改AddressServiceImpl的保存方法片段:
- addressDao.save(地址);
为
- addressDao.save(地址);
- //抛出异常,将标识当前事务需要回滚
- 抛出 新 的RuntimeException();
二,修改UserServiceImpl的保存方法片段:
- addressService.save(user.getAddress());
为
- 尝试 {
- addressService.save(user.getAddress()); //将在同一个事务内执行
- } 赶上 (RuntimeException的E){
- }
如果该业务方法执行时事务被标记为回滚,则不管在此是否捕获该异常都将发生回滚,因为处于同一逻辑事务。
三,修改测试方法片段:
- userService.save(用户);
- Assert.assertEquals(1,userService.countAll());
- Assert.assertEquals(1,addressService.countAll());
为如下形式:
- 尝试 {
- userService.save(用户);
- Assert.fail();
- } 赶上 (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所示:
图9-4 RequiresNew传播行为
图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(用户);
- Assert.assertEquals(1,userService.countAll());
- Assert.assertEquals(1,addressService.countAll());
3,修改UserServiceImpl的保存方法片段
- userDao.save(用户);
- 。user.getAddress()setUserId(user.getId());
- addressService.save(user.getAddress());
为如下形式,表示userServiceImpl类的save方法将发生回滚,而AddressServiceImpl类的方法由于在抛出异常前执行,将成功提交事务到数据库:
- userDao.save(用户);
- 。user.getAddress()setUserId(user.getId());
- addressService.save(user.getAddress());
- 抛出 新 的RuntimeException();
4,修改测试方法片段:
- userService.save(用户);
- Assert.assertEquals(1,userService.countAll());
- Assert.assertEquals(1,addressService.countAll());
为如下形式:
- 尝试 {
- userService.save(用户);
- Assert.fail();
- } 赶上 (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所示:
图9-6所需+支持传播行为
图9-7支架+支持传播行为
NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行,如图9-8和9-9所示:
图9-8所需的+ NotSupported传播行为
图9-9支架+ NotSupported传播行为
Mandatory:必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException),如图9-10和9-11所示:
图9-10必需+强制性传播行为
图9-11支架+强制性传播行为
Never:不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException),如图9-12和9-13所示:
图9-12所需的+决不传播行为
图9-13支持+决不传播行为
Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚,如图9-14和9-15所示:
图9-14所需的+嵌套的传播行为
图9-15嵌套+嵌套的传播行为
嵌套和RequiresNew 的区别:
1,RequiresNew每次都创建新的独立的物理事务,而嵌套只有一个物理事务;
2,RequiresNew由于都是全新的事务,所以之间是无关联的;
3,嵌套使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。
使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要我们来开启。
对于事务传播行为我们只演示了Required和RequiresNew,其他传播行为类似,如果对这些事务传播行为不太会使用,请参考chapter9包下的TransactionTest测试类中的testPropagation方法,方法内有详细示例。
- 事务超时:设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;
使用如setTimeout(100)来设置超时时间,如果事务超时将抛出org.springframework.transaction.TransactionTimedOutException异常并将当前事务标记为应该回滚,即超时后事务被自动回滚;
setDefaultTimeout(10)。
- 事务只读:将事务标识为只读,只读事务不修改任何数据;
对于JDBC只是简单的将连接设置为只读模式,对于更新将抛出异常;
而对于一些其他ORM框架有一些优化作用,如在Hibernate中,Spring事务管理器将执行“session.setFlushMode(FlushMode.MANUAL)”即指定Hibernate会话在只读事务模式下不用尝试检测和同步持久对象的状态的更新。
如果使用设置具体事务管理的validateExistingTransaction属性为true(默认false),将确保整个事务传播链都是只读或都不是只读,如图9-16是正确的事务只读设置,而图9-17是错误的事务只读设置:
图9-16正确的事务只读设置
图9-17错误的事务只读设置
如图10-17,对于错误的事务只读设置将抛出IllegalTransactionStateException异常,并伴随“Participating交易带的定义[......]没有被标记为只读......“信息,表示参与的事务只读属性设置错误。
大家有没有感觉到编程式实现事务管理是不是很繁琐冗长,重复,而且是侵入式的,因此发展到这Spring决定使用配置方式实现事务管理。
9.3.6 配置方式实现事务管理
在Spring2.x之前为了解决编程式事务管理的各种不好问题,Spring提出使用配置方式实现事务管理,配置方式利用代理机制实现,即使有TransactionProxyFactoryBean类来为目标类代理事务管理。
接下来演示一下具体使用吧:
1 ,重新定义业务类实现,在业务类中无需显示的事务管理代码:
- 包 cn.javass.spring.chapter9.service.impl;
- //省略进口
- 公共 类 ConfigAddressServiceImpl 实现 IAddressService {
- 私人 IAddressDao addressDao;
- 公共 无效 setAddressDao(IAddressDao addressDao){
- 这 .addressDao = addressDao;
- }
- @覆盖
- 公共 无效 保存(最终 AddressModel地址){
- addressDao.save(地址);
- }
- // countAll方法实现不变
- }
- 包 cn.javass.spring.chapter9.service.impl;
- //省略进口
- 公共 类 ConfigUserServiceImpl 实现 IUserService {
- 私人 IUserDao userDAO的;
- 私人 IAddressService addressService;
- 公共 无效 setUserDao(IUserDao userDAO的){
- 这 .userDao = UserDAO的;
- }
- 公共 无效 setAddressService(IAddressService addressService){
- 这 .addressService = addressService;
- }
- @覆盖
- 公共 无效 保存(最终 用户的usermodel){
- userDao.save(用户);
- 。user.getAddress()setUserId(user.getId());
- addressService.save(user.getAddress());
- }
- // countAll方法实现不变
- }
从以上业务类中可以看出,没有事务管理的代码,即没有侵入式的代码。
2,在第9章/服务/的applicationContext-service.xml的配置文件中添加如下配置:
2.1,首先添加目标类定义:
- <bean的ID = “targetUserService” 级 = “cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl” >
- <属性名= “userDAO的” 参考= “userDAO的” />
- <属性名= “addressService” 参考= “targetAddressService” />
- </豆>
- <bean的ID = “targetAddressService” 级 = “cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl” >
- <属性名= “addressDao” 参考= “addressDao” />
- </豆>
2.2,配置TransactionProxyFactoryBean的类:
- <豆id= "transactionProxyParent" class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract = "true" >
- <属性名= “transactionManager的” 参考= “txManager” />
- <属性名= “transactionAttributes” >
- <道具>
- <支撑键= “拯救*” >
- PROPAGATION_REQUIRED,
- ISOLATION_READ_COMMITTED,
- timeout_10,
- -Exception,
- + NoRollBackException
- </道具>
- <支撑键= “*” >
- PROPAGATION_REQUIRED,
- ISOLATION_READ_COMMITTED,
- readOnly的
- </道具>
- </道具>
- </物业>
- </豆>
- TransactionProxyFactoryBean的:用于为目标业务类创建代理的豆;
- 抽象=“真” :表示该Bean的是抽象的,用于去除重复配置;
- transactionManager的:事务管理器定义;
- transactionAttributes :表示事务属性定义:
- PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,timeout_10,-Exception,+NoRollBackException :事务属性定义,Required传播行为,提交读隔离级别,事务超时时间为10秒,将对所有Exception异常回滚,而对于抛出NoRollBackException异常将不发生回滚而是提交;
- PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,readOnly :事务属性定义,Required传播行为,提交读隔离级别,事务是只读的,且只对默认的RuntimeException异常回滚;
- <道具key="save*"> :表示将代理以save开头的方法,即当执行到该方法时会为该方法根据事务属性配置来开启/关闭事务;
- <道具key="*"> :表示将代理其他所有方法,但需要注意代理方式,默认是JDK代理,只有public方法能代理;
注:事务属性的传播行为和隔离级别使用TransactionDefinition静态变量名指定;事务超时使用“timeout_超时时间”指定,事务只读使用“readOnly”指定,需要回滚的异常使用“-异常”指定,不需要回滚的异常使用“+异常”指定,默认只对RuntimeException异常回滚。
需要特别注意“-异常”和“+异常”中“异常”只是真实异常的部分名,内部使用如下方式判断:
- //真实抛出的异常.name.indexOf(配置中指定的需要回滚/不回滚的异常名)
- exceptionClass.getName()的indexOf(此 .exceptionName)
因此异常定义时需要特别注意,配置中定义的异常只是真实异常的部分名。
2.3 ,定义代理豆:
- <bean的ID = “proxyUserService” 父= “transactionProxyParent” >
- <属性名= “目标” 参考= “targetUserService” />
- </豆>
- <bean的ID = “proxyAddressService” 父= “transactionProxyParent” >
- <属性名= “目标” 参考= “targetAddressService” />
- </豆>
代理Bean通过集成抽象Bean“transactionProxyParent”,并通过target属性设置目标Bean,在实际使用中应该使用该代理Bean。
3 ,修改测试方法并测试该配置方式是否好用:
将TransactionTest类的testServiceTransaction测试方法拷贝一份命名为testConfigTransaction:
并在testConfigTransaction测试方法内将:
- IUserService UserService的=
- ctx2.getBean(“UserService的”,IUserService。类);
- IAddressService addressService =
- ctx2.getBean(“addressService”,IAddressService。类);
替换为:
- IUserService UserService的=
- ctx2.getBean(“proxyUserService”,IUserService。类);
- IAddressService addressService =
- ctx2.getBean(“proxyAddressService”,IAddressService。类);
4 、执行测试,测试正常通过,说明该方式能正常工作,当调用save方法时将匹配到“ <prop关键=“救*”> “定义,而countAll将匹配到” <托key="save*"> ”定义,底层代理会应用相应定义中的事务属性来创建或关闭事务。
图9-18代理方式实现事务管理
如图9-18,代理方式实现事务管理只是将硬编码的事务管理代码转移到代理中去由代理实现,在代理中实现事务管理。
注:在代理模式下,默认只有通过代理对象调用的方法才能应用相应的事务属性,而在目标方法内的“自我调用”是不会应用相应的事务属性的,即被调用方法不会应用相应的事务属性,而是使用调用方法的事务属性。
如图9-19所示,在目标对象targetUserService的save方法内调用事务方法“this.otherTransactionMethod()”将不会应用配置的传播行为RequriesNew,开启新事务,而是使用save方法的已开启事务,如果非要这样使用如下方式实现:
1,修改TransactionProxyFactoryBean的配置定义,添加exposeProxy属性为真;
2,在业务方法内通过代理对象调用相应的事务方放,如“((IUserService)AopContext.currentProxy()).otherTransactionMethod() ”即可应用配置的事务属性。
3,使用这种方式属于侵入式,不推荐使用,除非必要。
图9-19代理方式下的自我调用
配置方式也好麻烦啊,每个业务实现都需要配置一个事务代理,发展到这,Spring想出更好的解决方案,Spring2.0及之后版本提出使用新的“<tx:tags/>”方式配置事务,从而无需为每个业务实现配置一个代理。