9.3 編程式事務
9.3.1 編程式事務概述
所謂編程式事務指的是經過編碼方式實現事務,即相似於JDBC編程實現事務管理。javascript
Spring框架提供一致的事務抽象,所以對於JDBC仍是JTA事務都是採用相同的API進行編程。html
java代碼:Java代碼java
- Connection conn = null;
- UserTransaction tx = null;
- try {
- tx = getUserTransaction(); //1.獲取事務
- tx.begin(); //2.開啓JTA事務
- conn = getDataSource().getConnection(); //3.獲取JDBC
- //4.聲明SQL
- String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES";
- PreparedStatement pstmt = conn.prepareStatement(sql);//5.預編譯SQL
- ResultSet rs = pstmt.executeQuery(); //6.執行SQL
- process(rs); //7.處理結果集
- closeResultSet(rs); //8.釋放結果集
- tx.commit(); //7.提交事務
- } catch (Exception e) {
- tx.rollback(); //8.回滾事務
- throw e;
- } finally {
- conn.close(); //關閉鏈接
- }
此處能夠看到使用UserTransaction而不是Connection鏈接進行控制事務,從而對於JDBC事務和JTA事務是採用不一樣API進行編程控制的,而且JTA和JDBC事務管理的異常也是不同的。spring
具體如何使用JTA編程進行事務管理請參考cn.javass.spring.chapter9包下的TranditionalTransactionTest類。sql
而在Spring中將採用一致的事務抽象進行控制和一致的異常控制,即面向PlatformTransactionManager接口編程來控制事務。數據庫
9.3.1 Spring對編程式事務的支持
Spring中的事務分爲物理事務和邏輯事務;編程
- 物理事務:就是底層數據庫提供的事務支持,如JDBC或JTA提供的事務;
- 邏輯事務:是Spring管理的事務,不一樣於物理事務,邏輯事務提供更豐富的控制,並且若是想獲得Spring事務管理的好處,必須使用邏輯事務,所以在Spring中若是沒特別強調通常就是邏輯事務;
邏輯事務即支持很是低級別的控制,也有高級別解決方案:session
工具類:使用工具類獲取鏈接(會話)和釋放鏈接(會話),如使用org.springframework.jdbc.datasource包中的 DataSourceUtils 類來獲取和釋放具備邏輯事務功能的鏈接。固然對集成第三方ORM框架也提供了相似的工具類,如對Hibernate提供了SessionFactoryUtils工具類,JPA的EntityManagerFactoryUtils等,其餘工具類都是使用相似***Utils命名;多線程
java代碼:Java代碼併發
- //獲取具備Spring事務(邏輯事務)管理功能的鏈接
- DataSourceUtils. getConnection(DataSource dataSource)
- //釋放具備Spring事務(邏輯事務)管理功能的鏈接
- DataSourceUtils. releaseConnection(Connection con, DataSource dataSource)
-
TransactionAwareDataSourceProxy:使用該數據源代理類包裝須要Spring事務管理支持的數據源,該包裝類必須位於最外層,主要用於遺留項目中可能直接使用數據源獲取鏈接和釋放鏈接支持或但願在Spring中進行混合使用各類持久化框架時使用,其內部實際使用 DataSourceUtils 工具類獲取和釋放真正鏈接;
java代碼:Java代碼
- <!--使用該方式包裝數據源,必須在最外層,targetDataSource 知道目標數據源-->
- <bean id="dataSourceProxy"
- class="org.springframework.jdbc.datasource.
- TransactionAwareDataSourceProxy">
- <property name="targetDataSource" ref="dataSource"/>
- </bean>
經過如上方式包裝數據源後,能夠在項目中使用物理事務編碼的方式來得到邏輯事務的支持,即支持直接從DataSource獲取鏈接和釋放鏈接,且這些鏈接自動支持Spring邏輯事務;
模板類:使用Spring提供的模板類,如JdbcTemplate、HibernateTemplate和JpaTemplate模板類等,而這些模板類內部實際上是使用了低級別解決方案中的工具類來管理鏈接或會話;
Spring提供兩種編程式事務支持:直接使用PlatformTransactionManager實現和使用TransactionTemplate模板類,用於支持邏輯事務管理。
若是採用編程式事務推薦使用TransactionTemplate模板類和高級別解決方案。
9.3.3 使用PlatformTransactionManager
首先讓咱們看下如何使用PlatformTransactionManager實現來進行事務管理:
1、數據源定義,此處使用第7章的配置文件,即「chapter7/ applicationContext-resources.xml」文件。
2、事務管理器定義(chapter9/applicationContext-jdbc.xml):
java代碼:Java代碼
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- </bean>
三、 準備測試環境:
3.1、首先準備測試時使用的SQL:
java代碼:Java代碼
- package cn.javass.spring.chapter9;
- //省略import
- public class TransactionTest {
- //id自增主鍵從0開始
- private static final String CREATE_TABLE_SQL = "create table test" +
- "(id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " +
- "name varchar(100))";
- private static final String DROP_TABLE_SQL = "drop table test";
- private static final String INSERT_SQL = "insert into test(name) values(?)";
- private static final String COUNT_SQL = "select count(*) from test";
- ……
- }
3.2、初始化Spring容器
java代碼:Java代碼
- package cn.javass.spring.chapter9;
- //省略import
- public class TransactionTest {
- private static ApplicationContext ctx;
- private static PlatformTransactionManager txManager;
- private static DataSource dataSource;
- private static JdbcTemplate jdbcTemplate;
- ……
- @BeforeClass
- public static void setUpClass() {
- String[] configLocations = new String[] {
- "classpath:chapter7/applicationContext-resources.xml",
- "classpath:chapter9/applicationContext-jdbc.xml"};
- ctx = new ClassPathXmlApplicationContext(configLocations);
- txManager = ctx.getBean(PlatformTransactionManager.class);
- dataSource = ctx.getBean(DataSource.class);
- jdbcTemplate = new JdbcTemplate(dataSource);
- }
- ……
- }
3.3、使用高級別方案JdbcTemplate來進行事務管理器測試:
java代碼:Java代碼
- @Test
- public void testPlatformTransactionManager() {
- DefaultTransactionDefinition def = new DefaultTransactionDefinition();
- def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- TransactionStatus status = txManager.getTransaction(def);
- jdbcTemplate.execute(CREATE_TABLE_SQL);
- try {
- jdbcTemplate.update(INSERT_SQL, "test");
- txManager.commit(status);
- } catch (RuntimeException e) {
- txManager.rollback(status);
- }
- jdbcTemplate.execute(DROP_TABLE_SQL);
- }
- DefaultTransactionDefinition:事務定義,定義如隔離級別、傳播行爲等,即在本示例中隔離級別爲ISOLATION_READ_COMMITTED(提交讀),傳播行爲爲PROPAGATION_REQUIRED(必須有事務支持,即若是當前沒有事務,就新建一個事務,若是已經存在一個事務中,就加入到這個事務中)。
- TransactionStatus:事務狀態類,經過PlatformTransactionManager的getTransaction方法根據事務定義獲取;獲取事務狀態後,Spring根據傳播行爲來決定如何開啓事務;
- JdbcTemplate:經過JdbcTemplate對象執行相應的SQL操做,且自動享受到事務支持,注意事務是線程綁定的,所以事務管理器能夠運行在多線程環境;
- txManager.commit(status):提交status對象綁定的事務;
- txManager.rollback(status):當遇到異常時回滾status對象綁定的事務。
3.4、使用低級別解決方案來進行事務管理器測試:
java代碼:Java代碼
- @Test
- public void testPlatformTransactionManagerForLowLevel1() {
- DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- TransactionStatus status = txManager.getTransaction(def);
- Connection conn = DataSourceUtils.getConnection(dataSource);
- try {
- conn.prepareStatement(CREATE_TABLE_SQL).execute();
- PreparedStatement pstmt = conn.prepareStatement(INSERT_SQL);
- pstmt.setString(1, "test");
- pstmt.execute();
- conn.prepareStatement(DROP_TABLE_SQL).execute();
- txManager.commit(status);
- } catch (Exception e) {
- status.setRollbackOnly();
- txManager.rollback(status);
- } finally {
- DataSourceUtils.releaseConnection(conn, dataSource);
- }
- }
-
低級別方案中使用DataSourceUtils獲取和釋放鏈接,使用txManager開管理事務,並且面向JDBC編程,比起模板類方式來繁瑣和複雜的多,所以不推薦使用該方式。在此就不介紹數據源代理類使用了,須要請參考platformTransactionManagerForLowLevelTest2測試方法。
到此事務管理是否是還很繁瑣?必須手工提交或回滾事務,有沒有更好的解決方案呢?Spring提供了TransactionTemplate模板類來簡化事務管理。
9.3.4 使用TransactionTemplate
TransactionTemplate模板類用於簡化事務管理,事務管理由模板類定義,而具體操做須要經過TransactionCallback回調接口或TransactionCallbackWithoutResult回調接口指定,經過調用模板類的參數類型爲TransactionCallback或TransactionCallbackWithoutResult的execute方法來自動享受事務管理。
TransactionTemplate模板類使用的回調接口:
- TransactionCallback:經過實現該接口的「T doInTransaction(TransactionStatus status) 」方法來定義須要事務管理的操做代碼;
- TransactionCallbackWithoutResult:繼承TransactionCallback接口,提供「void doInTransactionWithoutResult(TransactionStatus status)」便利接口用於方便那些不須要返回值的事務操做代碼。
1、接下來演示一下TransactionTemplate模板類如何使用:
java代碼:Java代碼
- @Test
- public void testTransactionTemplate() {//位於TransactionTest類中
- jdbcTemplate.execute(CREATE_TABLE_SQL);
- TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
- transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- jdbcTemplate.update(INSERT_SQL, "test");
- }});
- jdbcTemplate.execute(DROP_TABLE_SQL);
- }
- TransactionTemplate :經過new TransactionTemplate(txManager)建立事務模板類,其中構造器參數爲PlatformTransactionManager實現,並經過其相應方法設置事務定義,如事務隔離級別、傳播行爲等,此處未指定傳播行爲,其默認爲PROPAGATION_REQUIRED;
- TransactionCallbackWithoutResult:此處使用不帶返回的回調實現,其doInTransactionWithoutResult方法實現中定義了須要事務管理的操做;
- transactionTemplate.execute():經過該方法執行須要事務管理的回調。
這樣是否是簡單多了,沒有事務管理代碼,而是由模板類來完成事務管理。
注:對於拋出Exception類型的異常且須要回滾時,須要捕獲異常並經過調用status對象的setRollbackOnly()方法告知事務管理器當前事務須要回滾,以下所示:
java代碼:Java代碼
- try {
- //業務操做
- } catch (Exception e) { //可以使用具體業務異常代替
- status.setRollbackOnly();
- }
2、前邊已經演示了JDBC事務管理,接下來演示一下JTA分佈式事務管理:
java代碼:Java代碼
- @Test
- public void testJtaTransactionTemplate() {
- String[] configLocations = new String[] {
- "classpath:chapter9/applicationContext-jta-derby.xml"};
- ctx = new ClassPathXmlApplicationContext(configLocations);
- final PlatformTransactionManager jtaTXManager = ctx.getBean(PlatformTransactionManager.class);
- final DataSource derbyDataSource1 = ctx.getBean("dataSource1", DataSource.class);
- final DataSource derbyDataSource2 = ctx.getBean("dataSource2", DataSource.class);
- final JdbcTemplate jdbcTemplate1 = new JdbcTemplate(derbyDataSource1);
- final JdbcTemplate jdbcTemplate2 = new JdbcTemplate(derbyDataSource2);
- TransactionTemplate transactionTemplate = new TransactionTemplate(jtaTXManager);
- transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
- jdbcTemplate1.update(CREATE_TABLE_SQL);
- int originalCount = jdbcTemplate1.queryForInt(COUNT_SQL);
- try {
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- jdbcTemplate1.update(INSERT_SQL, "test");
- //由於數據庫2沒有建立數據庫表所以會回滾事務
- jdbcTemplate2.update(INSERT_SQL, "test");
- }});
- } catch (RuntimeException e) {
- int count = jdbcTemplate1.queryForInt(COUNT_SQL);
- Assert.assertEquals(originalCount, count);
- }
- jdbcTemplate1.update(DROP_TABLE_SQL);
- }
- 配置文件:使用此前定義的chapter9/applicationContext-jta-derby.xml;
- jtaTXManager: JTA事務管理器;
- derbyDataSource1和derbyDataSource2:derby數據源1和derby數據源2;
- jdbcTemplate1和jdbcTemplate2:分別使用derbyDataSource1和derbyDataSource2構造的JDBC模板類;
- transactionTemplate:使用jtaTXManager事務管理器的事務管理模板類,其隔離級別爲提交讀,傳播行爲默認爲PROPAGATION_REQUIRED(必須有事務支持,即若是當前沒有事務,就新建一個事務,若是已經存在一個事務中,就加入到這個事務中);
- jdbcTemplate1.update(CREATE_TABLE_SQL):此處只有derbyDataSource1所表明的數據庫建立了「test」表,而derbyDataSource2所表明的數據庫沒有此表;
- TransactionCallbackWithoutResult:在此接口實現中定義了須要事務支持的操做:
jdbcTemplate1.update(INSERT_SQL, "test"):表示向數據庫1中的test表中插入數據;
jdbcTemplate2.update(INSERT_SQL, "test"):表示向數據庫2中的test表中插入數據,但數據庫2沒有此表將拋出異常,且JTA分佈式事務將回滾;
- Assert.assertEquals(originalCount, count):用來驗證事務是否回滾,驗證結果返回爲true,說明分佈式事務回滾了。
到此咱們已經會使用PlatformTransactionManager和TransactionTemplate進行簡單事務處理了,那如何應用到實際項目中去呢?接下來讓咱們看下如何在實際項目中應用Spring管理事務。
接下來看一下如何將Spring管理事務應用到實際項目中,爲簡化演示,此處只定義最簡單的模型對象和不完整的Dao層接口和Service層接口:
一、 首先定義項目中的模型對象,本示例使用用戶模型和用戶地址模型:
模型對象通常放在項目中的model包裏。
java代碼:Java代碼
- package cn.javass.spring.chapter9.model;
- public class UserModel {
- private int id;
- private String name;
- private AddressModel address;
- //省略getter和setter
- }
java代碼:Java代碼
- package cn.javass.spring.chapter9.model;
- public class AddressModel {
- private int id;
- private String province;
- private String city;
- privateString street;
- private int userId;
- //省略getter和setter
- }
2.1、定義Dao層接口:
java代碼:Java代碼
- package cn.javass.spring.chapter9.service;
- import cn.javass.spring.chapter9.model.UserModel;
- public interface IUserService {
- public void save(UserModel user);
- public int countAll();
- }
-
java代碼:Java代碼
- package cn.javass.spring.chapter9.service;
- import cn.javass.spring.chapter9.model.AddressModel;
- public interface IAddressService {
- public void save(AddressModel address);
- public int countAll();
- }
2.二、定義Dao層實現:
java代碼:Java代碼
- package cn.javass.spring.chapter9.dao.jdbc;
- //省略import,注意model要引用chapter包裏的
- public class UserJdbcDaoImpl extends NamedParameterJdbcDaoSupport implements IUserDao {
- private final String INSERT_SQL = "insert into user(name) values(:name)";
- private final String COUNT_ALL_SQL = "select count(*) from user";
- @Override
- public void save(UserModel user) {
- KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
- SqlParameterSource paramSource = new BeanPropertySqlParameterSource(user);
- getNamedParameterJdbcTemplate().update(INSERT_SQL, paramSource, generatedKeyHolder);
- user.setId(generatedKeyHolder.getKey().intValue());
- }
- @Override
- public int countAll() {
- return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
- }
- }
java代碼:Java代碼
- package cn.javass.spring.chapter9.dao.jdbc;
- //省略import,注意model要引用chapter包裏的
- public class AddressJdbcDaoImpl extends NamedParameterJdbcDaoSupport implements IAddressDao {
- private final String INSERT_SQL = "insert into address(province, city, street, user_id)" + "values(:province, :city, :street, :userId)";
- private final String COUNT_ALL_SQL = "select count(*) from address";
- @Override
- public void save(AddressModel address) {
- KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
- SqlParameterSource paramSource = new BeanPropertySqlParameterSource(address);
- getNamedParameterJdbcTemplate().update(INSERT_SQL, paramSource, generatedKeyHolder);
- address.setId(generatedKeyHolder.getKey().intValue());
- }
- @Override
- public int countAll() {
- return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
- }
- }
3.1、定義Service層接口,通常使用「I×××Service」命名:
java代碼:Java代碼
- package cn.javass.spring.chapter9.service;
- import cn.javass.spring.chapter9.model.UserModel;
- public interface IUserService {
- public void save(UserModel user);
- public int countAll();
- }
-
-
- package cn.javass.spring.chapter9.service;
- import cn.javass.spring.chapter9.model.AddressModel;
- public interface IAddressService {
- public void save(AddressModel address);
- public int countAll();
- }
3.2、定義Service層實現,通常使用「×××ServiceImpl」或「×××Service」命名:
java代碼:Java代碼
- package cn.javass.spring.chapter9.service.impl;
- //省略import,注意model要引用chapter包裏的
- public class AddressServiceImpl implements IAddressService {
- private IAddressDao addressDao;
- private PlatformTransactionManager txManager;
- public void setAddressDao(IAddressDao addressDao) {
- this.addressDao = addressDao;
- }
- public void setTxManager(PlatformTransactionManager txManager) {
- this.txManager = txManager;
- }
- @Override
- public void save(final AddressModel address) {
- TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- addressDao.save(address);
- }
- });
- }
- @Override
- public int countAll() {
- return addressDao.countAll();
- }
- }
-
java代碼:Java代碼
-
- package cn.javass.spring.chapter9.service.impl;
- //省略import,注意model要引用chapter包裏的
- public class UserServiceImpl implements IUserService {
- private IUserDao userDao;
- private IAddressService addressService;
- private PlatformTransactionManager txManager;
- public void setUserDao(IUserDao userDao) {
- this.userDao = userDao;
- }
- public void setTxManager(PlatformTransactionManager txManager) {
- this.txManager = txManager;
- }
- public void setAddressService(IAddressService addressService) {
- this.addressService = addressService;
- }
- @Override
- public void save(final UserModel user) {
- TransactionTemplate transactionTemplate =
- TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- userDao.save(user);
- user.getAddress().setUserId(user.getId());
- addressService.save(user.getAddress());
- }
- });
- }
- @Override
- public int countAll() {
- return userDao.countAll();
- }
- }
-
-
Service實現中須要Spring事務管理的部分應該使用TransactionTemplate模板類來包裝執行。
4、定義TransactionTemplateUtils,用於簡化獲取TransactionTemplate模板類,工具類通常放在util包中:
java代碼:Java代碼
- package cn.javass.spring.chapter9.util;
- //省略import
- public class TransactionTemplateUtils {
- public static TransactionTemplate getTransactionTemplate(
- PlatformTransactionManager txManager,
- int propagationBehavior,
- int isolationLevel) {
-
- TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
- transactionTemplate.setPropagationBehavior(propagationBehavior);
- transactionTemplate.setIsolationLevel(isolationLevel);
- return transactionTemplate;
- }
-
- public static TransactionTemplate getDefaultTransactionTemplate(PlatformTransactionManager txManager) {
- return getTransactionTemplate(
- txManager,
- TransactionDefinition.PROPAGATION_REQUIRED,
- TransactionDefinition.ISOLATION_READ_COMMITTED);
- }
- }
-
getDefaultTransactionTemplate用於獲取傳播行爲爲PROPAGATION_REQUIRED,隔離級別爲ISOLATION_READ_COMMITTED的模板類。
5、數據源配置定義,此處使用第7章的配置文件,即「chapter7/ applicationContext-resources.xml」文件。
6、Dao層配置定義(chapter9/dao/applicationContext-jdbc.xml):
java代碼:Java代碼
- <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <bean id="abstractDao" abstract="true">
- <property name="dataSource" ref="dataSource"/>
- </bean>
java代碼:Java代碼
- <bean id="userDao" class="cn.javass.spring.chapter9.dao.jdbc.UserJdbcDaoImpl" parent="abstractDao"/>
- <bean id="addressDao" class="cn.javass.spring.chapter9.dao.jdbc.AddressJdbcDaoImpl" parent="abstractDao"/>
7、Service層配置定義(chapter9/service/applicationContext-service.xml):
java代碼:Java代碼
- <bean id="userService" class="cn.javass.spring.chapter9.service.impl.UserServiceImpl">
- <property name="userDao" ref="userDao"/>
- <property name="txManager" ref="txManager"/>
- <property name="addressService" ref="addressService"/>
- </bean>
- <bean id="addressService" class="cn.javass.spring.chapter9.service.impl.AddressServiceImpl">
- <property name="addressDao" ref="addressDao"/>
- <property name="txManager" ref="txManager"/>
- </bean>
8、準備測試須要的表建立語句,在TransactionTest測試類中添加以下靜態變量:
java代碼:Java代碼
- private static final String CREATE_USER_TABLE_SQL =
- "create table user" +
- "(id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " +
- "name varchar(100))";
- private static final String DROP_USER_TABLE_SQL = "drop table user";
-
- private static final String CREATE_ADDRESS_TABLE_SQL =
- "create table address" +
- "(id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " +
- "province varchar(100), city varchar(100), street varchar(100), user_id int)";
- private static final String DROP_ADDRESS_TABLE_SQL = "drop table address";
九、 測試一下吧:
java代碼:Java代碼
- @Test
- public void testServiceTransaction() {
- String[] configLocations = new String[] {
- "classpath:chapter7/applicationContext-resources.xml",
- "classpath:chapter9/dao/applicationContext-jdbc.xml",
- "classpath:chapter9/service/applicationContext-service.xml"};
- ApplicationContext ctx2 = new ClassPathXmlApplicationContext(configLocations);
-
- DataSource dataSource2 = ctx2.getBean(DataSource.class);
- JdbcTemplate jdbcTemplate2 = new JdbcTemplate(dataSource2);
- jdbcTemplate2.update(CREATE_USER_TABLE_SQL);
- jdbcTemplate2.update(CREATE_ADDRESS_TABLE_SQL);
-
- IUserService userService = ctx2.getBean("userService", IUserService.class);
- IAddressService addressService = ctx2.getBean("addressService", IAddressService.class);
- UserModel user = createDefaultUserModel();
- userService.save(user);
- Assert.assertEquals(1, userService.countAll());
- Assert.assertEquals(1, addressService.countAll());
- jdbcTemplate2.update(DROP_USER_TABLE_SQL);
- jdbcTemplate2.update(DROP_ADDRESS_TABLE_SQL);
- }
- private UserModel createDefaultUserModel() {
- UserModel user = new UserModel();
- user.setName("test");
- AddressModel address = new AddressModel();
- address.setProvince("beijing");
- address.setCity("beijing");
- address.setStreet("haidian");
- user.setAddress(address);
- return user;
- }
-
從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 Required傳播行爲
圖9-3 Required傳播行爲拋出異常狀況
在前邊示例中就是使用的Required傳播行爲:
1、在調用userService對象的save方法時,此方法用的是Required傳播行爲且此時Spring事務管理器發現還沒開啓邏輯事務,所以Spring管理器以爲開啓邏輯事務,
2、在此邏輯事務中調用了addressService對象的save方法,而在save方法中發現一樣用的是Required傳播行爲,所以使用該已經存在的邏輯事務;
3、在返回到addressService對象的save方法,當事務模板類執行完畢,此時提交併關閉事務。
所以userService對象的save方法和addressService的save方法屬於同一個物理事務,若是發生回滾,則二者都回滾。
接下來測試一下該傳播行爲如何執行吧:
1、正確提交測試,如上一節的測試,在此再也不演示;
2、回滾測試,修改AddressServiceImpl的save方法片斷:
java代碼:Java代碼
- addressDao.save(address);
爲
java代碼:Java代碼
- addressDao.save(address);
- //拋出異常,將標識當前事務須要回滾
- throw new RuntimeException();
2、修改UserServiceImpl的save方法片斷:
java代碼:Java代碼
- addressService.save(user.getAddress());
爲
java代碼:Java代碼
- try {
- addressService.save(user.getAddress());//將在同一個事務內執行
- } catch (RuntimeException e) {
- }
-
若是該業務方法執行時事務被標記爲回滾,則無論在此是否捕獲該異常都將發生回滾,由於處於同一邏輯事務。
3、修改測試方法片斷:
java代碼:Java代碼
- userService.save(user);
- Assert.assertEquals(1, userService.countAll());
- Assert.assertEquals(1, addressService.countAll());
爲以下形式:
java代碼:Java代碼
- 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所示:
圖9-4 RequiresNew傳播行爲
圖9-5 RequiresNew傳播行爲並拋出異常
接下來測試一個該傳播行爲如何執行吧:
一、將以下獲取事務模板方式
java代碼:Java代碼
- TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
替換爲以下形式,表示傳播行爲爲RequiresNew:
java代碼:Java代碼
- TransactionTemplate transactionTemplate = TransactionTemplateUtils.getTransactionTemplate(
- txManager,
- TransactionDefinition.PROPAGATION_REQUIRES_NEW,
- TransactionDefinition.ISOLATION_READ_COMMITTED);
二、執行以下測試,發現執行結果是正確的:
java代碼:Java代碼
- userService.save(user);
- Assert.assertEquals(1, userService.countAll());
- Assert.assertEquals(1, addressService.countAll());
三、修改UserServiceImpl的save方法片斷
java代碼:Java代碼
- userDao.save(user);
- user.getAddress().setUserId(user.getId());
- addressService.save(user.getAddress());
爲以下形式,表示userServiceImpl類的save方法將發生回滾,而AddressServiceImpl類的方法因爲在拋出異常前執行,將成功提交事務到數據庫:
java代碼:Java代碼
- userDao.save(user);
- user.getAddress().setUserId(user.getId());
- addressService.save(user.getAddress());
- throw new RuntimeException();
四、修改測試方法片斷:
java代碼:Java代碼
- userService.save(user);
- Assert.assertEquals(1, userService.countAll());
- Assert.assertEquals(1, addressService.countAll());
爲以下形式:
java代碼:Java代碼
- 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。
該傳播行爲執行流程(正確提交狀況):
1、當執行userService對象的save方法時,因爲傳播行爲是RequiresNew,所以建立一個新的邏輯事務(物理事務也是不一樣的);
2、當執行到addressService對象的save方法時,因爲傳播行爲是RequiresNew,所以首先暫停上一個邏輯事務並建立一個新的邏輯事務(物理事務也是不一樣的);
3、addressService對象的save方法執行完畢後,提交邏輯事務(並提交物理事務)並從新恢復上一個邏輯事務,繼續執行userService對象的save方法內的操做;
4、最後userService對象的save方法執行完畢,提交邏輯事務(並提交物理事務);
5、userService對象的save方法和addressService對象的save方法不屬於同一個邏輯事務且也不屬於同一個物理事務。
Supports:支持當前事務,使用PROPAGATION_SUPPORTS指定,指若是當前存在邏輯事務,就加入到該邏輯事務,若是當前沒有邏輯事務,就以非事務方式執行,如圖9-6和9-7所示:
圖9-6 Required+Supports傳播行爲
圖9-7 Supports+Supports傳播行爲
NotSupported:不支持事務,若是當前存在事務則暫停該事務,使用PROPAGATION_NOT_SUPPORTED指定,即以非事務方式執行,若是當前存在邏輯事務,就把當前事務暫停,以非事務方式執行,如圖9-8和9-9所示:
圖9-8 Required+NotSupported傳播行爲
圖9-9 Supports+NotSupported傳播行爲
Mandatory:必須有事務,不然拋出異常,使用PROPAGATION_MANDATORY指定,使用當前事務執行,若是當前沒有事務,則拋出異常(IllegalTransactionStateException),如圖9-10和9-11所示:
圖9-10 Required+Mandatory傳播行爲
圖9-11 Supports+Mandatory傳播行爲
Never:不支持事務,若是當前存在是事務則拋出異常,使用PROPAGATION_NEVER指定,即以非事務方式執行,若是當前存在事務,則拋出異常(IllegalTransactionStateException),如圖9-12和9-13所示:
圖9-12 Required+Never傳播行爲
圖9-13 Supports+Never傳播行爲
Nested:嵌套事務支持,使用PROPAGATION_NESTED指定,若是當前存在事務,則在嵌套事務內執行,若是當前不存在事務,則建立一個新的事務,嵌套事務使用數據庫中的保存點來實現,即嵌套事務回滾不影響外部事務,但外部事務回滾將致使嵌套事務回滾,如圖9-14和9-15所示:
圖9-14 Required+Nested傳播行爲
圖9-15 Nested+Nested傳播行爲
Nested和RequiresNew的區別:
一、 RequiresNew每次都建立新的獨立的物理事務,而Nested只有一個物理事務;
二、 Nested嵌套事務回滾或提交不會致使外部事務回滾或提交,但外部事務回滾將致使嵌套事務回滾,而 RequiresNew因爲都是全新的事務,因此之間是無關聯的;
三、 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是錯誤的事務只讀設置:
圖9-16 正確的事務只讀設置
圖9-17 錯誤的事務只讀設置
如圖10-17,對於錯誤的事務只讀設置將拋出IllegalTransactionStateException異常,並伴隨「Participating transaction with definition [……] is not marked as read-only……」信息,表示參與的事務只讀屬性設置錯誤。
你們有沒有感受到編程式實現事務管理是否是很繁瑣冗長,重複,並且是侵入式的,所以發展到這Spring決定使用配置方式實現事務管理。
9.3.6 配置方式實現事務管理
在Spring2.x以前爲了解決編程式事務管理的各類很差問題,Spring提出使用配置方式實現事務管理,配置方式利用代理機制實現,即便有TransactionProxyFactoryBean類來爲目標類代理事務管理。
接下來演示一下具體使用吧:
1、從新定義業務類實現,在業務類中無需顯示的事務管理代碼:
java代碼:Java代碼
- 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方法實現不變
- }
java代碼:Java代碼
- 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方法實現不變
- }
從以上業務類中能夠看出,沒有事務管理的代碼,即沒有侵入式的代碼。
二、在chapter9/service/applicationContext-service.xml配置文件中添加以下配置:
2.一、首先添加目標類定義:
java代碼:Java代碼
- <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.二、配置TransactionProxyFactoryBean類:
java代碼:Java代碼
- <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異常回滾。
須要特別注意「-異常」和「+異常」中「異常」只是真實異常的部分名,內部使用以下方式判斷:
java代碼:Java代碼
- //真實拋出的異常.name.indexOf(配置中指定的須要回滾/不回滾的異常名)
- exceptionClass.getName().indexOf(this.exceptionName)
-
所以異常定義時須要特別注意,配置中定義的異常只是真實異常的部分名。
2.3、定義代理Bean:
java代碼:Java代碼
- <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測試方法內將:
java代碼:Java代碼
- IUserService userService =
- ctx2.getBean("userService", IUserService.class);
- IAddressService addressService =
- ctx2.getBean("addressService", IAddressService.class);
替換爲:
java代碼:Java代碼
- IUserService userService =
- ctx2.getBean("proxyUserService ", IUserService.class);
- IAddressService addressService =
- ctx2.getBean("proxyAddressService ", IAddressService.class);
4、執行測試,測試正常經過,說明該方式能正常工做,當調用save方法時將匹配到「<prop key="save*">」定義,而countAll將匹配到「<prop key="save*">」定義,底層代理會應用相應定義中的事務屬性來建立或關閉事務。
圖9-18 代理方式實現事務管理
如圖9-18,代理方式實現事務管理只是將硬編碼的事務管理代碼轉移到代理中去由代理實現,在代理中實現事務管理。
注:在代理模式下,默認只有經過代理對象調用的方法才能應用相應的事務屬性,而在目標方法內的「自我調用」是不會應用相應的事務屬性的,即被調用方法不會應用相應的事務屬性,而是使用調用方法的事務屬性。
如圖9-19所示,在目標對象targetUserService的save方法內調用事務方法「this.otherTransactionMethod()」將不會應用配置的傳播行爲RequriesNew,開啓新事務,而是使用save方法的已開啓事務,若是非要這樣使用以下方式實現:
一、 修改TransactionProxyFactoryBean配置定義,添加exposeProxy屬性爲true;
二、 在業務方法內經過代理對象調用相應的事務方放,如 「((IUserService)AopContext.currentProxy()).otherTransactionMethod()」便可應用配置的事務屬性。
三、 使用這種方式屬於侵入式,不推薦使用,除非必要。
圖9-19 代理方式下的自我調用
配置方式也好麻煩啊,每一個業務實現都須要配置一個事務代理,發展到這,Spring想出更好的解決方案,Spring2.0及以後版本提出使用新的「<tx:tags/>」方式配置事務,從而無需爲每一個業務實現配置一個代理。
原創內容,轉載請註明出處【http://sishuok.com/forum/blogPost/list/2506.html】