zoukankan      html  css  js  c++  java
  • springboot基于注解动态配置多数据源以及多数据源的事务统一

    参考文档: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);
        }
  • 相关阅读:
    mac os x 查看网络端口情况
    mac 启动php-fpm报错 failed to open configuration file '/private/etc/php-fpm.conf': No such file or direc
    视频播放—— H5同层播放器接入规范
    [转]webpack中require和import的区别
    职场方法论系列—怎样做项目
    一幅图帮你搞懂订单的拆分与合并
    如何用数据去驱动决策?
    分库分表的 9种分布式主键ID 生成方案,挺全乎的
    快手基于 Apache Flink 的优化实践
    SQL数据库中临时表、临时变量和WITH AS关键词创建“临时表”的区别
  • 原文地址:https://www.cnblogs.com/cq-yangzhou/p/10945779.html
Copyright © 2011-2022 走看看