javascript
Spring Data JPA事务管理
1、事務(wù)基礎(chǔ)概念_四大特性
數(shù)據(jù)庫中事務(wù)的四大特性(ACID),如果一個(gè)數(shù)據(jù)庫聲稱支持事務(wù)的操作,那么該數(shù)據(jù)庫必須要具備以下四個(gè)特性:
⑴ 原子性(Atomicity)
原子性,是指事務(wù)包含的所有操作,要么全部成功,要么全部失敗回滾。因此,事務(wù)的操作如果成功就必須要完全持久化到數(shù)據(jù)庫,如果操作失敗則不能對數(shù)據(jù)庫有任何影響。
⑵ 一致性(Consistency)
一致性,是指事務(wù)必須使數(shù)據(jù)庫從一個(gè)一致性狀態(tài)變換到另一個(gè)一致性狀態(tài),也就是說一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后都必須處于一致性狀態(tài)。
舉例:假設(shè)用戶甲和用戶乙兩者的錢加起來一共是10000,那么不管甲和乙之間如何轉(zhuǎn)賬,轉(zhuǎn)幾次賬,事務(wù)結(jié)束后兩個(gè)用戶的錢相加起來應(yīng)該還是10000,這即是事務(wù)的一致性。
⑶ 隔離性(Isolation)
隔離性,是當(dāng)多個(gè)用戶并發(fā)訪問數(shù)據(jù)庫時(shí),比如操作同一張表時(shí),數(shù)據(jù)庫為每一個(gè)用戶開啟的事務(wù),不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離。
即要達(dá)到這么一種效果:對于任意兩個(gè)并發(fā)的事務(wù)T1和T2,在事務(wù)T1看來,T2要么在T1開始之前就已經(jīng)結(jié)束,要么在T1結(jié)束之后才開始,這樣每個(gè)事務(wù)都感覺不到有其他事務(wù)在并發(fā)地執(zhí)行。
? ? ? ?在介紹數(shù)據(jù)庫提供的各種隔離級別之前,我們先看看如果不考慮事務(wù)的隔離性,會(huì)發(fā)生的幾種問題:
? ? ? ?第一、臟讀
臟讀是指在一個(gè)事務(wù)處理過程里讀取了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)。
當(dāng)一個(gè)事務(wù)正在多次修改某個(gè)數(shù)據(jù),而在這個(gè)事務(wù)中這多次的修改都還未提交,這時(shí)一個(gè)并發(fā)的事務(wù)來訪問該數(shù)據(jù),就會(huì)造成兩個(gè)事務(wù)得到的數(shù)據(jù)不一致。例如:用戶A向用戶B轉(zhuǎn)賬100元,對應(yīng)SQL命令如下
? ? update account set money=money+100 where name=’B’; ?(此時(shí)A通知B)update account set money=money - 100 where name=’A’;
當(dāng)只執(zhí)行第一條SQL時(shí),A通知B查看賬戶,B發(fā)現(xiàn)確實(shí)錢已到賬(此時(shí)即發(fā)生了臟讀),而之后無論第二條SQL是否執(zhí)行,只要該事務(wù)不提交,則所有操作都將回滾,那么當(dāng)B以后再次查看賬戶時(shí)就會(huì)發(fā)現(xiàn)錢其實(shí)并沒有轉(zhuǎn)。
? ? ? ?第二、不可重復(fù)讀
不可重復(fù)讀是指在對于數(shù)據(jù)庫中的某個(gè)數(shù)據(jù),一個(gè)事務(wù)范圍內(nèi)多次查詢卻返回了不同的數(shù)據(jù)值,這是由于在查詢間隔,被另一個(gè)事務(wù)修改并提交了。
例如事務(wù)T1在讀取某一數(shù)據(jù),而事務(wù)T2立馬修改了這個(gè)數(shù)據(jù)并且提交事務(wù)給數(shù)據(jù)庫,事務(wù)T1再次讀取該數(shù)據(jù)就得到了不同的結(jié)果,發(fā)送了不可重復(fù)讀。
不可重復(fù)讀和臟讀的區(qū)別是,臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù),而不可重復(fù)讀則是讀取了前一事務(wù)提交的數(shù)據(jù)。
在某些情況下,不可重復(fù)讀并不是問題,比如我們多次查詢某個(gè)數(shù)據(jù)當(dāng)然以最后查詢得到的結(jié)果為主。但在另一些情況下就有可能發(fā)生問題,例如對于同一個(gè)數(shù)據(jù)A和B依次查詢就可能不同,A和B就可能打起來了……
? ? ? ?第三、虛讀(幻讀)
幻讀是事務(wù)非獨(dú)立執(zhí)行時(shí)發(fā)生的一種現(xiàn)象。例如事務(wù)T1對一個(gè)表中所有的行的某個(gè)數(shù)據(jù)項(xiàng)做了從“1”修改為“2”的操作,這時(shí)事務(wù)T2又對這個(gè)表中插入了一行數(shù)據(jù)項(xiàng),而這個(gè)數(shù)據(jù)項(xiàng)的數(shù)值還是為“1”并且提交給數(shù)據(jù)庫。而操作事務(wù)T1的用戶如果再查看剛剛修改的數(shù)據(jù),會(huì)發(fā)現(xiàn)還有一行沒有修改,其實(shí)這行是從事務(wù)T2中添加的,就好像產(chǎn)生幻覺一樣,這就是發(fā)生了幻讀。
幻讀和不可重復(fù)讀都是讀取了另一條已經(jīng)提交的事務(wù)(這點(diǎn)就臟讀不同),所不同的是不可重復(fù)讀查詢的都是同一個(gè)數(shù)據(jù)項(xiàng),而幻讀針對的是一批數(shù)據(jù)整體(比如數(shù)據(jù)的個(gè)數(shù))
關(guān)于事務(wù)的隔離性數(shù)據(jù)庫提供了多種隔離級別,mySQL數(shù)據(jù)庫為我們提供的四種隔離級別:
① Serializable (串行化):可避免臟讀、不可重復(fù)讀、幻讀的發(fā)生。
② Repeatable read (可重復(fù)讀):可避免臟讀、不可重復(fù)讀的發(fā)生。
③ Read committed (讀已提交):可避免臟讀的發(fā)生。
④ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
? ? 以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當(dāng)然級別越高,執(zhí)行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時(shí)選用何種隔離級別應(yīng)該根據(jù)實(shí)際情況。在MySQL數(shù)據(jù)庫中默認(rèn)的隔離級別為Repeatable read (可重復(fù)讀)。
在MySQL數(shù)據(jù)庫中,支持上面四種隔離級別,默認(rèn)的為Repeatable read (可重復(fù)讀);而在Oracle數(shù)據(jù)庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認(rèn)的為Read committed級別。
查看mysql數(shù)據(jù)庫事務(wù)的默認(rèn)隔離級別
SELECT @@tx_isolation;
⑷ 持久性(Durability)
持久性,是指一個(gè)事務(wù)一旦被提交了,那么對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久性的,即便是在數(shù)據(jù)庫系統(tǒng)遇到故障的情況下也不會(huì)丟失提交事務(wù)的操作。
例如我們在使用JDBC操作數(shù)據(jù)庫時(shí),在提交事務(wù)方法后,提示用戶事務(wù)操作完成,當(dāng)我們程序執(zhí)行完成直到看到提示后,就可以認(rèn)定事務(wù)以及正確提交,即使這時(shí)候數(shù)據(jù)庫出現(xiàn)了問題,也必須要將我們的事務(wù)完全執(zhí)行完成,否則就會(huì)造成我們看到提示事務(wù)處理完畢,但是數(shù)據(jù)庫因?yàn)楣收隙鴽]有執(zhí)行事務(wù)的重大錯(cuò)誤。
2、spring-data-jpa事務(wù)管理
1)默認(rèn)事務(wù)
? Spring Data 提供了默認(rèn)的事務(wù)處理方式,即所有的查詢均聲明為只讀事務(wù)。確保了單個(gè)請求過程數(shù)據(jù)的一致性。
對于自定義的方法,如需改變 SpringData 提供的事務(wù)默認(rèn)方式,可以在方法上注解@Transactional聲明進(jìn)行多個(gè) Repository操作時(shí),也應(yīng)該使它們在同一個(gè)事務(wù)中處理,按照分層架構(gòu)的思想,這部分屬于業(yè)務(wù)邏輯層,因此,需要在Service 層實(shí)現(xiàn)對多個(gè) Repository的調(diào)用,并在相應(yīng)的方法上聲明事務(wù)。?
? ? Repository概念:按照最初提出者的介紹,它是銜接數(shù)據(jù)映射層和域之間的一個(gè)紐帶,作用相當(dāng)于一個(gè)在內(nèi)存中的域?qū)ο蠹稀?蛻舳藢ο蟀巡樵兊囊恍?shí)體進(jìn)行組合,并把它們提交給Repository。對象能夠從Repository中移除或者添加,就好比這些對象在一個(gè)Collection對象上就行數(shù)據(jù)操作,同時(shí)映射層的代碼會(huì)對應(yīng)的從數(shù)據(jù)庫中取出相應(yīng)的數(shù)據(jù)。
? ? 從概念上講,Repository是把一個(gè)數(shù)據(jù)存儲區(qū)的數(shù)據(jù)給封裝成對象的集合并提供了對這些集合的操作。
? ? 廣義上可以理解為我們常說的DAO
2)dao層代碼
@Modifying @Query(value="UPDATE hr_employee_contract t SET t.deleteStatus=1 WHERE t.id=?1",nativeQuery = true) void delete(Long id);說明:@Modifying注解
①在@Query注解中,編寫JPQL實(shí)現(xiàn)DELETE和UPDATE操作的時(shí)候,必須加上@modifying注解,以通知Spring Data 這是一個(gè)DELETE或UPDATE操作。
②UPDATE或者DELETE操作需要使用事務(wù),此時(shí)需要定義Service層,在Service層的方法上添加事務(wù)操作。
③注意JPQL不支持INSERT操作。
3)service層代碼
使用@Transactional手動(dòng)開啟事務(wù)管理
@Transactional @Override public void delete(Long id) {employeeContractDao.delete(id); }@Transactional注解支持9個(gè)屬性的設(shè)置,其中使用較多的三個(gè)屬性:readOnly、propagation、isolation。其中propagation屬性用來枚舉事務(wù)的傳播行為,isolation用來設(shè)置事務(wù)隔離級別,readOnly進(jìn)行讀寫事務(wù)控制。
① readOnly
從這一點(diǎn)設(shè)置的時(shí)間點(diǎn)開始(時(shí)間點(diǎn)a)到這個(gè)事務(wù)結(jié)束的過程中,其他事務(wù)所提交的數(shù)據(jù),該事務(wù)將看不見!(查詢中不會(huì)出現(xiàn)別人在時(shí)間點(diǎn)a之后提交的數(shù)據(jù))
應(yīng)用場合:
NO.1、如果你一次執(zhí)行單條查詢語句,則沒有必要啟用事務(wù)支持,數(shù)據(jù)庫默認(rèn)支持SQL執(zhí)行期間的讀一致性;?
NO.2、如果你一次執(zhí)行多條查詢語句,例如統(tǒng)計(jì)查詢,報(bào)表查詢,在這種場景下,多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之后,后條SQL查詢之前,數(shù)據(jù)被其他用戶改變,則該次整體的統(tǒng)計(jì)查詢將會(huì)出現(xiàn)讀數(shù)據(jù)不一致的狀態(tài),此時(shí),應(yīng)該啟用事務(wù)支持。
【注意是一次執(zhí)行多次查詢來統(tǒng)計(jì)某些信息,這時(shí)為了保證數(shù)據(jù)整體的一致性,要用只讀事務(wù)】
怎樣設(shè)置:
對于只讀查詢,可以指定事務(wù)類型為readonly,即只讀事務(wù)。
由于只讀事務(wù)不存在數(shù)據(jù)的修改,因此數(shù)據(jù)庫將會(huì)為只讀事務(wù)提供一些優(yōu)化手段,例如Oracle對于只讀事務(wù),不啟動(dòng)回滾段,不記錄回滾log。
(1)在JDBC中,指定只讀事務(wù)的辦法為: connection.setReadOnly(true);
(2)在Hibernate中,指定只讀事務(wù)的辦法為: session.setFlushMode(FlushMode.NEVER);?
此時(shí),Hibernate也會(huì)為只讀事務(wù)提供Session方面的一些優(yōu)化手段
(3)在Spring的Hibernate封裝中,指定只讀事務(wù)的辦法為: bean配置文件中,prop屬性增加“readOnly”
或者用注解方式@Transactional(readOnly=true)
【 if the transaction is marked as read-only, Spring will set the Hibernate Session’s flush mode to FLUSH_NEVER,?
and will set the JDBC transaction to read-only】也就是說在Spring中設(shè)置只讀事務(wù)是利用上面兩種方式
② propagation
//支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù)。Spring默認(rèn)事務(wù)級別。 int PROPAGATION_REQUIRED = 0; ?//支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。 int PROPAGATION_SUPPORTS = 1; ?//支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就拋出異常。 int PROPAGATION_MANDATORY = 2; ?//新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。執(zhí)行新事務(wù)后,再激活當(dāng)前事務(wù)。 int PROPAGATION_REQUIRES_NEW = 3; ?//以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。 int PROPAGATION_NOT_SUPPORTED = 4; ?//以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。 int PROPAGATION_NEVER = 5;//如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則進(jìn)行與PROPAGATION_REQUIRED類似的操作。//嵌套時(shí)由外部事務(wù)決定,子事務(wù)是否是commit還是rollback。//一般在外部事務(wù)是使用try{}catch(嵌套事務(wù)方法){}進(jìn)行編碼。 int PROPAGATION_NESTED = 6;?案例分析1:
@Service class A{@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED) ? ?void call(){try{b.call();} catch(Exception e){//doSomething....//不拋異常,則A無法提交}//doSomething....} }@Service class B{@Transactional(propagation = Propagation.REQUIRED)void call(){} }A和B共用事務(wù),如果B異常。A未使用try..catch..捕獲,則AB一起回滾。
如果B異常。A捕獲,但并未拋出。則A最終也無法提交,因?yàn)锽的事務(wù)已經(jīng)被設(shè)置為rollback-only了。
案例分析2:
@Service class A{@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED) ? ?void call(){try{b.call();} catch(Exception e){throw e; //或者不拋}//doSomething....} }@Service class B{@Transactional(propagation = Propagation.REQUIRES_NEW)void call(){} }執(zhí)行b.call()時(shí)A事務(wù)掛起,此時(shí)如果B執(zhí)行異常。被A捕獲,如果拋出異常,則AB回滾;如果A捕獲未拋異常,則A繼續(xù)執(zhí)行不回滾。
執(zhí)行b.call()時(shí)A事務(wù)掛起,此時(shí)如果B正常執(zhí)行,而在A中出現(xiàn)異常。則B不回滾,A回滾。
案例分析3:
@Service class A{@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED) ? ?void call(){try{b.call();} catch(Exception e){throw e; //或者不拋}//doSomething....} }@Service class B{@Transactional(propagation = Propagation.NESTED)void call(){} }執(zhí)行b.call()時(shí)A事務(wù)掛起,B新起事務(wù)并設(shè)置SavePoint。如果B正常執(zhí)行,A出現(xiàn)異常,則AB一起回滾。
如果B失敗異常,此時(shí)A如果捕獲但未拋出,后續(xù)A正常執(zhí)行的話,A可以提交,而B已經(jīng)回滾。
如果B失敗異常,此時(shí)A如果捕獲且拋出,則AB一起回滾。
以上案例,我們可以得出第1種和第3種模式的區(qū)別,第3種在嵌套模式下,可以在內(nèi)部異常下執(zhí)行其它業(yè)務(wù)且外部正常提交,而第1種不可以這么操作。
③ isolation?
? ? @Override@Transactional(isolation = Isolation.READ_COMMITTED)public void test() {User user = userMapper.getOne(1L);System.out.println(user.getName());userMapper.updateUser();try {Thread.sleep(10000); // 10 s} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end");}@Transactional 默認(rèn)值
3、spring事務(wù)詳解
? ? Spring 為事務(wù)管理提供了豐富的功能支持。
? ? Spring 事務(wù)管理分為編碼式和聲明式的兩種方式。編程式事務(wù)指的是通過編碼方式實(shí)現(xiàn)事務(wù);聲明式事務(wù)基于 AOP,將具體業(yè)務(wù)邏輯與事務(wù)處理解耦。聲明式事務(wù)管理使業(yè)務(wù)代碼邏輯不受污染, 因此在實(shí)際使用中聲明式事務(wù)用的比較多。
? ? 聲明式事務(wù)有兩種方式:一種是在配置文件(xml)中做相關(guān)的事務(wù)規(guī)則聲明,另一種是基于@Transactional 注解的方式。注釋配置是目前流行的使用方式,此處重介紹基于@Transactional 注解的事務(wù)管理。
1)@Transactional 注解管理事務(wù)的實(shí)現(xiàn)步驟
使用@Transactional 注解管理事務(wù)的實(shí)現(xiàn)步驟分為兩步。
第一步,配置
在 xml 配置類或配置文件中添加如的事務(wù)配置信息。除了用配置文件的方式,@EnableTransactionManagement 注解也可以啟用事務(wù)管理功能。這里以簡單的 DataSourceTransactionManager 為例。
1、配置類
?
package com.myfutech.market.service.provider.config;import com.myfutech.common.spring.jpa.base.impl.BaseJpaRepositoryImpl; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource; import java.util.Properties;@Configuration @ConditionalOnClass(HikariDataSource.class) @EnableConfigurationProperties(ConnectionPoolProperties.class) @EnableJpaRepositories(entityManagerFactoryRef = "marketServiceEntityManagerFactory",transactionManagerRef = "marketServiceTransactionManager", basePackages="com.myfutech.market.service.provider.dao",repositoryBaseClass = BaseJpaRepositoryImpl.class) public class JpaConfig {@Value("${marketService.url}")private String url;@Value("${marketService.username}")private String username;@Value("${marketService.password}")private String password;@Autowiredprivate ConnectionPoolProperties properties;@Bean("marketServiceTransactionManager")PlatformTransactionManager marketServiceTransactionManager() {return new JpaTransactionManager(marketServiceEntityManagerFactory().getObject());}@Bean("marketServiceEntityManagerFactory")LocalContainerEntityManagerFactoryBean marketServiceEntityManagerFactory() {HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();vendorAdapter.setGenerateDdl(true);LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();Properties properties = new Properties();properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");properties.setProperty("hibernate.jdbc.batch_size", "20");properties.setProperty("current_session_context_class", "jpa");properties.setProperty("hibernate.ejb.entitymanager_factory_name", "marketServiceEntityManagerFactory");properties.setProperty("hibernate.hbm2ddl.auto", "none");factoryBean.setJpaProperties(properties);factoryBean.setDataSource(marketServiceDataSource());factoryBean.setJpaVendorAdapter(vendorAdapter);factoryBean.setPackagesToScan("com.myfutech.market.service.provider.model");return factoryBean;}@BeanJdbcTemplate initJdbcTemplate(){return new JdbcTemplate(marketServiceDataSource());}@Bean("marketServiceDataSource")DataSource marketServiceDataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setJdbcUrl(url);dataSource.setDriverClassName(properties.getDriverClass());dataSource.addDataSourceProperty("cachePrepStmts", properties.isCachePrepStmts());dataSource.addDataSourceProperty("prepStmtCacheSize", properties.getPrepStmtCacheSize());dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", properties.getPrepStmtCacheSqlLimit());dataSource.addDataSourceProperty("useServerPrepStmts", properties.isUseServerPrepStmts());dataSource.addDataSourceProperty("useLocalSessionState", properties.isUseLocalSessionState());dataSource.addDataSourceProperty("rewriteBatchedStatements", properties.isRewriteBatchedStatements());dataSource.addDataSourceProperty("cacheResultSetMetadata", properties.isCacheResultSetMetadata());dataSource.addDataSourceProperty("cacheServerConfiguration", properties.isCacheServerConfiguration());dataSource.addDataSourceProperty("elideSetAutoCommits", properties.isElideSetAutoCommits());dataSource.addDataSourceProperty("maintainTimeStats", properties.isMaintainTimeStats());return dataSource;} }2、或者在 xml 配置中的事務(wù)配置信息?
清單1
<tx:annotation-driven /><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /> </bean>
第二步,添加
將@Transactional 注解添加到合適的方法上,并設(shè)置合適的屬性信息。@Transactional 注解的屬性信息如表 1 展示。
表 1. @Transactional 注解的屬性信息
① @Transactional 注解也可添加在類級別上。當(dāng)把@Transactional 注解放在類級別時(shí),表示所有該類的公共方法都配置相同的事務(wù)屬性信息。如下面類,EmployeeService 的所有方法都支持事務(wù)并且是只讀。
② 當(dāng)類級別配置了@Transactional,方法級別也配置了@Transactional,應(yīng)用程序會(huì)以方法級別的事務(wù)屬性信息來管理事務(wù),換言之,方法級別的事務(wù)屬性信息會(huì)覆蓋類級別的相關(guān)配置信息。
@Transactional 注解的類級別支持
清單 2
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true) @Service(value ="employeeService") public class EmployeeService? ? ? ? 到此,您會(huì)發(fā)覺使用@Transactional 注解管理事務(wù)的實(shí)現(xiàn)步驟很簡單。但是如果對 Spring 中的 @transaction 注解的事務(wù)管理理解的不夠透徹,就很容易出現(xiàn)錯(cuò)誤,比如事務(wù)應(yīng)該回滾(rollback)而沒有回滾事務(wù)的問題。接下來,將首先分析 Spring 的注解方式的事務(wù)實(shí)現(xiàn)機(jī)制,然后列出相關(guān)的注意事項(xiàng),以最終達(dá)到幫助開發(fā)人員準(zhǔn)確而熟練的使用 Spring 的事務(wù)的目的。
2)Spring 的注解方式的事務(wù)實(shí)現(xiàn)機(jī)制
? ? ? ? 在應(yīng)用系統(tǒng)調(diào)用聲明@Transactional 的目標(biāo)方法時(shí),Spring Framework 默認(rèn)使用 AOP 代理,在代碼運(yùn)行時(shí)生成一個(gè)代理對象,根據(jù)@Transactional 的屬性配置信息,這個(gè)代理對象決定該聲明@Transactional 的目標(biāo)方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時(shí),會(huì)在在目標(biāo)方法開始執(zhí)行之前創(chuàng)建并加入事務(wù),并執(zhí)行目標(biāo)方法的邏輯, 最后根據(jù)執(zhí)行情況是否出現(xiàn)異常,利用抽象事務(wù)管理器(圖 2 有相關(guān)介紹)AbstractPlatformTransactionManager 操作數(shù)據(jù)源 DataSource 提交或回滾事務(wù), 如圖 1 所示。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 1. Spring 事務(wù)實(shí)現(xiàn)機(jī)制
? ? ? ? Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,圖 1 是以 CglibAopProxy 為例,對于 CglibAopProxy,需要調(diào)用其內(nèi)部類的 DynamicAdvisedInterceptor 的 intercept 方法。對于 JdkDynamicAopProxy,需要調(diào)用其 invoke 方法。
? ? ? ? 正如上文提到的,事務(wù)管理的框架是由抽象事務(wù)管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務(wù)處理實(shí)現(xiàn),由 PlatformTransactionManager 的具體實(shí)現(xiàn)類來實(shí)現(xiàn),如事務(wù)管理器 DataSourceTransactionManager。不同的事務(wù)管理器管理不同的數(shù)據(jù)資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
PlatformTransactionManager,AbstractPlatformTransactionManager 及具體實(shí)現(xiàn)類關(guān)系如圖 2 所示。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 2. TransactionManager 類結(jié)構(gòu)
3)注解方式的事務(wù)使用注意事項(xiàng)
? ? 當(dāng)您對 Spring 的基于注解方式的實(shí)現(xiàn)步驟和事務(wù)內(nèi)在實(shí)現(xiàn)機(jī)制有較好的理解之后,就會(huì)更好的使用注解方式的事務(wù)管理,避免當(dāng)系統(tǒng)拋出異常,數(shù)據(jù)不能回滾的問題。
正確的設(shè)置@Transactional 的 propagation 屬性
需要注意下面三種 propagation 可以不啟動(dòng)事務(wù)。本來期望目標(biāo)方法進(jìn)行事務(wù)管理,但若是錯(cuò)誤的配置這三種 propagation,事務(wù)將不會(huì)發(fā)生回滾。
TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
TransactionDefinition.PROPAGATION_NEVER:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則拋出異常。
正確的設(shè)置@Transactional 的 rollbackFor 屬性
默認(rèn)情況下,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務(wù);除此之外,Spring 不會(huì)回滾事務(wù)。
如果在事務(wù)中拋出其他類型的異常,并期望 Spring 能夠回滾事務(wù),可以指定 rollbackFor。例:
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
通過分析 Spring 源碼可以知道,若在目標(biāo)方法中拋出的異常是 rollbackFor 指定的異常的子類,事務(wù)同樣會(huì)回滾。
清單 3. RollbackRuleAttribute 的 getDepth 方法
private int getDepth(Class<?> exceptionClass, int depth) {if (exceptionClass.getName().contains(this.exceptionName)) {// Found it!return depth;}// If we've gone as far as we can go and haven't found it...if (exceptionClass == Throwable.class) {return -1;}return getDepth(exceptionClass.getSuperclass(), depth + 1);}
@Transactional 只能應(yīng)用到 public 方法才有效
只有@Transactional 注解應(yīng)用到 public 方法,才能進(jìn)行事務(wù)管理。這是因?yàn)樵谑褂?Spring AOP 代理時(shí),Spring 在調(diào)用在圖 1 中的 TransactionInterceptor 在目標(biāo)方法執(zhí)行前后進(jìn)行攔截之前,DynamicAdvisedInterceptor(CglibAopProxy 的內(nèi)部類)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會(huì)間接調(diào)用 AbstractFallbackTransactionAttributeSource(Spring 通過這個(gè)類獲取表 1. @Transactional 注解的事務(wù)屬性配置屬性信息)的 computeTransactionAttribute 方法。
清單 4. AbstractFallbackTransactionAttributeSource
protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}這個(gè)方法會(huì)檢查目標(biāo)方法的修飾符是不是 public,若不是 public,就不會(huì)獲取@Transactional 的屬性配置信息,最終會(huì)造成不會(huì)用 TransactionInterceptor 來攔截該目標(biāo)方法進(jìn)行事務(wù)管理。
避免 Spring 的 AOP 的自調(diào)用問題
在 Spring 的 AOP 代理下,只有目標(biāo)方法由外部調(diào)用,目標(biāo)方法才由 Spring 生成的代理對象來管理,這會(huì)造成自調(diào)用問題。若同一類中的其他沒有@Transactional 注解的方法內(nèi)部調(diào)用有@Transactional 注解的方法,有@Transactional 注解的方法的事務(wù)被忽略,不會(huì)發(fā)生回滾。見清單 5 舉例代碼展示。
清單 5.自調(diào)用問題舉例
? ? @Servicepublic class OrderService {private void insert() {insertOrder();}@Transactionalpublic void insertOrder() {//insert log info//insertOrder//updateAccount}}insertOrder 盡管有@Transactional 注解,但它被內(nèi)部方法 insert 調(diào)用,事務(wù)被忽略,出現(xiàn)異常事務(wù)不會(huì)發(fā)生回滾。
上面的兩個(gè)問題@Transactional 注解只應(yīng)用到 public 方法和自調(diào)用問題,是由于使用 Spring AOP 代理造成的。為解決這兩個(gè)問題,使用 AspectJ 取代 Spring AOP 代理。
需要將下面的 AspectJ 信息添加到 xml 配置信息中。
清單 6. AspectJ 的 xml 配置信息
<tx:annotation-driven mode="aspectj" /><bean id="transactionManager"? class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /> </bean><bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf"><property name="transactionManager" ref="transactionManager" /> </bean>同時(shí)在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。
清單 7. AspectJ 的 pom 配置信息
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.2.RELEASE</version> </dependency> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version> </dependency> <plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.9</version><configuration><showWeaveInfo>true</showWeaveInfo><aspectLibraries><aspectLibrary><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></aspectLibrary></aspectLibraries></configuration><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions> </plugin>
?
?
總結(jié)
以上是生活随笔為你收集整理的Spring Data JPA事务管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java加密与解密的艺术~DigestI
- 下一篇: 软件测试面试题linux,linux基础