全屏 被埋没的画家

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

标签:java,多数据源

一、动态注入多数据源

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


######多数据源配置文件####################
###第一个####spring.datasource.first.name=firstspring.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=secondspring.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>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 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 map = mapMap.get(key);
                map.put(s,properties.get(s));
            }else{
                Mapmap = new HashMap<>();
                map.put(s,properties.get(s));
                mapMap.put(key,map);
            }
        }
    }    public String getMapperInterfacePackage() {        return mapperInterfacePackage;
    }    /**
     * 获取SqlSessionTemplate
     * @return
     * @throws Exception     */
    public MapgetSqlSessionTemplateAndDataSource() throws Exception {
        Set>> entries = this.mapMap.entrySet();
        Mapresult = new HashMap<>(entries.size());        for (Map.Entry> entry : entries) {
            String key = entry.getKey();
            Map 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";
}

@DataSourceRoutepublic interface SepUserMapper {
    List> findByUserLevel(Long userLevel);    void insert(Mapmap);
}


@DataSourceRoute(name = "second")public interface IDiDataItemMapper {

    @Select("SELECT dataitem_id,name FROM di_dataitem WHERE dataitem_id=#{dataItemId}")
    MapselectOne(Long dataItemId);    void insert(Map map);
}

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


public class ClassScanner {    public static Map>getMapperInterface(String mapperInterfacePackage) throws Exception {
        Map>classMap = new HashMap<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();        //将"."替换成"/"
        String packagePath = mapperInterfacePackage.replace(".", "/");
        URL url = loader.getResource(packagePath);
        List 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 getClassNameByFile(String filePath, List className, boolean childPackage) {
        List 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> getClassByPath(String classPath)            throws Exception{
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Map> 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动态插入数据源


@Componentpublic class DataSourceBean implements BeanFactoryPostProcessor{

    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================");        DataSourceConfig dataSourceConfig = new DataSourceConfig();        try {
            Map sqlSessionTemplateAndDataSource = dataSourceConfig.getSqlSessionTemplateAndDataSource();
            Map> mapperInterface = ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage());
            Set>> entries = mapperInterface.entrySet();            for (Map.Entry> 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
@Aspectpublic 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 dataSourceTransactionManagerStack = new Stack();
        Stack transactionStatuStack = new Stack();        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 dataSourceTransactionManagerStack,
                                    Stack 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 dataSourceTransactionManagerStack,
                        Stack transactionStatuStack) {        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());
        }
    }    /**
     * 回滚处理方法
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack     */
    private void rollback(Stack dataSourceTransactionManagerStack,
                          Stack transactionStatuStack) {        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
        }
    }
}

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


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

 辅助类:SpringContextUtil


@Componentpublic 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 getBean(Class 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 getBean(String beanName, Class beanClass){        return applicationContext.getBean(beanName,beanClass);
    }