如果一个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. ldap sssd授权linux登录

    业务系统越来越多,服务器也越来越多,本文主要是给企业用户减少账号密码管理难度的. 目的:使用ldap统一管理账号密码,实现单点登录linux. 一点废话,网上找了很多文章,看得云里雾里,搞了几天算是搞 ...

  2. 数字孪生 3D 风电场,智慧风电之陆上风电

    前言 "十四五"期间,在传统产业数字化升级和绿色改造领域.绿色低碳城镇化和现代城市建设领域.绿色低碳消费领域,和可再生能源或电力系统建设等领域,总投资可以达到近 45 万亿,平均每 ...

  3. XSS、CSRF 以及如何防范

  4. 274. H 指数

    1.题目介绍 给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数.计算并返回该研究者的 h 指数. 根据维基百科上 h 指数的定义:h 代表 ...

  5. 0xGame 2023【WEEK2】Crypto全解

    中间的那个人 题目信息 from secret import flag from Crypto.Util.number import * from Crypto.Cipher import AES f ...

  6. java - classpath 的配置

    classpath C:\Program Files\Java\jdk\jre\lib\rt.jar

  7. [转帖]配置 Windows XP 正常上网(TLS HTTPS),连接到 NAS

    https://zhuanlan.zhihu.com/p/208685816# 学习一下. 知乎用户8kqKq9 等 45 人赞同了该文章 Windows XP 是经典的.高效的.可靠的.性能良好的操 ...

  8. [转帖]Web技术(五):HTTP/2 是如何解决HTTP/1.1 性能瓶颈的?

    文章目录 一.HTTP/2 概览 二.HTTP/2 协议原理 2.1 Binary frame layer 2.1.1 DATA帧定义 2.1.2 HEADERS帧定义 2.2 Streams and ...

  9. 人大金仓学习之二_ksh和kddm的学习

    人大金仓学习之二_ksh和kddm的学习 摘要 承接上一篇文章 主要是这里总结一下ksh相关的文档. 这里学习了很多文档: https://help.kingbase.com.cn/v8/perfor ...

  10. [转帖]LTP使用和分析

    一.安装及编译流程 1.下载LTP LTP 项目目前位于 GitHub,项目地址:https://github.com/linux-test-project/ltp . 获取最新版可以执行以下命令: ...