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);
    }
posted @ 2019-05-29 19:42  炫舞风中  阅读(7401)  评论(0编辑  收藏  举报