<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3 ## 9.3? 編程式事務 ### 9.3.1? 編程式事務概述 所謂編程式事務指的是通過編碼方式實現事務,即類似于JDBC編程實現事務管理。 Spring框架提供一致的事務抽象,因此對于JDBC還是JTA事務都是采用相同的API進行編程。 ``` 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事務管理的異常也是不一樣的。 具體如何使用JTA編程進行事務管理請參考cn.javass.spring.chapter9包下的TranditionalTransactionTest類。 而在Spring中將采用一致的事務抽象進行控制和一致的異常控制,即面向PlatformTransactionManager接口編程來控制事務。 ### 9.3.1?? ?Spring對編程式事務的支持 Spring中的事務分為物理事務和邏輯事務; * **物理事務**:就是底層數據庫提供的事務支持,如JDBC或JTA提供的事務; * **邏輯事務**:是Spring管理的事務,不同于物理事務,邏輯事務提供更豐富的控制,而且如果想得到Spring事務管理的好處,必須使用邏輯事務,因此在Spring中如果沒特別強調一般就是邏輯事務; 邏輯事務即支持非常低級別的控制,也有高級別解決方案: * **低級別解決方案:** **工具類:**使用工具類獲取連接(會話)和釋放連接(會話),如使用org.springframework.jdbc.datasource包中的 DataSourceUtils?類來獲取和釋放具有邏輯事務功能的連接。當然對集成第三方ORM框架也提供了類似的工具類,如對Hibernate提供了SessionFactoryUtils工具類,JPA的EntityManagerFactoryUtils等,其他工具類都是使用類似***Utils命名; ``` //獲取具有Spring事務(邏輯事務)管理功能的連接 DataSourceUtils. getConnection(DataSource dataSource) //釋放具有Spring事務(邏輯事務)管理功能的連接 DataSourceUtils. releaseConnection(Connection con, DataSource dataSource) ``` **TransactionAwareDataSourceProxy:**使用該數據源代理類包裝需要Spring事務管理支持的數據源,該包裝類必須位于最外層,主要用于遺留項目中可能直接使用數據源獲取連接和釋放連接支持或希望在Spring中進行混合使用各種持久化框架時使用,其內部實際使用 DataSourceUtils?工具類獲取和釋放真正連接; ``` <!--使用該方式包裝數據源,必須在最外層,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):** ``` <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> ``` **3、?準備測試環境:** **3.1、首先準備測試時使用的SQL:** ``` 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容器** ``` 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來進行事務管理器測試:** ``` @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、使用低級別解決方案來進行事務管理器測試:** ``` @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模板類如何使用:** ``` @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()方法告知事務管理器當前事務需要回滾,如下所示:** ``` try { //業務操作 } catch (Exception e) { //可使用具體業務異常代替 status.setRollbackOnly(); } ``` **2、前邊已經演示了JDBC事務管理,接下來演示一下JTA分布式事務管理:** ``` @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層接口: **1、?首先定義項目中的模型對象,本示例使用用戶模型和用戶地址模型:** **模型對象一般放在項目中的model包里。** ``` package cn.javass.spring.chapter9.model; public class UserModel { private int id; private String name; private AddressModel address; //省略getter和setter } ``` ``` 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層接口:** ``` 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(); } ``` 2.2、定義Dao層實現: ``` 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); } } ``` ``` 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”命名:** ``` 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”命名:** ``` 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(); } } ``` ``` 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包中:** ``` 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):** ``` <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> ``` ``` <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):** ``` <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測試類中添加如下靜態變量:** ``` 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"; ``` **9、?測試一下吧:** ``` @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所示; ![](https://box.kancloud.cn/2016-05-13_5735471fc6580.JPG) 圖9-2 Required傳播行為 ![](https://box.kancloud.cn/2016-05-13_5735471fdcdf0.JPG) 圖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所示: ![](https://box.kancloud.cn/2016-05-13_5735471ff1fb1.JPG) 圖9-4 RequiresNew傳播行為 ![](https://box.kancloud.cn/2016-05-13_5735472013d83.JPG) 圖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所示: ![](https://box.kancloud.cn/2016-05-13_573547203e103.JPG) 圖9-6 Required+Supports傳播行為 ![](https://box.kancloud.cn/2016-05-13_5735472056d40.JPG) 圖9-7 Supports+Supports傳播行為 **NotSupported**:不支持事務,如果當前存在事務則暫停該事務,使用PROPAGATION_NOT_SUPPORTED指定,即以非事務方式執行,如果當前存在邏輯事務,就把當前事務暫停,以非事務方式執行,如圖9-8和9-9所示: ![](https://box.kancloud.cn/2016-05-13_573547206eff3.JPG) 圖9-8 Required+NotSupported傳播行為 ![](https://box.kancloud.cn/2016-05-13_57354720862a8.JPG) 圖9-9 Supports+NotSupported傳播行為 **Mandatory**:必須有事務,否則拋出異常,使用PROPAGATION_MANDATORY指定,使用當前事務執行,如果當前沒有事務,則拋出異常(IllegalTransactionStateException),如圖9-10和9-11所示: ![](https://box.kancloud.cn/2016-05-13_573547209f3b9.JPG) 圖9-10 Required+Mandatory傳播行為 ![](https://box.kancloud.cn/2016-05-13_57354720b69f7.JPG) 圖9-11 Supports+Mandatory傳播行為 **Never**:不支持事務,如果當前存在是事務則拋出異常,使用PROPAGATION_NEVER指定,即以非事務方式執行,如果當前存在事務,則拋出異常(IllegalTransactionStateException),如圖9-12和9-13所示: ![](https://box.kancloud.cn/2016-05-13_57354720cc2e2.JPG) 圖9-12 Required+Never傳播行為 ![](https://box.kancloud.cn/2016-05-13_57354720e8715.JPG) 圖9-13 Supports+Never傳播行為 **Nested**:嵌套事務支持,使用PROPAGATION_NESTED指定,如果當前存在事務,則在嵌套事務內執行,如果當前不存在事務,則創建一個新的事務,嵌套事務使用數據庫中的保存點來實現,即嵌套事務回滾不影響外部事務,但外部事務回滾將導致嵌套事務回滾,如圖9-14和9-15所示: ![](https://box.kancloud.cn/2016-05-13_5735472114893.JPG) 圖9-14 Required+Nested傳播行為 ![](https://box.kancloud.cn/2016-05-13_573547212d693.JPG) 圖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是錯誤的事務只讀設置: ![](https://box.kancloud.cn/2016-05-13_573547214771c.JPG) 圖9-16 正確的事務只讀設置 ![](https://box.kancloud.cn/2016-05-13_573547215fa23.JPG) 圖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異常回滾; * **&lt;prop key="save*"&gt;:**表示將代理以save開頭的方法,即當執行到該方法時會為該方法根據事務屬性配置來開啟/關閉事務; * **&lt;prop key="*"&gt;:**表示將代理其他所有方法,但需要注意代理方式,默認是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方法時將匹配到“**&lt;prop key="save*"&gt;**”定義,而countAll將匹配到“**&lt;prop key="save*"&gt;**”定義,底層代理會應用相應定義中的事務屬性來創建或關閉事務。 ![](https://box.kancloud.cn/2016-05-13_573547217dd9c.JPG) 圖9-18 代理方式實現事務管理 如圖9-18,代理方式實現事務管理只是將硬編碼的事務管理代碼轉移到代理中去由代理實現,在代理中實現事務管理。 注:**在代理模式下,默認只有通過代理對象調用的方法才能應用相應的事務屬性,而在目標方法內的“自我調用”是不會應用相應的事務屬性的,即被調用方法不會應用相應的事務屬性,而是使用調用方法的事務屬性。** 如圖9-19所示,在目標對象targetUserService的save方法內調用事務方法“this.otherTransactionMethod()”將不會應用配置的傳播行為RequriesNew,開啟新事務,而是使用save方法的已開啟事務,如果非要這樣使用如下方式實現: 1、? 修改TransactionProxyFactoryBean配置定義,添加exposeProxy屬性為true; 2、? 在業務方法內通過代理對象調用相應的事務方放,如 “**((IUserService)AopContext.currentProxy()).otherTransactionMethod()**”即可應用配置的事務屬性。 3、? 使用這種方式屬于侵入式,不推薦使用,除非必要。 ![](https://box.kancloud.cn/2016-05-13_5735472198fe4.JPG)? 圖9-19 代理方式下的自我調用 配置方式也好麻煩啊,每個業務實現都需要配置一個事務代理,發展到這,Spring想出更好的解決方案,Spring2.0及之后版本提出使用新的“&lt;tx:tags/&gt;”方式配置事務,從而無需為每個業務實現配置一個代理。 原創內容,轉載請注明出處【[http://sishuok.com/forum/blogPost/list/2506.html](http://sishuok.com/forum/blogPost/list/2506.html)】
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看