如果一個BEAN類上加了@Transactional,則默認的該類及其子類的公開方法均會開啟事務,但有時某些業務場景下某些公開的方法可能并不需要事務,那這種情況該如何做呢?
常規的做法:
針對不同的場景及事務傳播特性,定義不同的公開方法【哪怕是同一種業務】,并在方法上添加@Transactional且指明不同的傳播特性,示例代碼如下:
@Service
@Transactional
public class DemoSerivce {
//SUPPORTED 若無事務傳播則默認不會有事務,若有事務傳播則會開啟事務
@Transactional(propagation = Propagation.SUPPORTED)
public int getValue(){
return 0;
}
//默認開啟事務(由類上的@Transactional決定的)
public int getValueWithTx(){
return 0;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int getValueWithoutTx(){
return 0;
}
}
上述這樣的弊端就是:若是同一個邏輯但如果要精細控制事務,則需要定義3個方法來支持,getValue、getValueWithTx、getValueWithoutTx 3個方法,分別對應3種事務場景,這種代碼就顯得冗余過多,那有沒有簡單一點的方案呢?其實是有的,
宣告式事務的本質是通過AOP切面,在代理執行原始方法【即:被標注了@Transactional的公開方法】前開啟DB事務,在執行后提交DB事務(若拋錯則執行回滾),如果要想事務不生效,則讓AOP失效即可,即:調原生的service Bean公開方法而不是代理類的公開方法,那如何獲得原生的BEAN類呢,答案是:在原生BEAN方法內部通過this獲取即可,如果沒理解,下面寫一個手寫代理示例,大家就明白了:
public class DemoService{
public int getValue(){
return 666;
}
public DemoService getReal(){
//這里的this指向的就是當前的自己,并非代理物件
return this;
}
}
public class DemoServiceProxy{
private DemoService target;
public DemoServiceProxy(DemoService target){
this.target=target;
}
public int getValue(){
//增強:開啟事務
return target.getValue()+222;
//增強:提交事務
}
public DemoService getReal(){
//這里就會間接的把原生的物件傳遞回傳
return target.getReal();
}
}
public static void main(String[] args) {
DemoServiceProxy proxy=new DemoServiceProxy(new DemoService());
System.out.println("proxy class:" +proxy.getClass().getName());
System.out.println("real class:" +proxy.getReal().getClass().getName());
System.out.println("proxy getValue result:" + proxy.getValue() );
System.out.println("real getValue result:" + proxy.getReal().getValue() );
}
最終的輸出結果為:
proxy class:...DemoServiceProxy
real class:...DemoService →原始的物件
proxy getValue result:888
real getValue result:666
通過DEMO證實了通過避開代理的方案是正確的,而且非常簡單,那么有了這個基礎,再應用到實際的代碼中則很簡單,想控制有事務則取代理物件,想控制不要事務則取原生物件即可,就是這么簡單,
下面貼出核心也是全部的ProxyableBeanAccessor代碼:(注意必需擴展自RawTargetAccess,否則即使回傳this也會被強制回傳代理)
/**
* @author zuowenjun
* @date 2022/12/5 22:03
* @description 可代理BEAN訪問者介面(支持獲取代理的真實物件、獲取代理物件)
*/
public interface ProxyableBeanAccessor<T extends ProxyableBeanAccessor> extends RawTargetAccess {
String CONTEXT_KEY_REAL_GET = "proxyable_bean_accessor_real_get";
/**
* 獲取代理的真實物件(即:未被代理的物件)
* 注:若呼叫未被代理的bean的公開方法,則均不會再走AOP切面
*
* @return 未被代理的物件Bean
*/
@SuppressWarnings("unchecked")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T getReal() {
return (T) this;
}
/**
* 獲取當前類的代理物件(即:已被代理的物件)
* 注:若呼叫已被代理的物件Bean的公開方法,則相關AOP切面均可正常攔截與執行
*
* @return 已被代理的物件Bean
*/
@SuppressWarnings("unchecked")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T getProxy() {
return (T) SpringUtils.getBean(this.getClass());
}
/**
* 將當前BEAN轉換為代理物件或真實物件
*
* @param realGet 是否轉換獲取真實物件
* @return 未被代理的物件Bean OR 已被代理的物件Bean
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T selfAs(Supplier<Boolean> realGet) {
Boolean needGetReal = false;
if (realGet == null) {
if (ContextUtils.get() != null) {
needGetReal = (Boolean) ContextUtils.get().getGlobalVariableMap().getOrDefault(CONTEXT_KEY_REAL_GET, false);
}
} else {
needGetReal = realGet.get();
}
return Boolean.TRUE.equals(needGetReal) ? getReal() : getProxy();
}
}
其中,SpringUtils是一個獲取BEAN的工具類,代碼如下:
public SpringUtils implements ApplicationContextAware{
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context=applicationContext;
}
public static <T> getBean(Class<T> clazz){
return context.getBean(clazz);
}
}
ContextUtils只是一個內部定義了一個ThreadLocal的靜態map欄位,用于存放執行緒背景關系要傳遞的物件,
使用方法:只需將原來Service的子類或其它可能被切面代理的類 加上實作自ProxyableBeanAccessor即可,然后在這個類里面或外部呼叫均可通過getReal獲得原生物件、getProxy獲得代理物件、selfAs動態根據條件來判斷是否需要代理或原生物件,使用示例如下:
//serive BEAN定義
@Service
@Transactional
public class DemoService implements ProxyableBeanAccessor<DemoService> {
... ...
public Demo selectByMergerParam(Demo demo){
return getMapper().selectByMergerParam(demo);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public Demo selectByMergerParam2(Demo demo){
//通過getProxy獲取當前類的代理BEAN,以便可以執行事務切面
return getProxy().doSelectByMergerParam(demo);
}
public Demo doSelectByMergerParam(Demo demo){
return getMapper().selectByMergerParam(demo);
}
}
//具體使用:
@Autowired
private DemoService demoService;
Demo query = new Demo ();
query.setWaybillNumber("123455667");
//示例一:獲取原生物件查詢,由于沒有代理,則無事務
demoService.getReal().selectByMergerParam(query);
//示例二:根據LAMBDA運算式的布林值來決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生物件,即:不開事務)
demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query);
//示例三:根據執行緒背景關系設定的布林值來決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生物件,即:不開事務),這種方式主要是為了簡化大批量的動態邏輯判斷的場景,
// 一次設定同執行緒的所有ProxyableBeanAccessor的子類的selfAs(null)均可自動判斷
ContextUtils.get().addGlobalVariable(ProxyableBeanAccessor.CONTEXT_KEY_REAL_GET,TidbDataSourceSwitcher.isUsingTidbDataSource());
demoService.selfAs(null).selectByMergerParam(query);
//示例四:獲取代理物件,一般用于BEAN內部方法之間呼叫,外部呼叫其實本身就是代理無意義
demoService.getProxy().selectByMergerParam(query);
通過上述示例代碼可以看到,借助于ProxyableBeanAccessor介面默認實作的getReal、getProxy、selfAs方法,可以很靈活的實作按需獲取代理或非代理物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/548956.html
標籤:其他
