JAVA && Spring && SpringBoot2.x — 學(xué)習(xí)目錄
1. 編程式事務(wù)和聲明式事務(wù)
spring支持編程式事務(wù)管理和聲明式事務(wù)管理兩種方式。
編程式事務(wù)Spring推薦使用
TransactionTemplate
。需要在業(yè)務(wù)代碼中摻雜事務(wù)管理的代碼蜂厅,粒度可以作用到代碼塊級別廓旬。聲明式事務(wù)管理建立在
AOP
之上的纺座。其本質(zhì)是對方法前后進(jìn)行攔截荷腊,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個事務(wù)素征,在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。聲明式事務(wù)可以在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或基于@Transactional
注解的方式)梧兼,不需要侵入業(yè)務(wù)代碼放吩。但是聲明式事務(wù)粒度只能做到方法級別。
Spring基于XML的聲明式事務(wù)配置:
- 配置
事務(wù)管理器
羽杰,將數(shù)據(jù)源交由事務(wù)管理器屎慢。
<bean id="transactionManager" class="...DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 配置
事務(wù)通知
,根據(jù)方法名忽洛,指定事務(wù)的屬性腻惠。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation='"SUPPORTS">
<tx:method name="purchase" protagation="REQUIRED">
</tx:attributes>
</tx:advice>
- 配置
aop
,將事務(wù)和切點關(guān)聯(lián)起來欲虚。
<aop:config>
<aop:pointcut expresssion="execution(* com.yxr.*(...))" id="txPointcut">
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
Spring事務(wù)管理集灌,本質(zhì)上就是AOP編程的實現(xiàn),將數(shù)據(jù)源配置到數(shù)據(jù)管理器之后复哆。在配置事務(wù)通知欣喧,規(guī)定哪些方法需要事務(wù)。最后將事務(wù)通知和切點整合起來梯找。
Spring基于注解的聲明式事務(wù)配置:
- 配置
事務(wù)管理器
唆阿,將數(shù)據(jù)源交由事務(wù)管理器。
<bean id="transactionManager" class="...DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 啟動事務(wù)注解
<tx:annotation-driven transaction-manager="transactionManager">
- 可以在對應(yīng)方法上添加Transaction注解
@Transaction
public void purchase(){}
SpringBoot如何使用事務(wù):
Springboot默認(rèn)集成事物,只要在方法上加上@Transactional即可
2. Spring事務(wù)的使用和注意事項
說起來Spring事務(wù)锈锤,我們需要先讀一下系統(tǒng)之間那點事-異常知多少驯鳖,復(fù)習(xí)下Java異常的東西。
注意事項:
- @Transaction可以用于接口以及接口方法上久免,類以及類方法上浅辙。當(dāng)作用于類上時,該類的所有
public
方法都具有該類型的事務(wù)屬性阎姥。同時可以使用在方法級別使用注解來覆蓋類級別的定義记舆。 - Spring不建議
@Transaction
注解作用于接口以及接口方法上,因為這只有在使用基于接口的代理事務(wù)情況下才會生效呼巴。另外泽腮,@Transaction
注解只能被應(yīng)用到public方法上,這是由于Spring AOP本質(zhì)上決定的衣赶,在其他訪問修飾符的方法使用@Transaction
注解诊赊,事務(wù)不會起作用。 - 只有【外部方法】調(diào)用【事務(wù)方法】時才會被AOP代理捕獲屑埋,也就是說豪筝,類內(nèi)部方法調(diào)用本類事務(wù)方法并不會引起事務(wù)行為。(內(nèi)部方法使用this調(diào)用時:this為目標(biāo)對象)。
- Spring默認(rèn)情況下會對運(yùn)行期異常(非檢查異常
RuntimeException
)或者ERROR進(jìn)行回滾续崖∏媒郑可以使用rollbackFor=Exception.class
對檢查時異常進(jìn)行回滾。 - MySQL事務(wù)庫表引擎應(yīng)為InnoDB严望,否則不支持事務(wù)多艇。
3. Spring事務(wù)的傳播行為
正如行為的英文含義,很好的解釋各種行為的含義:
事務(wù)的傳播(propagation [?pr?p?'ɡe??n])
行為是指:如果在開始當(dāng)前事務(wù)之前像吻,一個事務(wù)上下文已經(jīng)存在峻黍,此時我們可以有多個選項指定事務(wù)性方法的執(zhí)行行為。
-
PROPAGATION_REQUIRED:
[adj 必須的]
默認(rèn)傳播行為拨匆,指的是若當(dāng)前存在事務(wù)姆涩,則加入該事務(wù);如果當(dāng)前沒有事務(wù)惭每,則創(chuàng)建一個新事務(wù)骨饿。 -
PROPAGATION_REQUIRES_NEW:
[v 需要新的]
需要創(chuàng)建一個新的,若當(dāng)前有事務(wù)台腥,則將當(dāng)前事務(wù)掛起宏赘。 -
PROPAGATION_SUPPORTS:
[v 支持]
當(dāng)前存在事務(wù),就在事務(wù)中運(yùn)行黎侈;當(dāng)前不存在事務(wù)察署,則不在事務(wù)中運(yùn)行。 -
PROPAGATION_NOT_SUPPORTED
[v 不被支持]
不運(yùn)行在事務(wù)中峻汉,當(dāng)前有事務(wù)贴汪,則掛掉當(dāng)前事務(wù)。 -
PROPAGATION_NEVER:
[adv 絕不]
不運(yùn)行在事務(wù)中俱济,如果當(dāng)前有事務(wù)嘶是,則拋出異常。 -
PROPAGATION_MANDARORY
[[?m?nd?t?ri]
強(qiáng)制的]`必須運(yùn)行在事務(wù)中蛛碌,如果當(dāng)前方法沒有事務(wù),則拋出異常辖源。 -
PROPAGATION_NESTED
[[nest?d] 嵌套的]
當(dāng)前存在事務(wù)蔚携,則創(chuàng)建一個事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)運(yùn)行,如果當(dāng)前沒有事務(wù)克饶,則創(chuàng)建一個新的事務(wù)酝蜒。
4. Spring事務(wù)隔離級別
隔離級別是指若干個并發(fā)事務(wù)之間的隔離
(ISOLATION [?a?s??le??n])
程度。
- READ_UNCOMMITTED:讀未提交矾湃,一個事務(wù)可以讀取到另一個事務(wù)未提交的數(shù)據(jù)亡脑。(臟讀,不可重復(fù)讀,幻讀)霉咨。
- READ_COMMITTED:讀已提交蛙紫,一個事務(wù)只能讀取另一個事務(wù)已經(jīng)提交的數(shù)據(jù)。(不可重復(fù)讀途戒,幻讀)坑傅。
-
REPEATABLE_READ:可重復(fù)讀
[r??pi:t?bl]
一個事務(wù)在整個過程中多次重復(fù)執(zhí)行某個查詢,每次返回的結(jié)果都相同喷斋。(幻讀) -
SERIALIZABLE:序列化
[s??r??la?'z?bl]
所有事務(wù)依次逐個執(zhí)行唁毒,這樣事務(wù)之間不可能存在干擾。
5. Spring事務(wù)回滾規(guī)則
5.1 Exception異承亲Γ回滾事務(wù)
指示spring事務(wù)管理器回滾一個事務(wù)的推薦方法是在當(dāng)前事務(wù)的上下文拋出異常浆西。Spring事務(wù)管理器會捕獲任何未處理的異常,然后依據(jù)規(guī)則決定是否回滾拋出異常的事務(wù)顽腾。
默認(rèn)配置下近零,spring只有在拋出運(yùn)行時異常(RuntimeException
及其子類)或者Error異常時才會回滾,但是可以配置rollbackFor=Exception.class
將檢查時異常進(jìn)行回滾崔泵。
@Transactional(rollbackFor = Exception.class)
@Override
public int addEmployee(Employee record) {
int delResult = employeeMapper.deleteByPrimaryKey(11);
int addResult = employeeMapper.insert(record);
if (delResult == 0 || addResult == 0) {
throw new RuntimeException("123");
}
return addResult;
}
5.2 手動回滾事務(wù)
Spring事務(wù)原理就是AOP秒赤,即當(dāng)Spring捕獲住RuntimeException異常后,自動執(zhí)行【回滾】操作憎瘸。但是若不想拋出異常(捕獲處理異常)入篮,但依舊想回滾事務(wù),該如何處理幌甘?
我們可以使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
手動進(jìn)行回滾潮售,這樣上層便無需處理異常。
@Transactional
@Override
public int addEmployee(Employee record) {
int delResult = employeeMapper.deleteByPrimaryKey(11);
int addResult = employeeMapper.insert(record);
if (delResult == 0 || addResult == 0) {
//手動回滾锅风,上層無需要處理異常酥诽。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return addResult;
}
5.3 套嵌事務(wù)的回滾
情況大概是這樣的:
- 【事務(wù)嵌套調(diào)用】事務(wù)A方法調(diào)用了事務(wù)B方法;
- 【事務(wù)B出現(xiàn)異持宀海】事務(wù)A捕獲事務(wù)B的異常(其實事務(wù)A不想回滾)肮帐;
- 【事務(wù)A回滾操作】最終事務(wù)A還是回滾了;
public void testA() throws BussinessException {
try {
bService.testB();
} catch (BussinessException e) {
System.out.println(e.getErrorCode());
}
pictureService.addPicture("", "", "a.jpg", "", new File("d:/1.jpg"));
}
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" rollback-for="com.soft.core.BussinessException" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="all" expression="execution(* com.soft.*.*.*ServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="all"/>
</aop:config>
回答:
事務(wù)B方法的Transaction配置propagation屬性是使用的默認(rèn)值(required)边器。這樣的話训枢,本質(zhì)上事務(wù)A和事務(wù)B共用了一個Transaction
。transactionManager中有一個參數(shù):globalRollbackOnParticipationFailure(參與者失敗導(dǎo)致全局回滾)
f (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
// Throw UnexpectedRollbackException only at outermost transaction boundary
// or if explicitly asked to.
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
解決辦法:
- 【方案一】如果想要事務(wù)B失敗不影響事務(wù)A忘巧,可以將事務(wù)B的傳播行為設(shè)置為propagation=Propagation.REQUIRES_NEW恒界。
- 【方案二】將globalRollbackOnParticipationFailure參數(shù)設(shè)置為false。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<property name="globalRollbackOnParticipationFailure" value="false"/>
</bean>
5.4 內(nèi)部方法調(diào)用事務(wù)方法
上面說到砚嘴,【外部方法】調(diào)用【事務(wù)方法】事務(wù)才會生效十酣;【內(nèi)部方法】調(diào)用【本類的事務(wù)方法】涩拙,事務(wù)不會起作用。
service層代碼
@Service
public class TestTransactionService {
@Resource
private UsertMapper usertMapper;
//業(yè)務(wù)邏輯方法
public void businessUsert(Usert usert) {
insertUsert(usert);
// 調(diào)用遠(yuǎn)程接口
}
@Transactional
public void insertUsert(Usert usert) {
usertMapper.insert(usert);
throw new RuntimeException("拋出異常");
}
}
Controller層代碼
@ResponseBody
@RequestMapping(value = "user", method = RequestMethod.GET)
public String showUserName() {
Usert usert = new Usert();
usert.setId(4);
usert.setAge(20);
usert.setUserName("李吉吉");
usert.setPassword("123");
testTransactionService.businessUsert(usert);
return "success";
}
執(zhí)行結(jié)果
16:21:31.251 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
16:21:31.264 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b9581da] was not registered for synchronization because synchronization is not active
16:21:31.354 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@429902b9] will not be managed by Spring
16:21:31.357 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@429902b9]
16:21:31.365 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ==> Preparing: insert into user_t (id, user_name, password, age) values (?, ?, ?, ?)
16:21:31.552 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ==> Parameters: 4(Integer), 李吉吉(String), 123(String), 20(Integer)
16:21:31.593 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b9581da]
16:21:31.597 [http-bio-8081-exec-1] INFO com.springmvc.common.GlobalExceptionHandler - 全局捕獲異常日志打印...
================全局捕獲異常=================
java.lang.RuntimeException: 拋出異常
at com.springmvc.service.TestTransactionService.insertUsert(TestTransactionService.java:34)
at com.springmvc.service.TestTransactionService.businessUsert(TestTransactionService.java:28)
at com.springmvc.service.TestTransactionService$$FastClassBySpringCGLIB$$75b3ded0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:640)
at com.springmvc.service.TestTransactionService$$EnhancerBySpringCGLIB$$1b4acae.businessUsert(<generated>)
at com.springmvc.web.UserController.showUserName(UserController.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
我們可以看到耸采,事務(wù)方法沒有生效(即沒有AOP增強(qiáng)兴泥,也沒有Rollback操作)。因為我們本類方法調(diào)用事務(wù)方法洋幻。
實際開發(fā)中郁轻,我們的【業(yè)務(wù)方法】中可能存在調(diào)用遠(yuǎn)程接口等耗時操作,需要將【事務(wù)】抽取出來文留,但【業(yè)務(wù)方法】調(diào)用【事務(wù)方法】事務(wù)不起作用好唯。那么如何解決?
解決方法:
@Service
public class TestTransactionService {
@Resource
private UsertMapper usertMapper;
/**
* 內(nèi)部方法調(diào)用本類事務(wù)方法的關(guān)鍵燥翅,重新進(jìn)行AOP增強(qiáng)
*/
@Resource
TestTransactionService testTransactionService;
//業(yè)務(wù)邏輯方法
public void businessUsert(Usert usert) {
testTransactionService.insertUsert(usert);
}
@Transactional
public void insertUsert(Usert usert) {
usertMapper.insert(usert);
throw new RuntimeException("拋出異常");
}
}
日志打印
16:39:00.643 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
16:39:00.643 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@508ee7ba]
16:39:00.644 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@4be855b6] will be managed by Spring
16:39:00.644 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@4be855b6]
16:39:00.644 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ==> Preparing: insert into user_t (id, user_name, password, age) values (?, ?, ?, ?)
16:39:00.644 [http-bio-8081-exec-1] DEBUG com.springmvc.generic.mybatis.mapper.UsertMapper.insert - ==> Parameters: 4(Integer), 李吉吉(String), 123(String), 20(Integer)
16:39:00.646 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@508ee7ba]
16:39:00.656 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization rolling back SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@508ee7ba]
16:39:00.656 [http-bio-8081-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@508ee7ba]
16:39:00.658 [http-bio-8081-exec-1] INFO com.springmvc.common.GlobalExceptionHandler - 全局捕獲異常日志打印...
================全局捕獲異常=================
java.lang.RuntimeException: 拋出異常
at com.springmvc.service.TestTransactionService.insertUsert(TestTransactionService.java:37)
at com.springmvc.service.TestTransactionService$$FastClassBySpringCGLIB$$75b3ded0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:708)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at com.springmvc.service.TestTransactionService$$EnhancerBySpringCGLIB$$d0b071e3.insertUsert(<generated>)
at com.springmvc.service.TestTransactionService.businessUsert(TestTransactionService.java:31)
在service類中骑篙,將【本類對象】引入【本類】中,可以看到內(nèi)部方法調(diào)用事務(wù)方法時森书,事務(wù)方法進(jìn)行了AOP增強(qiáng)靶端,即出現(xiàn)異常時回滾。
小伙伴會問凛膏,若是使用@Autowired注解會出現(xiàn)什么情況杨名?
當(dāng)使用@Autowired注解時,用來注入已有的bean猖毫。但是有些時候台谍,會注入失敗。原因就是@Autowired默認(rèn)就是@Autowired(required=true)
吁断,表示注入的時候趁蕊,該bean必須存在,否則就會注入失敗仔役。
org.springframework.beans.factory.BeanCreationException: Error creating
bean with name 'testTransactionService': Injection of autowired dependencies
failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire
field: com.springmvc.service.TestTransactionService
com.springmvc.service.TestTransactionService.testTransactionService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.springmvc.service.TestTransactionService]
found for dependency: expected at least 1 bean which qualifies as autowire
candidate for this dependency. Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
使用@Autowired注入屬性之后掷伙,會但是在bean容器中并沒有該類型的對象,于是在項目啟動的時候出現(xiàn)上圖的錯誤又兵,但若是我們設(shè)置@Autowired(required=false)后任柜,本質(zhì)上,該對象并未注入進(jìn)去沛厨,會出現(xiàn)空指針異常乘盼。
故:我們要使用@Resource屬性。
有些小伙伴會問俄烁,會不會出現(xiàn)循環(huán)依賴呢?
spring是將Bean對象實例化(依賴無參構(gòu)造函數(shù))级野,在設(shè)置對象屬性的值页屠。避免setter和field的循環(huán)依賴粹胯。
循環(huán)依賴其實就是循環(huán)引用,也就是兩個或者兩個以上的bean互相持有對象辰企,最終形成閉環(huán)风纠。
解決Spring循環(huán)依賴的依據(jù)其實是基于Java的引用傳遞,當(dāng)我們獲取對象引用時牢贸,對象的field是可以延后設(shè)置的竹观。【但構(gòu)造器必須是在獲取引用之前】潜索。
- createBeanInstance:實例化臭增,其實就是調(diào)用對象的構(gòu)造方法實例化對象。
- populateBean:填充對象竹习,這一步主要是多bean的依賴屬性進(jìn)行填充誊抛。
- initializeBean:調(diào)用spring xml的init方法。
我們要解決循環(huán)引用也應(yīng)該是從初始化過程著手整陌,對于單例來說拗窃,在spring容器整個生命周期內(nèi),只有一個對象泌辫。保存在Cache中随夸。spring為解決單例的循環(huán)依賴問題,使用了三級緩存震放。
在createBeanInstance之后宾毒,其實單例對象已經(jīng)被創(chuàng)建出來(調(diào)用了構(gòu)造器),雖然不夠完美(未進(jìn)行初始化的第二步和第三步)澜搅,但是已經(jīng)能夠被認(rèn)出來(根據(jù)對象引用能定位到堆中的對象)伍俘。