如果一个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. 如何在 Debian 12 上安装 MariaDB

    MariaDB 是一个开源多线程的关系数据库管理系统,是 MySQL 的替代品. MariaDB 是 Debian 中 MySQL 的默认替换方案. 本教程介绍如何在 Debian 12 上安装 Ma ...

  2. secure boot(三)secure boot的签名和验签方案

    简介 FIT 格式支持存储镜像的hash值,并且在加载镜像时会校验hash值.这可以保护镜像免受破坏,但是,它并不能保护镜像不被替换. 而如果对hash值使用私钥签名,在加载镜像时使用公钥验签则可以保 ...

  3. centos7 docker配置防火墙firewalld

    docker防火墙使用的是底层iptables,封装后的firewalld默认不生效 如果想要使用firewalld,需要做以下调整: 让firewalld移除DOCKER-USER并新建一个 # R ...

  4. 缓存选型:Redis or MemCache

    ★ Redis24篇集合 1 背景 互联网产品为了保证高性能和高可用性,经常会使用缓存来进行架构设计.最常用的就是使用Redis了,也有部分企业会选择使用Memcache. 所以了解 Redis 和 ...

  5. [转帖]GC日志解读,这次别再说看不懂GC日志了

    https://juejin.cn/post/7029130033268555807   测试环境:机器内存16G,JDK 8,12核CPU 测试用例,从网上找的示例,视情况修改即可:   java ...

  6. [转帖]一文读懂 | 如何快速部署 OceanBase 开源版

    2021-11-281398 版权 本文涉及的产品 云数据库 RDS MySQL Serverless,0.5-2RCU 50GB 推荐场景: 学生管理系统数据库设计搭建个人博客 立即试用 云防火墙, ...

  7. TiKV 服务部署的注意事项

    TiKV 服务部署的注意事项 背景 最近发现tikv总是会掉线 不知道是哪里触发了啥样子的bug. 所以想着使用systemd 管理一下, 至少在tikv宕机的时候能够拉起来服务. 二进制文件 pd- ...

  8. [转帖]Nginx优化与防盗链

    目录 一.配置Nginx隐藏版本号 1.第一种方法修改配置文件 2.第二种方法修改源码文件,重新编译安装 二.修改Nginx用户与组 三.配置Nginx网页缓存时间 四.实现Nginx的日志分割 五. ...

  9. [转帖]Linux Shell编程 循环语法

    https://zhuanlan.zhihu.com/ for循环 for 循环是固定循环,也就是在循环时已经知道需要进行几次循环.有时也把 for 循环称为计数循环.语法: for 变量 in 值1 ...

  10. [转帖]比 Python 快 35000 倍!LLVM&Swift 之父宣布全新编程语言 Mojo:编程被颠覆了

    https://www.infoq.cn/article/GFfVLVpkIGOcKYB85Opb "Mojo 可能是近几十年来最大的编程语言进步." 近日,由 LLVM 和 Sw ...