如果一个BEAN类上加了@Transactional,则默认的该类及其子类的公开方法均会开启事务,但有时某些业务场景下某些公开的方法可能并不需要事务,那这种情况该如何做呢?
常规的做法:
针对不同的场景及事务传播特性,定义不同的公开方法【哪怕是同一种业务】,并在方法上添加@Transactional且指明不同的传播特性,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Service @Transactional public class DemoSerivce { @Transactional(propagation = Propagation.SUPPORTED) public int getValue(){ return 0; } 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获取即可,如果没理解,下面写一个手写代理示例,大家就明白了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class DemoService{ public int getValue(){ return 666; } public DemoService getReal(){ 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也会被强制返回代理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
public interface ProxyableBeanAccessor<T extends ProxyableBeanAccessor> extends RawTargetAccess { String CONTEXT_KEY_REAL_GET = "proxyable_bean_accessor_real_get";
@SuppressWarnings("unchecked") @Transactional(propagation = Propagation.NOT_SUPPORTED) default T getReal() { return (T) this; }
@SuppressWarnings("unchecked") @Transactional(propagation = Propagation.NOT_SUPPORTED) default T getProxy() { return (T) SpringUtils.getBean(this.getClass()); }
@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的工具类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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动态根据条件来判断是否需要代理或原生对象,使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @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){ 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); demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query); ContextUtils.get().addGlobalVariable(ProxyableBeanAccessor.CONTEXT_KEY_REAL_GET,TidbDataSourceSwitcher.isUsingTidbDataSource()); demoService.selfAs(null).selectByMergerParam(query); demoService.getProxy().selectByMergerParam(query);
|
通过上述示例代码可以看到,借助于ProxyableBeanAccessor接口默认实现的getReal、getProxy、selfAs方法,可以很灵活的实现按需获取代理或非代理对象。