如果一个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. Serverless 架构开发手册 — “人人都是 Serverless 架构师”先导篇

    摘要:本篇实战将介绍如何以超低成本构建动态的 Web 站点,并且实现灵活扩展,限流等效果,最后再跟大家聊一聊"现代应用"的相关概念. 相信很多同学都有过想要拥有自己的 Web 站点 ...

  2. k8s探针详解

    一.探针类型 Kubernetes(k8s)中的探针是一种健康检查机制,用于监测Pod内容器的运行状况.主要包括以下三种类型的探针: 1.存活探针(Liveness Probe) 2.就绪探针(Rea ...

  3. 【驱动】I2C驱动分析(一)-I2C驱协议简介

    什么是 I²C I²C 叫集成电路总线 它是一种串行通信接口,具有双向两线同步串行总线,通常由两根线组成--SDA(串行数据线)和SCL(串行时钟线)和上拉电阻. 它们用于需要许多不同部件(例如传感器 ...

  4. Fedora 设置 core 文件路径

    sudo vim /etc/sysctl.conf 输入 kernel.core_pattern=core.%p sudo /lib/systemd/systemd-sysctl 使修改生效 cat ...

  5. 每天学五分钟 Liunx 0011 | 服务篇:进程

    1. 进程 程序放在硬盘中,在运行它的时候加载到内存,在内存里程序以进程的方式运行,进程有唯一的 ID ,叫 PID.   写个简单的 Hellow world 程序,让它产生 PID: [root@ ...

  6. 05-逻辑仿真工具VCS-执行过程

    Verilog Simulation Event Queue 主要了解VCS是如何处理交给它的代码的 Verilog的仿真事件队列,介绍VCS如何处理交给它的代码.VCS是Synopsys公司的,支持 ...

  7. 【ThreadX-FileX】Azure RTOS FileX概述

    Azure RTOS FileX嵌入式文件系统是Azure RTOS的高级工业级解决方案,适用于Microsoft FAT文件格式,专门针对深度嵌入式,实时和IoT应用程序而设计.Azure RTOS ...

  8. Nginx 配置文件备忘单

    Nginx 是用于 Web 服务.反向代理.缓存.负载平衡.媒体流等的开源软件.在这篇文章中,我将提到一些我们经常使用的 Nginx 配置. 监听端口 server { Standard HTTP P ...

  9. [转帖]SQLServer的UTF8支持

    排序规则和 Unicode 支持 - SQL Server | Microsoft Learn UTF-8 支持 SQL Server 2019 (15.x) 完全支持广泛使用的 UTF-8 字符编码 ...

  10. [转帖]RocketMQ - nameSrv和Broker

    RocketMQ RocketMQ是一个统一的消息传递引擎,轻量级的数据处理平台. Name Server Name Server充当路由消息的提供者,生产者(Producer)或消费者(Custom ...