如果一个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方法,可以很灵活的实现按需获取代理或非代理对象。

任何Bean通过实现ProxyableBeanAccessor接口即可获得动态灵活的获取代理对象或原生对象的能力的更多相关文章

  1. Envelope几何对象 Curve对象几何对象 Multipatch几何对象 Geometry集合接口 IGeometryCollection接口

    Envelope是所有几何对象的外接矩形,用于表示几何对象的最小边框,所有的几何对象都有一个Envelope对象,IEnvelope是Envelope对象的主要接口,通过它可以获取几何对象的XMax, ...

  2. java Web项目Service层通用接口和entityVo对象与entity对象转化问题的解决方案

    Service层的接口中有一些比较常用方法,一次又一次的在新的Service层中被书写,所以懒惰的程序员又烦了,他们决定写个通用接口来解决这个问题. 有些项目中,实体类即承担接收表单数据的任务,又承担 ...

  3. 使用Spring容器动态注册和获取Bean

    有时候需要在运行时动态注册Bean到Spring容器,并根据名称获取注册的Bean.比如我们自己的SAAS架构的系统需要调用ThingsBoard API和Thingsboard交互,就可以通过Thi ...

  4. Activity之间传递数据或数据包Bundle,传递对象,对象序列化,对象实现Parcelable接口

    package com.gaojinhua.android.activitymsg; import android.content.Intent; import android.os.Bundle; ...

  5. 微信公众号平台接口开发:基础支持,获取access_token

    新建Asp.net MVC 4.0项目 WeChatSubscript是项目UI层 WeChatTools是封装操作访问公众号接口的一些方法类库 获取AccssToken 我们要的得到AccessTo ...

  6. 微信公众号平台接口开发:基础支持,获取微信服务器IP地址

    官方说明 目前看不出来这个接口有哪些具体运用,但是既然有这个接口,那我们就试试能不能用 访问接口 修改WeCharBase.cs,新增以下2个方法 public static string Serve ...

  7. cglib动态代理是通过继承父类的方式进行代理的 不是通过接口方式进行动态代理的 因此可以对普通的类进行代理

    cglib动态代理是通过继承父类的方式进行代理的 不是通过接口方式进行动态代理的

  8. Servlet接口的实现类,路径配置映射,ServletConfig对象,ServletContext对象及web工程中文件的读取

    一,Servlet接口实现类:sun公司为Servlet接口定义了两个默认的实现类,分别为:GenericServlet和HttpServlet. HttpServlet:指能够处理HTTP请求的se ...

  9. 无状态会话bean(3)---远程业务接口(没有排版)

    迄今为止,我们仅仅讨论了使用一个本地业务接口的会话bean.在这样的情况下.本地意味着仅仅能由执行在同一个应用程序server实例的JavaEE组件声明会话bean的依赖性.比如.远程client不可 ...

  10. spring4.1.8扩展实战之五:改变bean的定义(BeanFactoryPostProcessor接口)

    本章我们继续实战spring的扩展能力,通过自定义BeanFactoryPostProcessor接口的实现类,来对bean实例做一些控制: 原文地址:https://blog.csdn.net/bo ...

随机推荐

  1. vue学习笔记 三、文件和目录结构

    系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...

  2. ASIC 功能验证SVTB

    System Verilog进行验证是可以不综合的 发现DUT中的功能问题 预备知识:Linux/verilog/gvim System Verilog学习目录 System Verilog Test ...

  3. SD-Host控制器设计架构

    SD Host功能列表 SD Host挂接在SoC中,与外部的SD card进行交互 有控制寄存器和状态寄存器,SoC往往有CPU,通过CPU进行配置寄存器,有些SoC没有CPU,需要使用I2C或者S ...

  4. Clock Domain Crossing

    Clock Domain Crossing CDC问题主要有亚稳态问题,多比特信号同步,握手信号同步,异步Fifo等 Topics Describe the SoC Design Issues Und ...

  5. 使用pip安装pycharm插件时,要使用管理员权限打开cmd安装

    1.问题 安装到一半报错 报错1 报错2 2.解决 解决1 原文:https://blog.csdn.net/weixin_44899752/article/details/128372969 下面是 ...

  6. 探讨Java死锁的现象和解决方法

    死锁是多线程编程中常见的问题,它会导致线程相互等待,无法继续执行.在Java中,死锁是一个需要注意和解决的重要问题.让我们通过一系列详细的例子来深入了解Java死锁的现象和解决方法. 1. 什么是死锁 ...

  7. 【面试题精讲】MySQL中覆盖索引是什么

    有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 在MySQL中,覆盖索引是一种特殊类型的索引,它 ...

  8. [转帖]Prometheus-使用python开发exporter

    exporter有很多,但想要特定需求的话,还需自行开发.在这里使用python写一个exporter,用于监控/root下的目录数量. 开发exporter需要使用prometheus_client ...

  9. [转帖]Region Merge Config

    TiKV replicates a segment of data in Regions via the Raft state machine. As data writes increase, a ...

  10. [转帖]JVM性能提升50%,聊一聊背后的秘密武器Alibaba Dragonwell

    https://zhuanlan.zhihu.com/p/453437019 今年四月五日,阿里云开放了新一代ECS实例的邀测[1],Alibaba Dragonwell也在新ECS上进行了极致的优化 ...