参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html

https://blog.csdn.net/qq_34322777/article/details/80833935

一、动态注入多数据源

1、配置多数据源配置文件(application-db.properties)

######多数据源配置文件####################
###第一个####
spring.datasource.first.name=first
spring.datasource.first.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
spring.datasource.first.username=sepcore
spring.datasource.first.password=sepcore
spring.datasource.first.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.first.mapperLocations=classpath:mappers/*Mapper.xml
####第二个####
spring.datasource.second.name=second
spring.datasource.second.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.second.username=root
spring.datasource.second.password=123456
spring.datasource.second.driverClassName=com.mysql.jdbc.Driver
spring.datasource.second.mapperLocations=classpath:mappers/*Mapper.xml #####mapper接口所在包#######
scanner.mapperInterfacePackage=com.example.demo.mappers

2、读取配置文件类(DataSourceConfig)

public class DataSourceConfig {
/**
* 存储dataSource、SqlSessionTemplate、DataSourceTransactionManager
*/
private Map<String,Map<String,Object>>mapMap;
/**
* 获取mybatis扫描的指定接口包(所有数据源的接口放在同一的父包下面)
*/
private String mapperInterfacePackage; public DataSourceConfig(){
mapMap = new HashMap<>();
InputStream in = DataSourceConfig.class.getClassLoader().
getResourceAsStream("application-db.properties");
Properties properties = new Properties();
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
Set<String> set = properties.stringPropertyNames();
for (String s : set) {
//判断是否是mapper接口指定包路径
if (s.contains("mapperInterfacePackage")){
mapperInterfacePackage = properties.get(s).toString();
continue;
}
String key = s.substring(0, s.lastIndexOf("."));
if (mapMap.containsKey(key)){
Map<String, Object> map = mapMap.get(key);
map.put(s,properties.get(s));
}else{
Map<String,Object>map = new HashMap<>();
map.put(s,properties.get(s));
mapMap.put(key,map);
}
}
} public String getMapperInterfacePackage() {
return mapperInterfacePackage;
} /**
* 获取SqlSessionTemplate
* @return
* @throws Exception
*/
public Map<String,Object>getSqlSessionTemplateAndDataSource() throws Exception {
Set<Map.Entry<String, Map<String, Object>>> entries = this.mapMap.entrySet();
Map<String,Object>result = new HashMap<>(entries.size());
for (Map.Entry<String, Map<String, Object>> entry : entries) {
String key = entry.getKey();
Map<String, Object> map = entry.getValue();
DataSource dataSource = DataSourceBuilder.create().url(map.get(key+".url").toString()).
username(map.get(key+".username").toString()).password(map.get(key+".password").toString()).
driverClassName(map.get(key+".driverClassName").toString()).
build();
//为每个数据源设置事务
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource); SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置dataSource数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//设置*mapper.xml路径
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(map.get(key+".mapperLocations").toString()));
String s = map.get(key + ".name").toString();
result.put(s+"SqlSessionTemplate",new SqlSessionTemplate(sqlSessionFactoryBean.getObject()));
result.put(s+"DataSource",dataSource);
result.put(s+"DataSourceTransactionManager",dataSourceTransactionManager);
}
return result;
}
}

3、使用注解(DataSourceRoute),确定每个mapper接口使用哪个数据源

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceRoute {
String name() default "first";
}
@DataSourceRoute
public interface SepUserMapper {
List<Map<String,Object>> findByUserLevel(Long userLevel); void insert(Map<String,Object>map);
}
@DataSourceRoute(name = "second")
public interface IDiDataItemMapper { @Select("SELECT dataitem_id,name FROM di_dataitem WHERE dataitem_id=#{dataItemId}")
Map<String,Object>selectOne(Long dataItemId); void insert(Map<String, Object> map);
}

4、扫描指定包下面的mapper接口

public class ClassScanner {

    public static Map<String,Class<?>>getMapperInterface(String mapperInterfacePackage) throws Exception {
Map<String,Class<?>>classMap = new HashMap<>();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//将"."替换成"/"
String packagePath = mapperInterfacePackage.replace(".", "/");
URL url = loader.getResource(packagePath);
List<String> fileNames = null;
if (url != null) {
String type = url.getProtocol();
if ("file".equals(type)) {
fileNames = getClassNameByFile(url.getPath(), null, true);
}
}
for (String classPath : fileNames) {
classMap.putAll(getClassByPath(classPath));
}
return classMap;
} /**
* 读取package下的所有类文件
* @param filePath
* @param className
* @param childPackage
* @return
*/
private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
List<String> myClassName = new ArrayList<>();
File file = new File(filePath);
File[] childFiles = file.listFiles();
for (File childFile : childFiles) {
if (childFile.isDirectory()) {
if (childPackage) {
myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
}
} else {
String childFilePath = childFile.getPath();
if (childFilePath.endsWith(".class")) {
childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9,
childFilePath.lastIndexOf("."));
childFilePath = childFilePath.replace("\\", ".");
myClassName.add(childFilePath);
}
}
}
return myClassName;
} /**
* 将Mapper的标准文件,转成 Mapper Class
* @param classPath
* @return
* @throws Exception
*/
private static Map<String, Class<?>> getClassByPath(String classPath)
throws Exception{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Map<String, Class<?>> classMap = new HashMap<>();
classMap.put(getClassAlias(classPath),loader.loadClass(getFullClassName(classPath)));
return classMap;
} /**
* 将Mapper的标准文件,转成java标准的类名称
* @param classPath
* @return
* @throws Exception
*/
private static String getFullClassName(String classPath)
throws Exception{
int comIndex = classPath.indexOf("com");
classPath = classPath.substring(comIndex);
classPath = classPath.replaceAll("\\/", ".");
return classPath;
} /**
* 根据类地址,获取类的Alais,即根据名称,按照驼峰规则,生成可作为变量的名称
* @param classPath
* @return
* @throws Exception
*/
private static String getClassAlias(String classPath)
throws Exception{
String split = "\\/";
String[] classTmp = classPath.split(split);
String className = classTmp[classTmp.length-1];
return toLowerFisrtChar(className);
} /**
* 将字符串的第一个字母转小写
* @param className
* @return
*/
private static String toLowerFisrtChar(String className){
String fisrtChar = className.substring(0,1);
fisrtChar = fisrtChar.toLowerCase();
return fisrtChar+className.substring(1);
}

5、使用BeanFactoryPostProcessor动态插入数据源

@Component
public class DataSourceBean implements BeanFactoryPostProcessor{ @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================");
DataSourceConfig dataSourceConfig = new DataSourceConfig();
try {
Map<String, Object> sqlSessionTemplateAndDataSource = dataSourceConfig.getSqlSessionTemplateAndDataSource();
Map<String, Class<?>> mapperInterface = ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage());
Set<Map.Entry<String, Class<?>>> entries = mapperInterface.entrySet();
for (Map.Entry<String, Class<?>> entry : entries) {
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean();
Class<?> value = entry.getValue(); DataSourceRoute dataSourceRoute = value.getAnnotation(DataSourceRoute.class);
if (null==dataSourceConfig){
continue;
}
String name = dataSourceRoute.name();
SqlSessionTemplate template = (SqlSessionTemplate) sqlSessionTemplateAndDataSource.get(name + "SqlSessionTemplate");
mapperFactoryBean.setMapperInterface(value);
mapperFactoryBean.setSqlSessionTemplate(template);
mapperFactoryBean.afterPropertiesSet(); configurableListableBeanFactory.registerSingleton(name+"MapperFactory",mapperFactoryBean.getObject());
configurableListableBeanFactory.registerSingleton(name+"DataSource",sqlSessionTemplateAndDataSource.get(name + "DataSource"));
configurableListableBeanFactory.registerSingleton(name+"SqlSessionTemplate",template);
configurableListableBeanFactory.registerSingleton(name+"DataSourceTransactionManager",sqlSessionTemplateAndDataSource.get(name+"DataSourceTransactionManager")); }
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}

至此多数据源动态加载就完成了。

二、多数据源统一事务控制

当使用多数据源时,单一的事务会出现问题(当在service层同时操作两个数据源时,当发生异常,只会回滚离抛出异常最近的数据源的数据)

1、自定义事务注解

@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomTransaction {
String[] name() default {"firstDataSourceTransactionManager"};
}

2、创建aop切面进行事务控制

@Component
@Aspect
public class TransactionAop {
@Pointcut(value = "@annotation(com.example.demo.annon.CustomTransaction)")
public void pointCut(){} @Around(value = "pointCut()&&@annotation(annotation)")
public Object twiceAsOld(ProceedingJoinPoint point, CustomTransaction annotation) throws Throwable {
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<DataSourceTransactionManager>();
Stack<TransactionStatus> transactionStatuStack = new Stack<TransactionStatus>();
try {
if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
return null;
}
Object ret = point.proceed();
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
} catch (Throwable e) {
rollback(dataSourceTransactionManagerStack, transactionStatuStack);
throw e;
}
}
/**
* 开启事务处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
* @param multiTransactional
* @return
*/
private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack, CustomTransaction multiTransactional) { String[] transactionMangerNames = multiTransactional.name();
if (ArrayUtils.isEmpty(multiTransactional.name())) {
return false;
} for (String beanName : transactionMangerNames) {
//根据事务名称获取具体的事务
DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) SpringContextUtil
.getBean(beanName);
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}
return true;
} /**
* 提交处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());
}
}
/**
* 回滚处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}

3、在service层指定使用哪个事务

//注意事务的命名规则 
@CustomTransaction(name = {"firstDataSourceTransactionManager","secondDataSourceTransactionManager"})
public void setSepUserMapper(){ //操作数据源2
Map<String,Object>mm = new HashMap<>(2); mm.put("dataitemId",1L);
mm.put("name","测试");
diDataItemMapper.insert(mm); //操作数据源1
Map<String,Object>map = new HashMap<>(3);
map.put("userId",1L);
map.put("userName","张三");
map.put("name","平台管理员");
sepUserMapper.insert(map); throw new RuntimeException("sfsa");
}

辅助类:SpringContextUtil

@Component
public class SpringContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
} /**
* @Description: 获取spring容器中的bean,通过bean名称获取
* @param beanName bean名称
* @return: Object 返回Object,需要做强制类型转换
* @author: zongf
* @time: 2018-12-26 10:45:07
*/
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
} /**
* @Description: 获取spring容器中的bean, 通过bean类型获取
* @param beanClass bean 类型
* @return: T 返回指定类型的bean实例
* @author: zongf
* @time: 2018-12-26 10:46:31
*/
public static <T> T getBean(Class<T> beanClass) {
return applicationContext.getBean(beanClass);
} /**
* @Description: 获取spring容器中的bean, 通过bean名称和bean类型精确获取
* @param beanName bean 名称
* @param beanClass bean 类型
* @return: T 返回指定类型的bean实例
* @author: zongf
* @time: 2018-12-26 10:47:45
*/
public static <T> T getBean(String beanName, Class<T> beanClass){
return applicationContext.getBean(beanName,beanClass);
}

springboot基于注解动态配置多数据源以及多数据源的事务统一的更多相关文章

  1. (spring-第4回【IoC基础篇】)spring基于注解的配置

    基于XML的bean属性配置:bean的定义信息与bean的实现类是分离的. 基于注解的配置:bean的定义信息是通过在bean实现类上标注注解实现. 也就是说,加了注解,相当于在XML中配置了,一样 ...

  2. Spring boot 基于注解方式配置datasource

    Spring boot 基于注解方式配置datasource 编辑 ​ Xml配置 我们先来回顾下,使用xml配置数据源. 步骤: 先加载数据库相关配置文件; 配置数据源; 配置sqlSessionF ...

  3. Spring框架bean的配置(3):基于注解的配置

    1.基于注解的配置: @Component: 基本注解, 标识了一个受 Spring 管理的组件 @Respository: 标识持久层组件 @Service: 标识服务层(业务层)组件 @Contr ...

  4. Spring IoC — 基于注解的配置

    基于XML的配置,Bean定义信息和Bean实现类本身是分离的,而采用基于注解的配置方式时,Bean定义信息即通过在Bean实现类上标注注解实现. @Component:对类进行标注,Spring容器 ...

  5. Spring 基于注解零配置开发

    本文是转载文章,感觉比较好,如有侵权,请联系本人,我将及时删除. 原文网址:< Spring 基于注解零配置开发 > 一:搜索Bean 再也不用在XML文件里写什么配置信息了. Sprin ...

  6. 基于JMX动态配置Log4J日志级别

    先来看比较low的修改日志级别的方式,在写程序里面. http://blog.gssxgss.me/java%E8%BF%90%E8%A1%8C%E6%97%B6%E5%8A%A8%E6%80%81% ...

  7. Spring基于注解@Required配置

    基于注解的配置 从 Spring 2.5 开始就可以使用注解来配置依赖注入.而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身. ...

  8. Spring 基于注解的配置 简介

    基于注解的配置 从 Spring 2.5 开始就可以使用注解来配置依赖注入.而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身. ...

  9. [译]16-spring基于注解的配置元数据

    从spring2.5起spring框架开始支持java注解的配置元数据.所以除了使用xml配置文件来描述bean的装配之外,你还 可以使用基于java注解的配置元数据来完成同样的功能. spring框 ...

随机推荐

  1. 基于dokcer的zoookeeper集群安装

    编写docker-compose.ymlversion: '3.1' services: zoo1: image: zookeeper restart: always hostname: zoo1 p ...

  2. C# json对象中包含数组对象时,如何存入数据库

    前端创建的的对象例如: C#端这样将数组提取出来存入

  3. 报错——userdel: user hhh is currently used by process 9218

    报错 userdel: user hhh is currently used by process 9218 [root@centos71 ~]# useradd hhh [root@centos71 ...

  4. CentOS6.5下安装jdk配置环境变量错误问题:

    CentOS6.5下安装jdk,使用gedit /etc/profile  配置环境变量为如下: export JAVA_HOME=/usr/soft/jdk7export PATH=$JAVA_HO ...

  5. Delphi 如何在程序中执行动态生成的Delphi代码

    如何在程序中执行动态生成的Delphi代码 经常发现有人提这类问题,或者提问内容最后归结成这种问题 前些阵子有位高手写了一个“执行动态生成的代码”,这是真正的高手,我没那种功力,我只会投机取巧. 这里 ...

  6. chromedriver与chrome版本映射表(更新至v2.46)

    chromedriver版本 支持的Chrome版本 v2.46 v71-73 v2.45 v70-72 v2.44 v69-71 v2.43 v69-71 v2.42 v68-70 v2.41 v6 ...

  7. shell(计算机壳层)(二)

    shell 命令常用命令cat 文件名 输出文件内容到基本输出(屏幕 or 加>fileName 到另一个文件)cb 格式化源代码chmod //change mode,改变文件的权限cp co ...

  8. POJ 2114 (点分治)

    题目:https://vjudge.net/contest/307753#problem/B 题意:求树中路径和=k的点对是否存在 思路:点分治,这个题其实和上一题洛谷一样,只是这个数据强,我们不能直 ...

  9. JS 中的offset、scroll、client总结

    经常碰到offset.scroll.client这几个关键字,每次都要各种实验,这里总结一下. 两张图镇楼,随时翻阅 1. offset offset 指偏移,包括这个元素在文档中占用的所有显示宽度, ...

  10. 做Data Mining,其实大部分时间都花在清洗数据

    做Data Mining,其实大部分时间都花在清洗数据 时间 2016-12-12 18:45:50  51CTO 原文  http://bigdata.51cto.com/art/201612/52 ...