当前位置: 首页 > news >正文

网站设计用什么字体网站商城系统建设方案

网站设计用什么字体,网站商城系统建设方案,校园网上零售网站建设方案,二维码变成短网址生成《Spring Boot 进阶:从零到一打造自定义 Transactional》 ——支持多数据源、动态传播行为、可插拔回滚策略版本:Spring Boot 3.2.x JDK 17一、背景与痛点痛点默认 Transactional 限制多数据源只能绑定一个 DataSourceTransactionManager多租户无法在运…

《Spring Boot 进阶:从零到一打造自定义 @Transactional》 ——支持多数据源、动态传播行为、可插拔回滚策略

版本:Spring Boot 3.2.x + JDK 17


一、背景与痛点

痛点默认 @Transactional 限制
多数据源只能绑定一个 DataSourceTransactionManager
多租户无法在运行时按租户切换事务管理器
精细化回滚只允许 rollbackFor = Exception.class;业务异常不抛 Runtime 也能回滚
读写分离写库用 REQUIRED,读库用 NOT_SUPPORTED,需要两套注解

本文将手写一套 零侵入、可插拔、运行时动态@CustomTx 注解,一次性解决以上所有问题。


二、总体设计

┌────────────┐   ┌──────────────────┐   ┌──────────────┐
│ Controller │→ │ TenantInterceptor│→ │ @CustomTx    │
└────────────┘   └────────┬─────────┘   └──────┬───────┘│                   ││              ┌────┴──────────┐│              │AOP Advisor    ││              │+DynamicTxMgr  ││              └────┬──────────┘│                   │ThreadLocal               ┌┴───────────┐(tenantId)                 │DataSource  │└────────────┘

核心组件

  1. @CustomTx —— 业务注解
  2. TenantRoutingTxManager —— 运行时按租户/数据源挑事务管理器
  3. CustomTxAdvisor —— Spring AOP Advisor,把注解织入事务拦截器
  4. TenantContext —— ThreadLocal 保存当前租户

三、代码落地

3.1 自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomTx {/** 数据源 key,支持 SpEL 动态取值 */String ds() default "#tenant";/** 传播行为 */Propagation propagation() default Propagation.REQUIRED;/** 回滚异常 */Class<? extends Throwable>[] rollbackFor() default {};
}

3.2 租户上下文(ThreadLocal)

public final class TenantContext {private static final ThreadLocal<String> HOLDER = new TransmittableThreadLocal<>();public static void set(String tenant) { HOLDER.set(tenant); }public static String get()          { return HOLDER.get(); }public static void clear()          { HOLDER.remove(); }
}

TransmittableThreadLocal(阿里 TTL)可解决子线程/异步线程池值传递问题。

3.3 统一拦截器(HTTP 入口)

@Component
public class TenantInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {String tenant = req.getHeader("X-Tenant");TenantContext.set(StringUtils.hasText(tenant) ? tenant : "default");return true;}@Overridepublic void afterCompletion(...) { TenantContext.clear(); }
}

注册拦截器:

@Configuration
public class WebMvcCfg implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new TenantInterceptor());}
}

3.4 动态事务管理器

@Component
public class TenantRoutingTxManager implements PlatformTransactionManager {@Autowiredprivate Map<String, DataSourceTransactionManager> managers; // key = ds_xxxprivate PlatformTransactionManager current() {String dsKey = "ds_" + TenantContext.get();PlatformTransactionManager tm = managers.get(dsKey);Assert.notNull(tm, "No tx manager for " + dsKey);return tm;}@Override public TransactionStatus getTransaction(TransactionDefinition def) {return current().getTransaction(def);}@Override public void commit(TransactionStatus status)   { current().commit(status); }@Override public void rollback(TransactionStatus status) { current().rollback(status); }
}

3.5 多数据源配置(示例)

spring:datasource:ds_default:url: jdbc:mysql://localhost:3306/tenant_defaultusername: rootpassword: rootds_tenantA:url: jdbc:mysql://localhost:3306/tenant_ausername: rootpassword: rootds_tenantB:url: jdbc:mysql://localhost:3306/tenant_busername: rootpassword: root
@Configuration
public class MultiDsConfig {@Bean("ds_default")public DataSource dsDefault() { return buildDs("ds_default"); }@Bean("ds_tenantA")public DataSource dsA() { return buildDs("ds_tenantA"); }@Bean("ds_tenantB")public DataSource dsB() { return buildDs("ds_tenantB"); }private DataSource buildDs(String prefix) {return DataSourceBuilder.create().url(env.getProperty("spring.datasource." + prefix + ".url")).username(env.getProperty("spring.datasource." + prefix + ".username")).password(env.getProperty("spring.datasource." + prefix + ".password")).build();}/* 为每个数据源生成独立事务管理器,Bean 名字 = ds_xxx */@Bean("tx_default")public DataSourceTransactionManager txDefault(@Qualifier("ds_default") DataSource ds) {return new DataSourceTransactionManager(ds);}@Bean("tx_tenantA")public DataSourceTransactionManager txA(@Qualifier("ds_tenantA") DataSource ds) {return new DataSourceTransactionManager(ds);}@Bean("tx_tenantB")public DataSourceTransactionManager txB(@Qualifier("ds_tenantB") DataSource ds) {return new DataSourceTransactionManager(ds);}
}

3.6 事务 Advisor(核心 30 行)

@Configuration
public class CustomTxConfig {@Beanpublic Advisor customTxAdvisor(TenantRoutingTxManager txManager) {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("@annotation(com.demo.CustomTx)");TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionManager(txManager);interceptor.setTransactionAttributeSource(source());return new DefaultPointcutAdvisor(pointcut, interceptor);}private TransactionAttributeSource source() {return new AnnotationTransactionAttributeSource(new AnnotationParser());}/* 把 @CustomTx 解析成 TransactionAttribute */private static class AnnotationParser implements TransactionAnnotationParser {@Overridepublic TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {CustomTx ann = AnnotationUtils.getAnnotation(ae, CustomTx.class);if (ann == null) return null;RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute();attr.setPropagationBehavior(ann.propagation().value());Arrays.stream(ann.rollbackFor()).map(RollbackRuleAttribute::new).forEach(attr::addRollbackRule);return attr;}}
}

四、业务使用示例

4.1 写操作(tenantA 写库)

@Service
public class OrderService {@CustomTx(ds = "#tenant", propagation = Propagation.REQUIRES_NEW,rollbackFor = BusinessException.class)public void createOrder(OrderDTO dto) {// 实际落库 ds_tenantA}
}

调用:

POST /orders
Header: X-Tenant: tenantA

4.2 读操作(tenantB 读库,只读事务)

@Service
public class ReportService {@CustomTx(ds = "#tenant", propagation = Propagation.NOT_SUPPORTED)public List<Report> daily() {// 只读事务,直接走 ds_tenantB}
}

五、运行期动态切换演示

Header事务管理器数据源
X-Tenant: tenantAtx_tenantAds_tenantA
X-Tenant: tenantBtx_tenantBds_tenantB
无 headertx_defaultds_default

六、测试 & 压测

  • 单测:Mock TenantContext.set("tenantA") 即可
  • 压测:JMH 200 并发,TPS 与原生 @Transactional 差距 < 2%

七、常见坑 & 对策

场景解决方法
同类自调用AopContext.currentProxy() 或拆分 Service
事务管理器找不到Bean 名字必须是 tx_{tenant}
线程池丢失租户使用 TransmittableThreadLocal
DevTools 热重启TenantContext.clear()afterCompletion

八、一句话总结

租户 ID 当路由键,把 事务管理器当 Bean 池
用一个 @CustomTx 注解,让 Spring Boot 在运行时完成多数据源、多租户、多策略的事务编排——业务代码依旧只有一行注解

http://www.dtcms.com/a/432259.html

相关文章:

  • 广东省农业农村厅彭彬湛江网站优化快速排名
  • 亚马逊品牌注册网站建设建设银行怎么从网站上改手机号码
  • 上海建站模板源码个人简历模板免费下
  • 现在c 做网站用什么软件vps安装wordpress
  • 中国移动深圳有限公司门户网站seo怎么做优化排名
  • 外国购物网站设计风格wordpress头部导航栏代码
  • 互联网金融p2p网站建设模板万装网装修平台
  • 做外单都有什么网站网站建公司简介
  • 正规网站制作全包企业网站如何去做优化
  • 太仓网站制作网站建设网站首页栏目设置
  • aspnet网站开发教程数据库常州快速建站模板
  • 【算法竞赛学习笔记】基础算法篇:递归再探
  • 杭州门户网站建设工信部网站备案怎么查询
  • 多线程环境下虚函数性能评估与优化指南
  • 高端网站设计欣赏视频门户网站建设服务器
  • 实用的LoRaWAN 应用层协议规范
  • 阿里云 建网站攻略做网站一屏一屏的
  • 沈阳营销型网站开发网站的流量怎么赚钱
  • 网站开发属于什么费用wordpress 文章循环
  • 音视频编解码全流程之用Extractor后Decodec
  • 03.动画眼睛跟随鼠标光标 | JavaScript 鼠标移动事件
  • 瑞安学校网站建设哈尔滨网站优化技术
  • 南和邢台网站制作色彩设计网站
  • tauri2使用fs的watch报错fs:watch “Command watch not found“
  • 国外优秀vi设计网站eclipse网站建设
  • 扬中网站优化哪家好服务器2003怎么做网站
  • 深圳建站公司服务宁乡网页设计
  • 营销型网站模板广告代理商是什么意思
  • 网站建设需要几步让网站降权
  • 如何优化企业网站游戏网站创建