zoukankan      html  css  js  c++  java
  • SpringBoot+Jpa动态切换多数据源配置及实现

    数据源配置文件:conf.properties

    spring.datasource.primary.key=huitu
    spring.datasource.primary.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.primary.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.primary.url=jdbc:mysql://15d451d6752.iok.la:3306/tuoying?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    spring.datasource.primary.username=root
    spring.datasource.primary.password=tuoying678
    
    spring.datasource.secondary.key=dzzw
    spring.datasource.secondary.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.secondary.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.secondary.url=jdbc:mysql://192.168.0.18:3306/dzzw?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    spring.datasource.secondary.username=root
    spring.datasource.secondary.password=123456
    
    spring.datasource.skdd.key=skdd
    spring.datasource.skdd.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.skdd.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.skdd.url=jdbc:mysql://192.168.0.18:3306/skdd?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    spring.datasource.skdd.username=root
    spring.datasource.skdd.password=123456

    创建包prop,该包下放多数据源的注册类,配置类和自定义注解类

    自定义注解类:DS

    package com.codecat.portal.prop;
    
    import java.lang.annotation.*;
    
    /**
     * 1
     * 自定义注解:DS
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({
            ElementType.METHOD
    })
    @Inherited
    public @interface DS {
        String value() default "huitu";
    }

    数据源信息类:DynamicDataSourceContextHolder

    package com.codecat.portal.prop;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 创建存储数据源信息的类,并自定义实现AbstractRoutingDataSource
     */
    public class DynamicDataSourceContextHolder {
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
        /*
         * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
         * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
         */
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    
        /*
         * 管理所有的数据源id;
         * 主要是为了判断数据源是否存在;
         */
        public static List<String> dataSourceIds  = new ArrayList<String>();
    
        //设置数据源
        public static void setDataSourceType(String dataSourceType){
            logger.info("切换至{}数据源", dataSourceType);
            contextHolder.set(dataSourceType);
        }
    
        //获取数据源
        public static String getDataSourceType(){
            return contextHolder.get();
        }
    
        //清除数据源
        public static void clearDataSourceType(){
            contextHolder.remove();
        }
    
        public static void saveDataSourceTypeName(String name){
            dataSourceIds.add(name);
        }
    
        /**
         * 判断指定DataSrouce当前是否存在
         * @param dataSourceId
         * @return
         */
        public static boolean containsDataSource(String dataSourceId){
            return dataSourceIds.contains(dataSourceId);
        }
    }

    动态获取数据源类:DynamicDataSource,实现AbstractRoutingDataSource类,通过AOP切面拦截特定注解(DS)设定数据源,可以在Dao层或服务实现类中设置数据源注解(DS)

    package com.codecat.portal.prop;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 动态获取数据源
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
    
        /*
         * 代码中的determineCurrentLookupKey方法取得一个字符串,
         * 该字符串将与配置文件中的相应字符串进行匹配以定位数据源,配置文件,即applicationContext.xml文件中需要要如下代码:(non-Javadoc)
         * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
         */
        @Override
        protected Object determineCurrentLookupKey(){
            /*
             * DynamicDataSourceContextHolder代码中使用setDataSourceType
             * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,
             * 交给AbstractRoutingDataSource进行注入使用。
             */
            String dataSourceName = DynamicDataSourceContextHolder.getDataSourceType();
            logger.info("当前数据源是:{}", dataSourceName);
            return dataSourceName;
        }
    }

    多数据源注册类:DynamicDataSourceRegister

    package com.codecat.portal.prop;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.context.properties.bind.Bindable;
    import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
    import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
    import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
    import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.convert.ConversionService;
    import org.springframework.core.convert.support.DefaultConversionService;
    import org.springframework.boot.context.properties.bind.Binder;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.util.StringUtils;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 动态注册数据源
     * 启动动态数据源请在启动类中(如SpringBootSampleApplication)
     * 添加 @Import(DynamicDataSourceRegister.class)
     */
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    
        private DruidDataSource pool;
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
        private ConversionService conversionService = new DefaultConversionService();
    
        /**
         * 别名
         */
        private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
    
        /**
         * 由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
         */
        static {
            aliases.addAliases("url", new String[]{"jdbc-url"});
            aliases.addAliases("username", new String[]{"user"});
        }
    
        //存储注册的数据源
        private Map<String, DataSource> customDataSources = new HashMap<>();
        //配置上下文(也可以理解为配置文件的获取工具)
        private Environment evn;
        //参数绑定工具 springboot2.0新推出
        private Binder binder;
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry){
            //获取所有数据源配置
            Map config,defaultDataSourceProperties;
            defaultDataSourceProperties = binder.bind("spring.datasource.primary", Map.class).get();
            //获取数据源类型
            String typeStr = evn.getProperty("spring.datasource.primary.type");
            //获取数据源类型
            Class<? extends DataSource> clazz = getDataSourceType(typeStr);
            //绑定默认数据源参数,也就是主数据源
            DataSource consumerDatasource, defaultDatasource = bind(clazz, defaultDataSourceProperties);
            DynamicDataSourceContextHolder.dataSourceIds.add("master");
            logger.info("注册默认数据源成功");
    
            //获取第二数据源配置secondary
            config = binder.bind("spring.datasource.secondary", Map.class).get();
            //设置从数据源
            clazz = getDataSourceType((String) config.get("type"));
            defaultDataSourceProperties = config;
            // 绑定参数
            consumerDatasource = bind(clazz, defaultDataSourceProperties);
            // 获取数据源的key,以便通过该key可以定位到数据源
            String key = config.get("key").toString();
            customDataSources.put(key, consumerDatasource);
            // 数据源上下文,用于管理数据源与记录已经注册的数据源key
            DynamicDataSourceContextHolder.dataSourceIds.add(key);
            logger.info("注册数据源{}成功", key);
    
            //获取第三数据源配置skdd
            config = binder.bind("spring.datasource.skdd", Map.class).get();
            //设置从数据源
            clazz = getDataSourceType((String) config.get("type"));
            defaultDataSourceProperties = config;
            // 绑定参数
            consumerDatasource = bind(clazz, defaultDataSourceProperties);
            // 获取数据源的key,以便通过该key可以定位到数据源
            String skddKey = config.get("key").toString();
            customDataSources.put(skddKey, consumerDatasource);
            // 数据源上下文,用于管理数据源与记录已经注册的数据源key
            DynamicDataSourceContextHolder.dataSourceIds.add(skddKey);
            logger.info("注册数据源{}成功", skddKey);
    
            // bean定义类
            GenericBeanDefinition define = new GenericBeanDefinition();
            // 设置bean的类型,此处DynamicRoutingDataSource是继承AbstractRoutingDataSource的实现类
            define.setBeanClass(DynamicDataSource.class);
            // 需要注入的参数
            MutablePropertyValues mpv = define.getPropertyValues();
            // 添加默认数据源,避免key不存在的情况没有数据源可用
            mpv.add("defaultTargetDataSource", defaultDatasource);
            // 添加其他数据源
            mpv.add("targetDataSources", customDataSources);
            // 将该bean注册为datasource,不使用springboot自动生成的datasource
            beanDefinitionRegistry.registerBeanDefinition("datasource", define);
            logger.info("注册数据源成功,一共注册{}个数据源", customDataSources.keySet().size() + 1);
        }
    
        /**
         * 通过字符串获取数据源class对象
         *
         * @param typeStr
         * @return
         */
        private Class<? extends DataSource> getDataSourceType(String typeStr) {
            Class<? extends DataSource> type;
            try {
                if (StringUtils.hasLength(typeStr)) {
                    // 字符串不为空则通过反射获取class对象
                    type = (Class<? extends DataSource>) Class.forName(typeStr);
                } else {
                    // 默认为Druid数据源
                    type = DruidDataSource.class;
                }
                return type;
            } catch (Exception e) {
                throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception
            }
        }
    
        /**
         * 绑定参数,以下三个方法都是参考DataSourceBuilder的bind方法实现的,目的是尽量保证我们自己添加的数据源构造过程与springboot保持一致
         *
         * @param result
         * @param properties
         */
        private void bind(DataSource result, Map properties) {
            ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
            Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
            // 将参数绑定到对象
            binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
        }
    
        private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
            ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
            Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
            // 通过类型绑定参数并获得实例对象
            return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
        }
    
        /**
         * @param clazz
         * @param sourcePath 参数路径,对应配置文件中的值,如: spring.datasource
         * @param <T>
         * @return
         */
        private <T extends DataSource> T bind(Class<T> clazz, String sourcePath) {
            Map properties = binder.bind(sourcePath, Map.class).get();
            return bind(clazz, properties);
        }
    
        /**
         * EnvironmentAware接口的实现方法,通过aware的方式注入,此处是environment对象
         *
         * @param environment
         */
        @Override
        public void setEnvironment(Environment environment) {
            logger.info("开始注册数据源");
            this.evn = environment;
            // 绑定配置器
            binder = Binder.get(evn);
        }
    }

    切换数据源类:DynamicDataSourceAspect

    package com.codecat.portal.prop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.hibernate.engine.spi.SessionImplementor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    
    /**
     * 切换数据源Advice
     */
    @Aspect
    @Order(-1)//设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)
    @Component
    public class DynamicDataSourceAspect {
    
        @PersistenceContext
        private EntityManager entityManager;
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    
        /*
         * @Before("@annotation(ds)")
         * 的意思是:
         * @Before:在方法执行之前进行执行:@annotation(targetDataSource):
         * 会拦截注解targetDataSource的方法,否则不拦截;
         */
        @Before("@annotation(ds)")
        public void changeDataSource(JoinPoint point, DS ds) throws Throwable {
            //获取当前的指定的数据源;
            String dsId = ds.value();
            /**
             * 通过aop拦截,获取注解上面的value的值key,然后取判断我们注册的keys集合中是否有这个key,如果没有,则使用默认数据源,如果有,则设置上下文中当前数据源的key为注解的value。
             */
            if (DynamicDataSourceContextHolder.containsDataSource(dsId)) {
                //logger.debug("切入:{} >", dsId, point.getSignature());
                DynamicDataSourceContextHolder.setDataSourceType(dsId);
            } else {
                logger.info("数据源[{}]不存在,使用默认数据源 >{}", dsId, point.getSignature());
                DynamicDataSourceContextHolder.setDataSourceType("master");
            }
        }
    
        @After("@annotation(ds)")
        public void restoreDataSource(JoinPoint point, DS ds) {
            //logger.debug("Revert DataSource : {} > {}", ds.value(), point.getSignature());
            //logger.info("切完");
            //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
            DynamicDataSourceContextHolder.clearDataSourceType();
            SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
            //最关键的一句代码, 手动断开连接,不用重新设置 ,会自动重新设置连接。
            session.disconnect();
        }
    }

    项目启动类添加代码(标黄代码):

    package com.codecat;
    
    import com.codecat.portal.prop.DynamicDataSourceRegister;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.Banner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import com.codecat.framework.jpa.EssenceJpaRepositoryFactoryBean;
    @EnableAutoConfiguration
    @ServletComponentScan
    @EnableTransactionManagement
    @ComponentScan(basePackages = {"com.URMS","com.codecat"})
    @SpringBootApplication
    @Import({DynamicDataSourceRegister.class})
    @EnableCaching //启用ehcach
    @EnableScheduling // 启用定时任务
    @EnableJpaRepositories(repositoryFactoryBeanClass = EssenceJpaRepositoryFactoryBean.class)
    @PropertySource({
            "classpath:druid.properties",
            "classpath:prop/${spring.profiles.active}/conf.properties",
            "classpath:application.properties"
             })
    public class EbootApplication {
    
        static Logger logger = LoggerFactory.getLogger(EbootApplication.class);
    
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(EbootApplication.class);
            application.setBannerMode(Banner.Mode.OFF);
            application.run(args);
            logger.info("spring cloud start success");
        }
    }

    服务实现类中使用DS注解来标明使用哪一个数据源,主数据源不用添加(第二,三数据源需要加)

    package com.codecat.portal.zw.service.impl;
    
    import com.codecat.framework.jpa.Criterion;
    import com.codecat.framework.jpa.Paginator;
    import com.codecat.framework.jpa.PaginatorParam;
    import com.codecat.portal.prop.DS;
    import com.codecat.portal.zw.dao.YwNoticeDao;
    import com.codecat.portal.zw.entity.YwNotice;
    import com.codecat.portal.zw.service.ZwService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    /**
     * 政务信息 - 服务接口实现
     */
    @Service
    public class ZwServiceImpl implements ZwService {
    
        @Autowired
        YwNoticeDao ywNoticeDao;
    
        /**
         * 按时间倒序查询前8条资讯和通知
         * @return
         */
        @Override
        @DS("gx_dzzw")
        public List<Map<String,Object>> queryNoticeTop8(){
            List<Map<String,Object>> mapList = new ArrayList<>();
            //...
            return mapList;
        }
    
        /**
         * 根据ID查询一个资讯内容
         * @param id
         * @return
         */
        @Override
        @DS("gx_dzzw")
        public YwNotice queryNoticeDetailById(String id){
            YwNotice ywNotice = ywNoticeDao.queryDetailById(id);
            return ywNotice;
        }
    
        /**
         * 分页获取信息发布记录
         * @param param 条件
         * @return 分页结果
         */
        @Override
        @DS("gx_dzzw")
        public Paginator<YwNotice> queryNoticeListPage(PaginatorParam param){
            Paginator<YwNotice> all = ywNoticeDao.findAll(param);
            //...
            return all;
        }
    }
  • 相关阅读:
    C++ 11 Lambda表达式
    Hello word!
    nginx 官方文档翻译
    Http读书笔记1-5章
    ROM、RAM、DRAM、SRAM和FLASH的区别
    优化专题
    Typescript学习
    canvas实现的粒子效果
    【转载】js常用方法和片段
    【转载】图解正向代理、反向代理、透明代理
  • 原文地址:https://www.cnblogs.com/codecat/p/13406031.html
Copyright © 2011-2022 走看看