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

java自定义注解嵌套_Spring-基于自定义注解和Aop动态数据源配置

Spring-基于自定义注解和Aop动态数据源配置

在实际项目中,经常会因为需要增强数据库并发能力而设计分库分表或者读写分离等策略,每在旧项目中引进新技术的时候都会带来一系列的问题,我们的目的就是去解决问题,带着思考方式去重构系统,从中找到乐趣,对应引进自定义注解和Aop动态数据源配置技术带来的问题,我会在文章末尾介绍,也希望大神给予正确的引导,我们当时的需求就是:有一个XXX旧系统,我们在这个旧系统的基础上开发一个PC端的程序用于收银;对方提供他们的数据库文档和对接人员,旧系统代码他们不给,我们只能通过沟通去了解他们旧系统的设计思路,带着一万个艹尼玛去写代码了;我们属于二次开发,需要在旧系统的数据库基础上开发自己的业务数据库,到这里就设计到二个数据库了(一个是旧系统的数据库,一个收银系统的数据库),项目之前能想到得就是自定义注解和Aop动态数据源配置来实现,但存在坑,下面我会提出坑点;现在就让我们先从配置(本文是基于SSM框架下集成的动态数据源切换):

1.     配置pom.xml,使用的是阿里巴巴数据源包和Mysql 5.1.30的驱动

com.alibaba

druid

1.0.2

mysql

mysql-connector-java

5.1.30

2.     spring-dispatcher.xml 核心配置如下:

destroy-method="close">

destroy-method="close">

dialect=mysql

reasonable=true

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

3.     spring-dispatcher.xml依赖的config.properties配置文件如下:

# =====================数据源切换数据master和slave数据库=====================

# master 也是默认的数据源(默认为旧系统的:原因是他们的表比较多)

jdbc.url=jdbc:mysql://127.0.0.1:3306/his?useUnicode=true&characterEncoding=utf8

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.username=root

jdbc.password=root2

# slave 需要切换的数据源(slave,原因是我们的表比较少)

jdbc.slave.url=jdbc:mysql://127.0.0.1:3306/his_pay?useUnicode=true&characterEncoding=utf8

jdbc.slave.driverClassName=com.mysql.jdbc.Driver

jdbc.slave.username=root

jdbc.slave.password=root

# =====================数据源切换数据master和slave数据库=====================

jdbc.filters=stat

jdbc.maxActive=20

jdbc.initialSize=1

jdbc.maxWait=60000

jdbc.minIdle=10

jdbc.maxIdle=15

jdbc.timeBetweenEvictionRunsMillis=60000

jdbc.minEvictableIdleTimeMillis=300000

jdbc.validationQuery=SELECT 'x'

jdbc.testWhileIdle=true

jdbc.testOnBorrow=false

jdbc.testOnReturn=false

jdbc.maxOpenPreparedStatements=20

jdbc.removeAbandoned=true

jdbc.removeAbandonedTimeout=1800

jdbc.logAbandoned=true

4.     和controller包同目录dynamic.datasource包下有如下几个类:

DataSource.java(自定义的注解),DataSourceAspect.java(Aop切面),DataSourceType.java(枚举:用于指定是数据源名),DynamicDataSource.java,DynamicDataSourceHolder.java。

5.      DataSource.java 如下:

package cn.edu.his.pay.dynamic.datasource;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/*

@Target(ElementType.TYPE) //接口、类、枚举、注解

@Target(ElementType.FIELD) //字段、枚举的常量

@Target(ElementType.METHOD) //方法

@Target(ElementType.PARAMETER) //方法参数

@Target(ElementType.CONSTRUCTOR) //构造函数

@Target(ElementType.LOCAL_VARIABLE)//局部变量

@Target(ElementType.ANNOTATION_TYPE)//注解

@Target(ElementType.PACKAGE) ///包

@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS) //默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,

@Retention(RetentionPolicy.RUNTIME)//注解会在class字节码文件中存在,在运行时可以通过反射获取到

*/

/**

* @author 93287

*

*/

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DataSource {

DataSourceType value();

}

6.      DataSourceAspect.java 如下:

package cn.edu.his.pay.dynamic.datasource;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

import cn.edu.his.pay.common.log.Logger;

@Aspect

@Order(-1)

// 保证该AOP在@Transactional之前执行

@Component

public class DataSourceAspect {

private static final Logger LOG = new Logger(DataSourceAspect.class);

@Before("@annotation(ds)")

public void changeDataSource(JoinPoint point, DataSource ds) throws Throwable {

LOG.debug("=======================SET START=======================");

LOG.debug("Use DataSource : {} > {}", ds.value(), point.getSignature());

DynamicDataSourceHolder.setDataSourceType(ds.value().name());

LOG.debug("[annotation.set] datasource====》{}",ds.value().name());

LOG.debug("=======================SET END=======================");

}

@After("@annotation(ds)")

public void restoreDataSource(JoinPoint point, DataSource ds) {

LOG.debug("=======================CLEAR START=======================");

LOG.debug("Revert DataSource : {} > {}", ds.value().name(), point.getSignature());

DynamicDataSourceHolder.clearDataSourceType();

LOG.debug("[annotation.remove] datasource====》{}",ds.value().name());

LOG.debug("=======================CLEAR END=======================");

}

}

7.      DataSourceType.java 如下:

package cn.edu.his.pay.dynamic.datasource;

public enum DataSourceType {

MASTER, SLAVE

}

8.      DynamicDataSource.java 如下:

package cn.edu.his.pay.dynamic.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

return DynamicDataSourceHolder.getDataSourceType();

}

}

9.      DynamicDataSourceHolder.java 如下:

package cn.edu.his.pay.dynamic.datasource;

import org.springframework.util.Assert;

import cn.edu.his.pay.common.log.Logger;

public class DynamicDataSourceHolder {

private static final Logger LOG = new Logger(DynamicDataSourceHolder.class);

// 线程本地环境

private static final ThreadLocal contextHolder = new ThreadLocal();

// 设置数据源类型

public static void setDataSourceType(String dataSourceType) {

Assert.notNull(dataSourceType, "DataSourceType cannot be null");

contextHolder.set(dataSourceType);

LOG.debug("[this.set] datasource====》{}",dataSourceType);

}

// 获取数据源类型

public static String getDataSourceType() {

return contextHolder.get();

}

// 清除数据源类型

public static void clearDataSourceType() {

LOG.debug("[this.remove] datasource====》{}",contextHolder.get());

contextHolder.remove();

}

}

9.     基本核心配置和核心代码已经如上了,那我们要怎么使用了,如spring-dispatcher.xml 配置中配置Aop的切点是service包下的所有方法。所以需要将数据源切换到Slave上就直接使用如下注解配置到方法对应的方法上就行,不配置注解默认走Master。

@Override

@DataSource(value = DataSourceType.SLAVE)

public int insert(Admin record) {

return adminMapper.insert(record);

}

10.     疑问:如上配置是基于service为切入点,在百度的同时说可以将mapper(dao层)做切入点来做,但我实验了好几次也没成功,不知道这种方式是否能实现?

11.     开始我对于自己的实现是挺有信心的,可惜还是没有避免入坑,等代码测试人员测试的时候,发现功能不好用,之后各种排查,排查了一天居然是数据连错了,数据各种不对;找到后bug修复了,那边测试人员又开始测试主要流程支付,结果发现还是不好用,结果又是一顿排查,发现业务抛出异常后居然没有回滚,这里还好用的是测试库,结果发现问题出现在,spring的嵌套事务下执行得坑,啥话没说又一顿百度,又由于service方法中执行的业务比较多,数据源切换也比较频繁,数据源来回切换消耗的资源开销太大,所以我决定放弃,使用分布式事务管理jta来实现嵌套事务的ACID问题(使用jta来实现分布式事务会在下篇文章中介绍),虽然使用了其他方式解决了分布式事务的问题,但在这里我将整个问题描述一遍,希望和大家一块讨论并分析出问题出现在哪块?

12.     在同一个service方法中由于涉及到二个库的增删改查,但切换数据源注解是配置在service方法上的,所以导致不能自动切换数据源,采用的手手动切换,切换代码如下:

DynamicDataSourceHolder.setDataSourceType(DataSourceType.SLAVE.name());

securityAdditionMapper.insert(securityAddition);

DynamicDataSourceHolder.clearDataSourceType();

13.     嵌套事务演示代码如下:

@Override

@Transactional(rollbackFor = Exception.class)

public ApiCommonResultVo handlePay(){

handlePayFinish();

DynamicDataSourceHolder.setDataSourceType(DataSourceType.SLAVE.name());

securityAdditionMapper.insert(securityAddition);

DynamicDataSourceHolder.clearDataSourceType();

}

@Override

@Transactional(rollbackFor = Exception.class)

public void handlePayFinish(){

// 业务代码

}

14.     有上述对需求的描述我总结了如下几个问题,请大神给予正确的解答:

1)只用spring的事务管理能做到多数据源切换事务相关的ACID?

2)spring事务支持嵌套事务吗?

3)spring事务中去切换数据源为什么不可以?

4)像spring这样的事务但程序跑到一半后系统全面奔溃,这个时候还能保住数据的ACID吗?

相关文章:

  • java包名称_显示Java类的包名称
  • Java异构数据翻译器_CowNewSQL Java实现的多数据库SQL翻译器,可以把标准 生成多种 的方言,支持Oracle、 Develop 238万源代码下载- www.pudn.com...
  • linux java网络编程_Java网络编程深入之TCP协议编程
  • java顺序打印约瑟夫环_关于约瑟夫环问题,用java 编写程序,输出n个人出圈的顺序,书上的程序代码如下,但是有几点我搞不明白...
  • java翻译topping_java刚開始学习的人常见的问题
  • java里添加员工信息_SSH_框架整合4--添加员工信息
  • 毛刺现象 java_组合逻辑设计中的毛刺现象
  • java 有界类型_java泛型之有界类型
  • oracle mysql8_这一刻,MySQL 8终于追赶上了Oracle 8
  • power of three java_【LeetCode】326. Power of Three 3的幂(Easy)(JAVA)
  • python2和pytho3切换_电脑上同时安装Python2和Pytho
  • 学JS对学Java有用吗_【JS】编程语言那么多,为啥学Java的人那么多?
  • java offset用法_Java OffsetTime plusMinutes()用法及代码示例
  • php 判断是否对象_利用PHP判断JSON对象是否存在
  • php链接数据库2000,Linux下PHP连接Microsoft SQL Server 2000(图)
  • hexo+github搭建个人博客
  • [译]如何构建服务器端web组件,为何要构建?
  • angular2 简述
  • golang中接口赋值与方法集
  • JavaScript服务器推送技术之 WebSocket
  • JavaScript实现分页效果
  • js中forEach回调同异步问题
  • Mocha测试初探
  • vue 配置sass、scss全局变量
  • Vue.js-Day01
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 记录:CentOS7.2配置LNMP环境记录
  • 将 Measurements 和 Units 应用到物理学
  • 前端性能优化--懒加载和预加载
  • 三分钟教你同步 Visual Studio Code 设置
  • 世界上最简单的无等待算法(getAndIncrement)
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • #NOIP 2014# day.1 T2 联合权值
  • #单片机(TB6600驱动42步进电机)
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (1)常见O(n^2)排序算法解析
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (五)网络优化与超参数选择--九五小庞
  • *Django中的Ajax 纯js的书写样式1
  • *上位机的定义
  • .form文件_SSM框架文件上传篇
  • .helper勒索病毒的最新威胁:如何恢复您的数据?
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .Net FrameWork总结
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇