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

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

一、动态注入多数据源

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

  1. ######多数据源配置文件####################
  2. ###第一个####
  3. spring.datasource.first.name=first
  4. spring.datasource.first.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
  5. spring.datasource.first.username=sepcore
  6. spring.datasource.first.password=sepcore
  7. spring.datasource.first.driverClassName=oracle.jdbc.driver.OracleDriver
  8. spring.datasource.first.mapperLocations=classpath:mappers/*Mapper.xml
  9. ####第二个####
  10. spring.datasource.second.name=second
  11. spring.datasource.second.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
  12. spring.datasource.second.username=root
  13. spring.datasource.second.password=123456
  14. spring.datasource.second.driverClassName=com.mysql.jdbc.Driver
  15. spring.datasource.second.mapperLocations=classpath:mappers/*Mapper.xml
  16.  
  17. #####mapper接口所在包#######
  18. scanner.mapperInterfacePackage=com.example.demo.mappers

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

  1. public class DataSourceConfig {
  2. /**
  3. * 存储dataSource、SqlSessionTemplate、DataSourceTransactionManager
  4. */
  5. private Map<String,Map<String,Object>>mapMap;
  6. /**
  7. * 获取mybatis扫描的指定接口包(所有数据源的接口放在同一的父包下面)
  8. */
  9. private String mapperInterfacePackage;
  10.  
  11. public DataSourceConfig(){
  12. mapMap = new HashMap<>();
  13. InputStream in = DataSourceConfig.class.getClassLoader().
  14. getResourceAsStream("application-db.properties");
  15. Properties properties = new Properties();
  16. try {
  17. properties.load(in);
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. Set<String> set = properties.stringPropertyNames();
  22. for (String s : set) {
  23. //判断是否是mapper接口指定包路径
  24. if (s.contains("mapperInterfacePackage")){
  25. mapperInterfacePackage = properties.get(s).toString();
  26. continue;
  27. }
  28. String key = s.substring(0, s.lastIndexOf("."));
  29. if (mapMap.containsKey(key)){
  30. Map<String, Object> map = mapMap.get(key);
  31. map.put(s,properties.get(s));
  32. }else{
  33. Map<String,Object>map = new HashMap<>();
  34. map.put(s,properties.get(s));
  35. mapMap.put(key,map);
  36. }
  37. }
  38. }
  39.  
  40. public String getMapperInterfacePackage() {
  41. return mapperInterfacePackage;
  42. }
  43.  
  44. /**
  45. * 获取SqlSessionTemplate
  46. * @return
  47. * @throws Exception
  48. */
  49. public Map<String,Object>getSqlSessionTemplateAndDataSource() throws Exception {
  50. Set<Map.Entry<String, Map<String, Object>>> entries = this.mapMap.entrySet();
  51. Map<String,Object>result = new HashMap<>(entries.size());
  52. for (Map.Entry<String, Map<String, Object>> entry : entries) {
  53. String key = entry.getKey();
  54. Map<String, Object> map = entry.getValue();
  55. DataSource dataSource = DataSourceBuilder.create().url(map.get(key+".url").toString()).
  56. username(map.get(key+".username").toString()).password(map.get(key+".password").toString()).
  57. driverClassName(map.get(key+".driverClassName").toString()).
  58. build();
  59. //为每个数据源设置事务
  60. DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
  61. dataSourceTransactionManager.setDataSource(dataSource);
  62.  
  63. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  64. //设置dataSource数据源
  65. sqlSessionFactoryBean.setDataSource(dataSource);
  66. //设置*mapper.xml路径
  67. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(map.get(key+".mapperLocations").toString()));
  68. String s = map.get(key + ".name").toString();
  69. result.put(s+"SqlSessionTemplate",new SqlSessionTemplate(sqlSessionFactoryBean.getObject()));
  70. result.put(s+"DataSource",dataSource);
  71. result.put(s+"DataSourceTransactionManager",dataSourceTransactionManager);
  72. }
  73. return result;
  74. }
  75. }

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

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

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

  1. public class ClassScanner {
  2.  
  3. public static Map<String,Class<?>>getMapperInterface(String mapperInterfacePackage) throws Exception {
  4. Map<String,Class<?>>classMap = new HashMap<>();
  5. ClassLoader loader = Thread.currentThread().getContextClassLoader();
  6. //将"."替换成"/"
  7. String packagePath = mapperInterfacePackage.replace(".", "/");
  8. URL url = loader.getResource(packagePath);
  9. List<String> fileNames = null;
  10. if (url != null) {
  11. String type = url.getProtocol();
  12. if ("file".equals(type)) {
  13. fileNames = getClassNameByFile(url.getPath(), null, true);
  14. }
  15. }
  16. for (String classPath : fileNames) {
  17. classMap.putAll(getClassByPath(classPath));
  18. }
  19. return classMap;
  20. }
  21.  
  22. /**
  23. * 读取package下的所有类文件
  24. * @param filePath
  25. * @param className
  26. * @param childPackage
  27. * @return
  28. */
  29. private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
  30. List<String> myClassName = new ArrayList<>();
  31. File file = new File(filePath);
  32. File[] childFiles = file.listFiles();
  33. for (File childFile : childFiles) {
  34. if (childFile.isDirectory()) {
  35. if (childPackage) {
  36. myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
  37. }
  38. } else {
  39. String childFilePath = childFile.getPath();
  40. if (childFilePath.endsWith(".class")) {
  41. childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9,
  42. childFilePath.lastIndexOf("."));
  43. childFilePath = childFilePath.replace("\\", ".");
  44. myClassName.add(childFilePath);
  45. }
  46. }
  47. }
  48. return myClassName;
  49. }
  50.  
  51. /**
  52. * 将Mapper的标准文件,转成 Mapper Class
  53. * @param classPath
  54. * @return
  55. * @throws Exception
  56. */
  57. private static Map<String, Class<?>> getClassByPath(String classPath)
  58. throws Exception{
  59. ClassLoader loader = Thread.currentThread().getContextClassLoader();
  60. Map<String, Class<?>> classMap = new HashMap<>();
  61. classMap.put(getClassAlias(classPath),loader.loadClass(getFullClassName(classPath)));
  62. return classMap;
  63. }
  64.  
  65. /**
  66. * 将Mapper的标准文件,转成java标准的类名称
  67. * @param classPath
  68. * @return
  69. * @throws Exception
  70. */
  71. private static String getFullClassName(String classPath)
  72. throws Exception{
  73. int comIndex = classPath.indexOf("com");
  74. classPath = classPath.substring(comIndex);
  75. classPath = classPath.replaceAll("\\/", ".");
  76. return classPath;
  77. }
  78.  
  79. /**
  80. * 根据类地址,获取类的Alais,即根据名称,按照驼峰规则,生成可作为变量的名称
  81. * @param classPath
  82. * @return
  83. * @throws Exception
  84. */
  85. private static String getClassAlias(String classPath)
  86. throws Exception{
  87. String split = "\\/";
  88. String[] classTmp = classPath.split(split);
  89. String className = classTmp[classTmp.length-1];
  90. return toLowerFisrtChar(className);
  91. }
  92.  
  93. /**
  94. * 将字符串的第一个字母转小写
  95. * @param className
  96. * @return
  97. */
  98. private static String toLowerFisrtChar(String className){
  99. String fisrtChar = className.substring(0,1);
  100. fisrtChar = fisrtChar.toLowerCase();
  101. return fisrtChar+className.substring(1);
  102. }

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

  1. @Component
  2. public class DataSourceBean implements BeanFactoryPostProcessor{
  3.  
  4. @Override
  5. public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
  6. System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================");
  7. DataSourceConfig dataSourceConfig = new DataSourceConfig();
  8. try {
  9. Map<String, Object> sqlSessionTemplateAndDataSource = dataSourceConfig.getSqlSessionTemplateAndDataSource();
  10. Map<String, Class<?>> mapperInterface = ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage());
  11. Set<Map.Entry<String, Class<?>>> entries = mapperInterface.entrySet();
  12. for (Map.Entry<String, Class<?>> entry : entries) {
  13. MapperFactoryBean mapperFactoryBean = new MapperFactoryBean();
  14. Class<?> value = entry.getValue();
  15.  
  16. DataSourceRoute dataSourceRoute = value.getAnnotation(DataSourceRoute.class);
  17. if (null==dataSourceConfig){
  18. continue;
  19. }
  20. String name = dataSourceRoute.name();
  21. SqlSessionTemplate template = (SqlSessionTemplate) sqlSessionTemplateAndDataSource.get(name + "SqlSessionTemplate");
  22. mapperFactoryBean.setMapperInterface(value);
  23. mapperFactoryBean.setSqlSessionTemplate(template);
  24. mapperFactoryBean.afterPropertiesSet();
  25.  
  26. configurableListableBeanFactory.registerSingleton(name+"MapperFactory",mapperFactoryBean.getObject());
  27. configurableListableBeanFactory.registerSingleton(name+"DataSource",sqlSessionTemplateAndDataSource.get(name + "DataSource"));
  28. configurableListableBeanFactory.registerSingleton(name+"SqlSessionTemplate",template);
  29. configurableListableBeanFactory.registerSingleton(name+"DataSourceTransactionManager",sqlSessionTemplateAndDataSource.get(name+"DataSourceTransactionManager"));
  30.  
  31. }
  32. } catch (Exception e) {
  33. System.err.println(e.getMessage());
  34. }
  35. }
  36. }

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

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

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

1、自定义事务注解

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

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

  1. @Component
  2. @Aspect
  3. public class TransactionAop {
  4. @Pointcut(value = "@annotation(com.example.demo.annon.CustomTransaction)")
  5. public void pointCut(){}
  6.  
  7. @Around(value = "pointCut()&&@annotation(annotation)")
  8. public Object twiceAsOld(ProceedingJoinPoint point, CustomTransaction annotation) throws Throwable {
  9. Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<DataSourceTransactionManager>();
  10. Stack<TransactionStatus> transactionStatuStack = new Stack<TransactionStatus>();
  11. try {
  12. if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
  13. return null;
  14. }
  15. Object ret = point.proceed();
  16. commit(dataSourceTransactionManagerStack, transactionStatuStack);
  17. return ret;
  18. } catch (Throwable e) {
  19. rollback(dataSourceTransactionManagerStack, transactionStatuStack);
  20. throw e;
  21. }
  22. }
  23. /**
  24. * 开启事务处理方法
  25. *
  26. * @param dataSourceTransactionManagerStack
  27. * @param transactionStatuStack
  28. * @param multiTransactional
  29. * @return
  30. */
  31. private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
  32. Stack<TransactionStatus> transactionStatuStack, CustomTransaction multiTransactional) {
  33.  
  34. String[] transactionMangerNames = multiTransactional.name();
  35. if (ArrayUtils.isEmpty(multiTransactional.name())) {
  36. return false;
  37. }
  38.  
  39. for (String beanName : transactionMangerNames) {
  40. //根据事务名称获取具体的事务
  41. DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) SpringContextUtil
  42. .getBean(beanName);
  43. TransactionStatus transactionStatus = dataSourceTransactionManager
  44. .getTransaction(new DefaultTransactionDefinition());
  45. transactionStatuStack.push(transactionStatus);
  46. dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
  47. }
  48. return true;
  49. }
  50.  
  51. /**
  52. * 提交处理方法
  53. *
  54. * @param dataSourceTransactionManagerStack
  55. * @param transactionStatuStack
  56. */
  57. private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
  58. Stack<TransactionStatus> transactionStatuStack) {
  59. while (!dataSourceTransactionManagerStack.isEmpty()) {
  60. dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());
  61. }
  62. }
  63. /**
  64. * 回滚处理方法
  65. *
  66. * @param dataSourceTransactionManagerStack
  67. * @param transactionStatuStack
  68. */
  69. private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
  70. Stack<TransactionStatus> transactionStatuStack) {
  71. while (!dataSourceTransactionManagerStack.isEmpty()) {
  72. dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
  73. }
  74. }
  75. }

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

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

辅助类:SpringContextUtil

  1. @Component
  2. public class SpringContextUtil implements ApplicationContextAware{
  3. private static ApplicationContext applicationContext;
  4. @Override
  5. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  6. SpringContextUtil.applicationContext = applicationContext;
  7. }
  8.  
  9. /**
  10. * @Description: 获取spring容器中的bean,通过bean名称获取
  11. * @param beanName bean名称
  12. * @return: Object 返回Object,需要做强制类型转换
  13. * @author: zongf
  14. * @time: 2018-12-26 10:45:07
  15. */
  16. public static Object getBean(String beanName){
  17. return applicationContext.getBean(beanName);
  18. }
  19.  
  20. /**
  21. * @Description: 获取spring容器中的bean, 通过bean类型获取
  22. * @param beanClass bean 类型
  23. * @return: T 返回指定类型的bean实例
  24. * @author: zongf
  25. * @time: 2018-12-26 10:46:31
  26. */
  27. public static <T> T getBean(Class<T> beanClass) {
  28. return applicationContext.getBean(beanClass);
  29. }
  30.  
  31. /**
  32. * @Description: 获取spring容器中的bean, 通过bean名称和bean类型精确获取
  33. * @param beanName bean 名称
  34. * @param beanClass bean 类型
  35. * @return: T 返回指定类型的bean实例
  36. * @author: zongf
  37. * @time: 2018-12-26 10:47:45
  38. */
  39. public static <T> T getBean(String beanName, Class<T> beanClass){
  40. return applicationContext.getBean(beanName,beanClass);
  41. }

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. PyQt5界面上调用subprocess.Popen会闪命令窗口的问题

    最近再做一个界面开发,主要实现的点击一个按钮,会执行adb安装应用程序的功能,在调试阶段一切都正常,但打包成一个exe安装程序,安装之后运行,点击按钮会闪一下adb的命令窗口 先列出subproces ...

  2. pycharm 中切换terminal的盘符

    第一步,采用 cd .. 将当前路径设置为该盘符的根目录 第二步,采用 C: 将盘符设置为C盘然后使用 cd 命令将路径切换到指定位置

  3. angular 组件通信

    单页面应用组件通信有以下几种,这篇文章主要讲 Angular 通信 父组件 => 子组件 子组件 => 父组件 组件A = > 组件B 父组件 => 子组件 子组件 => ...

  4. Java web入门之Http请求和响应

    三层架构 web层:JSP + Servlet.Struts 2.SpringMVC service层:Spring dao层:JDBC.DBUtils.Hibernate.MyBatis form表 ...

  5. Navicat Premium 12 如何连接阿里云虚拟主机SQL Server 数据库

    这个是一台 阿里云购买云虚拟主机!密码已经重置完毕,现在我们 需要知道 数据连接的地址,数据库名,帐号以及密码. 根据不同的运营商 选择 这里我们选择阿里云 云数据库 SQL Server版本 填写 ...

  6. 比传统事务快10倍?一张图读懂阿里云全局事务服务GTS

    近日,阿里云全局事务服务GTS正式上线,为微服务架构中的分布式事务提供一站式解决方案.GTS的原理是将分布式事务与具体业务分离,在平台层面开发通用的事务中间件GTS,由事务中间件协调各服务的调用一致性 ...

  7. PHP数据如何向上取整

    PHP数据如何向上取整? PHP数据向上取整可以通过ceil()函数来实现,ceil()函数表示向上舍入为最接近的整数. 语法是: 1 ceil(x) 参数 x 必需.一个数. 说明 返回不小于 x ...

  8. 【Linux】ssh执行远程命令awk 参数报错问题

    ssh  ip    sudo docker ps -a | grep none | awk '{print \$1}'| sed 's/%//g' $1前面加上转移符就好

  9. 2019.6.1 模拟赛——[ 费用流 ][ 数位DP ][ 计算几何 ]

    第一题:http://codeforces.com/contest/1061/problem/E 把点集分成不相交的,然后跑费用流即可.然而错了一个点. #include<cstdio> ...

  10. codeforces 111C/112E Petya and Spiders

    题目: Petya and Spiders传送门: http://codeforces.com/problemset/problem/111/C http://codeforces.com/probl ...