springboot2多数据源完整示例

springboot2 + mybatis + mysql + oracle + sqlserver多数据源的配置

  • 相信很多朋友在开发的时候,可能会碰到需要一个项目,配置多个数据源的需求,可能是同一种数据库,也可能是不同种类的数据库。
  • 这种情况,我们就需要配置多数据源对程序的支持了。
  • (本例理论上支持很多种数据库,或者同种数据库配置多个库分别作数据源也许。拓展新强)

环境介绍

  • web框架:SpringBoot2
  • orm框架:Mybatis
  • 数据库连接池:Druid
  • 主数据源:Mysql
  • 从数据源1:Oracle
  • 从数据源2:SqlServer
  • 运行平台:Jdk8
  • 接口文档:Swagger-ui (提供伪Restful接口)
  • 日志配置:Logback

基本思路

  • 自定义多个数据源,并指定切换规则
  • 引入ThreadLocal来保存和管理数据源上下文标识
  • 使用AOP切面编程,根据某些自定义条件,动态切换数据源(反射)
  • 访问接口测试效果

大致步骤

  1. 创建一个拥有ThreadLocal变量的类,用来存取数据源名称

    public class JdbcContextHolder {
    
        /** 本地线程共享对象(保证在同一线程下切换后不要被其他线程修改) */
        private final static ThreadLocal<String> local = new ThreadLocal<>();
    
        public static void putDataSource(String name){
            local.set(name);
        }
    
        public static String getDataSource(){
            return local.get();
        }
    
        public static void removeDataSource(){
            local.remove();
        }
    
    }
    
    
  2. 创建一个枚举类,用来存放每个数据源的标识符(标识符是自定义的)
    public enum DataSourceType {
        Mysql("mysql"),
        Oracle("oracle");
    
        private String name;
    
        DataSourceType(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
  3. 在启动类上,禁用springboot自动配置的数据源:(exclude = DataSourceAutoConfiguration.class)
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @ComponentScan("com.dcm.*.**.**")
    public class MoreDsApplication extends SpringBootServletInitializer {
    
        public static void main(String[] args) {
    
            SpringApplication.run(MoreDsApplication.class, args);
            System.out.println("[---------------more_ds项目: started......]");
    
        }
    
        /** 创建一个SpringApplicationBuilder交付给springboot框架来完成初始化运行配置 */
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(MoreDsApplication.class);
        }
    }
    
    
  4. 创建动态数据源类,接管springboot的数据源配置
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
            /** 数据源路由,此方法用于产生要选取的数据源逻辑名称 */
            @Override
            protected Object determineCurrentLookupKey() {
                //从共享线程中获取数据源名称
                return JdbcContextHolder.getDataSource();
            }
        }
    
    
  5. 在application.yml文件中加入自定义的多数据源的配置
    datasource:
      druid:
        type: com.alibaba.druid.pool.DruidDataSource
        initialSize: 1
        minIdle: 3
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 30000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,‘wall‘用于防火墙
        filters: stat,wall,slf4j
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合并多个DruidDataSource的监控数据
        #useGlobalDataSourceStat: true
      mysql:
        url: jdbc:mysql://192.168.0.241:3306/pmpmain?useUnicode=true&useSSL=false&characterEncoding=utf8
        username: sdcm
        password: Sdcm_123456
        driverClassName: com.mysql.jdbc.Driver
        validationQuery: select ‘x‘
      oracle:
        url: jdbc:oracle:thin:@192.168.0.200:1522:sdcm
        username: sdcm
        password: Sdcm123456
        driverClassName: oracle.jdbc.OracleDriver
        validationQuery: select 1 from dual
    
    
  6. 根据application.yml的多数据源配置,初始化各数据源并指定默认数据源
    @Configuration
    public class DataSourceConfig {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        // -----------------------------------------mysql config-------------------------------------
    
        @Value("${datasource.mysql.url}")
        private String dbUrl;
    
        @Value("${datasource.mysql.username}")
        private String username;
    
        @Value("${datasource.mysql.password}")
        private String password;
    
        @Value("${datasource.mysql.driverClassName}")
        private String driverClassName;
    
        @Value("${datasource.mysql.validationQuery}")
        private String validationQuery;
    
        @Bean(name="dataSourceMysql")
        public DataSource dataSourceMysql(){
            System.out.println("----------------主配" + dbUrl);
    
            DruidDataSource datasource = new DruidDataSource();
            datasource.setUrl(dbUrl);
            datasource.setUsername(username);
            datasource.setPassword(password);
            datasource.setDriverClassName(driverClassName);
            datasource.setValidationQuery(validationQuery);
            setDruidOptions(datasource); // 设置druid数据源的属性
    
            return datasource;
        }
    
        // -----------------------------------------oracle config-------------------------------------
    
        @Value("${datasource.oracle.url}")
        private String oracleUrl;
    
        @Value("${datasource.oracle.username}")
        private String oracleUsername;
    
        @Value("${datasource.oracle.password}")
        private String oraclePassword;
    
        @Value("${datasource.oracle.driverClassName}")
        private String oracleDriverClassName;
    
        @Value("${datasource.oracle.validationQuery}")
        private String oracleValidationQuery;
    
        @Bean(name="dataSourceOracle")
        public DataSource dataSourceOracle(){
            System.out.println("----------------次配" + oracleUrl);
    
            DruidDataSource datasource = new DruidDataSource();
            datasource.setUrl(oracleUrl);
            datasource.setUsername(oracleUsername);
            datasource.setPassword(oraclePassword);
            datasource.setDriverClassName(oracleDriverClassName);
            datasource.setValidationQuery(oracleValidationQuery);
            setDruidOptions(datasource); // 设置druid数据源的属性
    
            return datasource;
        }
    
        // -----------------------------------------druid config-------------------------------------
    
        @Value("${datasource.druid.initialSize}")
        private int initialSize;
    
        @Value("${datasource.druid.minIdle}")
        private int minIdle;
    
        @Value("${datasource.druid.maxActive}")
        private int maxActive;
    
        @Value("${datasource.druid.maxWait}")
        private int maxWait;
    
        @Value("${datasource.druid.timeBetweenEvictionRunsMillis}")
        private int timeBetweenEvictionRunsMillis;
    
        @Value("${datasource.druid.minEvictableIdleTimeMillis}")
        private int minEvictableIdleTimeMillis;
    
        @Value("${datasource.druid.testWhileIdle}")
        private boolean testWhileIdle;
    
        @Value("${datasource.druid.testOnBorrow}")
        private boolean testOnBorrow;
    
        @Value("${datasource.druid.testOnReturn}")
        private boolean testOnReturn;
    
        @Value("${datasource.druid.poolPreparedStatements}")
        private boolean poolPreparedStatements;
    
        @Value("${datasource.druid.maxPoolPreparedStatementPerConnectionSize}")
        private int maxPoolPreparedStatementPerConnectionSize;
    
        @Value("${datasource.druid.filters}")
        private String filters;
    
        @Value("{datasource.druid.connectionProperties}")
        private String connectionProperties;
    
        private void setDruidOptions(DruidDataSource datasource){
            datasource.setInitialSize(initialSize);
            datasource.setMinIdle(minIdle);
            datasource.setMaxActive(maxActive);
            datasource.setMaxWait(maxWait);
            datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
            datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            datasource.setTestWhileIdle(testWhileIdle);
            datasource.setTestOnBorrow(testOnBorrow);
            datasource.setTestOnReturn(testOnReturn);
            datasource.setPoolPreparedStatements(poolPreparedStatements);
            datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
            try {
                datasource.setFilters(filters);
            } catch (SQLException e) {
                logger.error("druid configuration initialization filter Exception", e);
            }
            datasource.setConnectionProperties(connectionProperties);
        }
    
        @Bean(name = "dynamicDataSource")
        @Primary  // 优先使用,多数据源
        public DataSource dataSource(){
    
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            DataSource mysql = dataSourceMysql();
            DataSource oracle = dataSourceOracle();
    
            //设置默认数据源
            dynamicDataSource.setDefaultTargetDataSource(mysql);
    
            //配置多个数据源
            Map<Object,Object> map = new HashMap<>();
            map.put(DataSourceType.Mysql.getName(),mysql);
            map.put(DataSourceType.Oracle.getName(),oracle);
            dynamicDataSource.setTargetDataSources(map);
    
            return dynamicDataSource;
        }
    
        @Bean // 事务管理
        public PlatformTransactionManager txManager() {
            return new DataSourceTransactionManager(dataSource());
        }
    
        @Bean(name="druidServlet")
        public ServletRegistrationBean druidServlet() {
            ServletRegistrationBean reg = new ServletRegistrationBean();
            reg.setServlet(new StatViewServlet());
            reg.addUrlMappings("/druid/*");
            reg.addInitParameter("allow", ""); // 白名单
            return reg;
        }
    
        @Bean(name = "filterRegistrationBean")
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            filterRegistrationBean.setFilter(new WebStatFilter());
            filterRegistrationBean.addUrlPatterns("/*");
            filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
            filterRegistrationBean.addInitParameter("profileEnable", "true");
            filterRegistrationBean.addInitParameter("principalCookieName","USER_COOKIE");
            filterRegistrationBean.addInitParameter("principalSessionName","USER_SESSION");
            filterRegistrationBean.addInitParameter("DruidWebStatFilter","/*");
            return filterRegistrationBean;
        }
    }
    
    
  7. 自定义注解,作为AOP切面范围的一个条件(这样更加灵活)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface TargetDataSource {
    
        DataSourceType value() default DataSourceType.Mysql;
    }
    
    
  8. 创建AOP切换,动态切换数据源
    @Aspect
    @Order(2)
    @Component
    public class DataSourceAspect {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        // 切入点:service类的方法上(这个包的子包及所有包的里面的以Service结尾的类的任意方法名任意参数的方法,都讲被切到)
        @Pointcut("execution(* com.dcm.more_ds..*Service..*(..))")
        public void dataSourcePointCut(){
            System.out.println("dataSourcePointCut service");
        }
    
        @Before("dataSourcePointCut()")
        private void before(JoinPoint joinPoint){
            Object target = joinPoint.getTarget();
            String method = joinPoint.getSignature().getName();
            Class<?> classz = target.getClass();
            Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
            try {
                Method m = classz.getMethod(method,parameterTypes);
    
                // 如果 m 上存在切换数据源的注解,则根据注解内容进行数据源切换
                if (m != null && m.isAnnotationPresent(TargetDataSource.class)){
                    TargetDataSource data = m.getAnnotation(TargetDataSource.class);
                    JdbcContextHolder.putDataSource(data.value().getName());
                    logger.info("》》》》》》》 current thread " + Thread.currentThread().getName() + " add 【 " + data.value().getName() + " 】 to ThreadLocal");
                } else { // 如果不存在,则使用默认数据源
                    logger.info("》》》》》》》 use default datasource");
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
        // 执行完切面后,将线程共享中的数据源名称清空
        @After("dataSourcePointCut()")
        public void after(JoinPoint joinPoint){
            JdbcContextHolder.removeDataSource();
        }
    
    }
    
    
  9. 在需要切换数据源的dao interface或者service interface上(具体看你切面切的范围),加上自定义的注解(这里随便列举两个)
    @Transactional(readOnly = true)
        @TargetDataSource(DataSourceType.Oracle)
        public List<T> list(Integer startNum, Integer limit) {
            setDao();
            return baseDao.selectWithPage(Arrays.asList(startNum, limit));
        }
    
        @TargetDataSource(DataSourceType.Oracle)
        public List<T> selectByRequestBodyIds(@RequestBody List<P> ids) {
            return selectByPrimaryKeys(ids);
        }
        // .......more
    
    
  10. 配置mybatis
    @Configuration
    // 扫描指定包下的dao,这样就不用每个dao interface上面写@Mapper了
    @MapperScan(basePackages = "com.dcm.more_ds.dao.*.**")
    public class MyBatisConf {
    
        @Autowired
        @Qualifier("dynamicDataSource")
        private DataSource dataSource;
    
        @Bean
        public SqlSessionFactory sqlSessionFactory() {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
    
            // 分页插件
            PageHelper pageHelper = new PageHelper();
            Properties properties = new Properties();
            properties.setProperty("reasonable", "true");
            properties.setProperty("supportMethodsArguments", "true");
            properties.setProperty("returnPageInfo", "check");
            properties.setProperty("params", "count=countSql");
            properties.setProperty("autoRuntimeDialect","true"); // 运行时根据数据源自动选择方言 (这句很重要)
            pageHelper.setProperties(properties);
    
            // 添加插件
            bean.setPlugins(new Interceptor[] { pageHelper });
    
            // 添加XML目录
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            try {
                bean.setMapperLocations(resolver.getResources("classpath:com/dcm/more_ds/dao/*/*.xml"));
                bean.setConfigLocation(resolver.getResource("classpath:mybatis-conf.xml"));
                return bean.getObject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        @Bean(name = "sqlSessionTemplate")
        public SqlSessionTemplate sqlSessionTemplate() {
            return new SqlSessionTemplate(sqlSessionFactory());
        }
    
    }
    
    

源码地址

springboot2 + mybatis + mysql + oracle多数据源的配置

  • 源码中用到了AOP的其它应用:统一入参校验,统一异常处理。接口是伪Restful的。
  • 源码中的mapper.xml,dao,service,serviceImpl,controller都是代码生成器生成的。(本贴破10w阅读量就分享代码生成器)

文末寄语:
  • 转载请注明出处
  • 感谢

原文地址:https://blog.csdn.net/xu_san_duo/article/details/83860675

原文地址:https://www.cnblogs.com/jpfss/p/11078594.html

时间: 2024-10-19 18:11:38

springboot2多数据源完整示例的相关文章

润乾集算报表使用远程HTTP数据源的示例

报表的数据来源多种多样,有时会接收来自HTTP服务器的数据进行报表展现,一般报表工具只能通过报表自定义数据源使用高级语言(如JAVA)进行处理,实现较为复杂.集算报表简单地通用集算器接收HTTP数据源完成报表展现.这里通过一个实例说明. 学生成绩信息存储在远程的JSON格式文件中,其所在HTTP服务器对外提供统一HTTP访问接口,现需要读取学生成绩信息开发报表,汇总学生成绩并按总成绩排名.报表样式如下: JSON文件中包含班级.编号.姓名.学科.成绩等信息,格式如下: [ { "class&qu

WCF服务开发与调用的完整示例

开发工具:VS2008 开发语言:C# 开发内容:简单的权限管理系统 第一步.建立WCF服务库 点击确定,将建立一个WCF 服务库示例程序,自动生成一个包括IService1.cs和Service1.cs两个类文件.我们可以直接对其修改开发我们的服务,但一般直接删除. 第二步:开发实体类 在解决方案中,添加新类Module.cs 在类中要首先引入using System.Runtime.Serialization命名空间 实体类具体代码如下: namespace WcfServiceLib.mo

Android清理设备内存详细完整示例(一)

MainActivity如下: package come.on; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; i

无刷新分页代码,jQuery分页完整示例

<!DOCTYPE html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>jQuery分页演示效果</title><script type="text/javascript" src="/ajaxjs/jquery1.3.2.js">&l

Android清理设备内存详细完整示例(二)

MainActivity如下: package cc.c; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.List; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo;

Android图片旋转,缩放,位移,倾斜,对称完整示例(二)——Bitmap.createBitmap()和Matrix

MainActivity如下: package cc.c; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.widget.ImageView; /** * Demo描述: * 利用B

Android图片旋转,缩放,位移,倾斜,对称完整示例(一)——imageView.setImageMatrix(matrix)和Matrix

MainActivity如下: import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.ImageView; import android.app.Activity; import android.graphics.Matrix; /** * Demo描述:

转:WCF服务开发与调用的完整示例

转:http://blog.csdn.net/fxhflower/article/details/7274925 WCF服务开发与调用的完整示例 开发工具:VS2008 开发语言:C# 开发内容:简单的权限管理系统 第一步.建立WCF服务库 点击确定,将建立一个WCF 服务库示例程序,自动生成一个包括IService1.cs和Service1.cs两个类文件.我们可以直接对其修改开发我们的服务,但一般直接删除. 第二步:开发实体类 在解决方案中,添加新类Module.cs 在类中要首先引入usi

C连接MySQL数据库开发之Linux环境完整示例演示(增、删、改、查)

一.开发环境 ReadHat6.3 32位.mysql5.6.15.gcc4.4.6 二.编译 gcc -I/usr/include/mysql -L/usr/lib -lmysqlclient main.c -o main.out -I:指定mysql头文件所在目录(默认去/usr/include目录下寻找所用到的头文件) -L:指定mysql动态库文件所在目录(默认从/usr/lib目录查找) -l:链接libmysqlclient.so动态库 -o:生成的可执行文件名 三.完整示例 //