JAVA—Spring—SpringBoot—springboot多数据源

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 分包在yml中定义多个自定义数据源属性通过bean的方式自定义datasource注入进spring

1.定义配置文件多数据源


前缀无所谓,多数据源就不再使用默认的属性配置了


spring.datasource.test1.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false

spring.datasource.test1.username=****

spring.datasource.test1.password=****

spring.datasource.test1.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver


spring.datasource.test2.url=jdbc:mysql://(ip):3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false

spring.datasource.test2.username=****

spring.datasource.test2.password=****

spring.datasource.test2.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver


server.port=10001

spring.application.name=sub-package


2.定义bean注入spring


自定义要注入进spring容器的dataSource


package com.zhangyao.springboot.config;


import com.alibaba.druid.pool.DruidDataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.boot.jdbc.DataSourceBuilder;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

import tk.mybatis.spring.annotation.MapperScan;


import javax.sql.DataSource;



/**

* 分包多数据源配置

* 数据源1

*

* @author: zhangyao

* @create:2020-04-06 22:17

**/



@Configuration

//使用mybatis的话 通过mapperScan规定了不同的mapper包下使用的数据源

//sqlSessionFactoryRef执行sqlSessionFactory使用哪个数据源的

@MapperScan(basePackages = "com.zhangyao.springboot.mapper.test1",sqlSessionFactoryRef = "test1SqlSessionFactory")

public class DataSource1Config {


   //注入spring

   @Bean(name = "test1DataSource")

   //标记默认数据源

   @Primary

   //从配置文件中读取数据源配置 前缀格式不固定

   @ConfigurationProperties(prefix = "spring.datasource.test1")

   public DruidDataSource getDataSource1(){

       //这里使用的druid数据源,使用不同的数据源就创建不同的dataSource

       DruidDataSource datasourc =  DataSourceBuilder.create().type(DruidDataSource.class).build();

       return datasourc;

   }



   //通过自定义的方式把dataSource配置信息注入sqlSessionFactory

   //这是mybatis需要使用的

   @Bean("test1SqlSessionFactory")

   @Primary

   public SqlSessionFactory getSqlSessionFactory1(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {

       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

       sqlSessionFactoryBean.setDataSource(dataSource);

//        sqlSessionFactoryBean.setMapperLocations();


       return sqlSessionFactoryBean.getObject();

   }



   /**

    * 同样是myBatis需要使用的 把sqlSessionFactory 注入sqlSessionTemplate

    * @param sqlSessionFactory

    * @return

    */

   @Bean("test1SqlSessionTemplate")

   @Primary

   public SqlSessionTemplate getSqlSessionTemplate1(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){

       return new SqlSessionTemplate(sqlSessionFactory);

   }

}


这是第一个数据源的配置 也是主数据源的配置


如果不使用mybatis可以不用配置sqlSessionFactory 和 sqlSessionTemplate


3.使用(mybatis)


在业务代码中引入不同的mapper包下的mapper 就使用不同数据源


package com.zhangyao.springboot.service.impl;


import com.alibaba.fastjson.JSON;

import com.zhangyao.springboot.mapper.test1.ArticleMapper;

import com.zhangyao.springboot.service.ArticleService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

import org.springframework.stereotype.Service;


/**

* 多数据源分包方案

* 引入哪个数据源包下的mapper 就默认使用哪个数据源

* @author: zhangyao

* @create:2020-04-06 22:46

**/


@Service

@Repository

public class ArticleServiceImpl implements ArticleService {


   @Autowired

   ArticleMapper articleMapper;


   @Override

   public String queryAll() {

       return JSON.toJSONString(articleMapper.selectAll());

   }

}


这里使用的ArticleMapper 就是上述自定义主数据源配置类中对应的mapperScan扫描下的mapper


4.使用(jdbc)


jdbc使用就直接使用@Resource获取对应名称的DataSource即可


如果多数据源使用方式为jdbc,可以不用定义mybatis一系列的Bean(sqlSessionFactory和sqlSessionTemplate)


package com.zhangyao.springboot.service.impl;


import com.alibaba.druid.pool.DruidDataSource;

import com.alibaba.druid.pool.DruidPooledConnection;

import com.zhangyao.springboot.service.ArticleService3;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

import org.springframework.stereotype.Service;


import javax.annotation.Resource;

import javax.sql.DataSource;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;


/**

* jdbc方式使用dataSource

* 这种情况下就不需要配置分包了

* @author: zhangyao

* @create:2020-04-07 08:42

**/

@Service

@Repository

public class ArticleServiceImpl3 implements ArticleService3 {


   @Resource(name = "test2DataSource")

   DruidDataSource druidDataSource;



   @Override

   public String testJdbcSql() {

       DruidPooledConnection connection = null;

       PreparedStatement preparedStatement = null;

       ResultSet resultSet = null;

       try {

           connection = druidDataSource.getConnection();

           String sql = "select * from article";

           preparedStatement = connection.prepareStatement(sql);

           resultSet = preparedStatement.executeQuery();

           while (resultSet.next()){

               System.out.println(resultSet.getString("article_content"));

           }


       } catch (SQLException e) {

           e.printStackTrace();

       }finally {

           try {

               if(connection!=null){

                   connection.close();

               }

               if(preparedStatement!=null){

                   preparedStatement.close();

               }

               if(resultSet!=null){

                   resultSet.close();

               }

           } catch (SQLException e) {

               e.printStackTrace();

           }

       }

       return null;

   }

}


5.使用(jdbcTemplate)


在第二步注入spring的配置文件中 增加两个数据源的jdbcTemplate Bean,参数就是对应数据源的dataSource


@Bean(name="test1JdbcTemplate")

public JdbcTemplate getJdbcTemplte(@Qualifier("test1DataSource")DataSource dataSource){

   return new JdbcTemplate(dataSource);

}


使用时直接通过@Resource引入不同数据源对应的JdbcTemplate即可


@Resource(name="test2JdbcTemplate")

JdbcTemplate jdbcTemplate;


在业务类中使用jdbcTemplate对应的方法


aop动态切换


aop动态切换数据源实现基于 spring-jdbc 提供的一个类 AbstractRoutingDataSource


在这个类中把所有的数据源都放到一个map中,通过切换map的键来获取不同的dataSource


具体实现如下


1.定义配置文件多数据源


同分包方案


2.定义数据源类型操作类


AbstractRoutingDataSource通过一个map来控制数据源的切换,就需要定义一个map.key的操作类,通过切换key之后,就能切换数据源


把数据源类型放在线程安全的ThreadLocal中,保证了每个线程使用不同数据源时,不会切换混乱


package com.zhangyao.springboot.config;


import lombok.extern.slf4j.Slf4j;


import javax.xml.crypto.Data;


/**

* 这里用来定义有多个数据源

* 设置数据源类型以及获取数据源类型

* @author: zhangyao

* @create:2020-04-07 09:24

**/

@Slf4j

public class DataSourceType {


   //数据源类型

   public enum DataBaseType{

       TEST1,TEST2

   }


   //使用threadLocal保证切换数据源时的线程安全 不会在多线程的情况下导致切换错数据源

   private static final ThreadLocal TYPE = new ThreadLocal();


   /**

    * 切换数据源

    * @param dataBaseType

    */

   public static  void setDataBaseType(DataBaseType dataBaseType){

       if(dataBaseType==null){

           throw new NullPointerException();

       }


       log.info("切换数据源"+dataBaseType);


       TYPE.set(dataBaseType);

   }



   /**

    * 获取当前线程内的数据源类型

    * @return

    */

   public static DataBaseType getDataBaseType(){

       DataBaseType dataBaseType = TYPE.get()==null?DataBaseType.TEST1:TYPE.get();

       log.info("当前数据源"+ dataBaseType);

       return  dataBaseType;

   }


   /**

    * 清空ThreadLocal中的TYPE

    */

   public void clear(){

       TYPE.remove();

   }


}


3.重写AbstractRoutingDataSource 方法


AbstractRoutingDataSource 是一个抽象类,需要重写AbstractRoutingDataSource 中的determineCurrentLookupKey方法


这个方法是用来获取当前使用的DataSource在map中的key的


package com.zhangyao.springboot.config;


import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


/**

* 继承AbStractRoutingDataSource

* 动态切换数据源

* @author: zhangyao

* @create:2020-04-07 09:23

**/

public class DynamicChangeDataSourceConfig extends AbstractRoutingDataSource {


   @Override

   protected Object determineCurrentLookupKey() {

       DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();

       return dataBaseType;

   }

}


把我们上面定义的线程安全的ThreadLocal中的DataBaseType设置进去


4.配置动态数据源bean


把重写后的AbstractRoutingDataSource 子类(DynamicChangeDataSourceConfig)注册进spring  把配置文件中定义的多数据源注入进去,生成一个动态的数据源


如下: 重点是dynamicDataSource 其他配置与分包方案类似


package com.zhangyao.springboot.config;


import com.alibaba.druid.pool.DruidDataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.boot.jdbc.DataSourceBuilder;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

import tk.mybatis.spring.annotation.MapperScan;


import javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;



/**

*

* aop多数据源动态切换配置

* @author: zhangyao

* @create:2020-04-06 22:17

**/


@Configuration

@MapperScan(basePackages = "com.zhangyao.springboot.mapper",sqlSessionFactoryRef = "sqlSessionFactory")

public class DataSource1Config {


   /**

    * 主数据源配置

    * @return

    */

   @Bean(name = "test1DataSource")

   @Primary

   @ConfigurationProperties(prefix = "spring.datasource.test1")

   public DruidDataSource getDataSource1(){

       DruidDataSource datasourc =  DataSourceBuilder.create().type(DruidDataSource.class).build();

       return datasourc;

   }


   /**

    * 第二个数据源配置

    * @return

    */

   @Bean(name = "test2DataSource")

   @ConfigurationProperties(prefix = "spring.datasource.test2")

   public DruidDataSource getDataSource2(){

       DruidDataSource datasourc =  DataSourceBuilder.create().type(DruidDataSource.class).build();

       return datasourc;

   }



   /**

    * 动态装配所有的数据源

    * @param dataSource1

    * @param dataSource2

    * @return

    */

   @Bean("dynamicDataSource")

   public DynamicChangeDataSourceConfig setDynamicDataSource(@Qualifier("test1DataSource") DataSource dataSource1,

                                                             @Qualifier("test2DataSource") DataSource dataSource2){

       //定义所有的数据源

       Map allDataSource = new HashMap();

       //把配置的多数据源放入map

       allDataSource.put(DataSourceType.DataBaseType.TEST1, dataSource1);

       allDataSource.put(DataSourceType.DataBaseType.TEST2, dataSource2);


       //定义实现了AbstractDataSource的自定义aop切换类

       DynamicChangeDataSourceConfig dynamicChangeDataSourceConfig = new DynamicChangeDataSourceConfig();

       //把上面的所有的数据源的map放进去

       dynamicChangeDataSourceConfig.setTargetDataSources(allDataSource);

       //设置默认的数据源

       dynamicChangeDataSourceConfig.setDefaultTargetDataSource(dataSource1);


       return dynamicChangeDataSourceConfig;

   }




   @Bean("sqlSessionFactory")

   @Primary

   public SqlSessionFactory getSqlSessionFactory(@Qualifier("dynamicDataSource") DynamicChangeDataSourceConfig dynamicChangeDataSourceConfig) throws Exception {

       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

       sqlSessionFactoryBean.setDataSource(dynamicChangeDataSourceConfig);

//        sqlSessionFactoryBean.setMapperLocations();


       return sqlSessionFactoryBean.getObject();

   }



   @Bean("sqlSessionTemplate")

   @Primary

   public SqlSessionTemplate getSqlSessionTemplate1(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory){

       return new SqlSessionTemplate(sqlSessionFactory);

   }

}


5.定义不同数据源的注解


定义不同数据源的注解,当这些注解放在service层的方法上时,通过aop来动态设置数据源


package com.zhangyao.springboot.annotation;


import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;


/**

* 定义数据源切面使用的注解

* @author: zhangyao

* @create:2020-04-07 11:30

**/



//表示此注解可以使用的方法上

@Target({ElementType.METHOD})

//表示在程序运行期生效

@Retention(RetentionPolicy.RUNTIME)

public @interface DataSource2ServiceAop {

}


package com.zhangyao.springboot.annotation;


import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;


/**

* 定义数据源切面使用的注解

* @author: zhangyao

* @create:2020-04-07 11:30

**/



//表示此注解可以使用的方法上

@Target({ElementType.METHOD})

//表示在程序运行期生效

@Retention(RetentionPolicy.RUNTIME)

public @interface DataSourceServiceAop {

}


6.开发aop切面


需要通过刚才的注解切入service方法,在方法执行前设置不同的数据源


package com.zhangyao.springboot.config;


import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;


/**

* @author: zhangyao

* @create:2020-04-07 11:20

**/

@Aspect

@Component

@Slf4j

public class DataSourceAop {


   /**

    * 定义切入点

    * 切入点为有该注解的方法

    * 此注解用于数据源TEST1

    */

   @Pointcut("@annotation(com.zhangyao.springboot.annotation.DataSourceServiceAop)")

   public void serviceTest1DatasourceAspect(){};

   /**

    * 定义切入点

    * 切入点为有该注解的方法

    * 此注解用于数据源TEST1

    */

   @Pointcut("@annotation(com.zhangyao.springboot.annotation.DataSource2ServiceAop)")

   public void serviceTest2DatasourceAspect(){};



   /**

    * 在切入service方法之前执行

    * 设置数据源TEST1

    */

   @Before("serviceTest1DatasourceAspect()")

   public void beforeAspect1(){

       log.info("切入方法,开始设置数据源TEST1");

       DataSourceType.setDataBaseType(DataSourceType.DataBaseType.TEST1);

   }

   /**

    * 在切入service方法之前执行

    * 设置数据源TEST2

    */

   @Before("serviceTest2DatasourceAspect()")

   public void beforeAspect2(){

       log.info("切入方法,开始设置数据源TEST2");

       DataSourceType.setDataBaseType(DataSourceType.DataBaseType.TEST2);

   }

}


7.使用(mybatis)


package com.zhangyao.springboot.service.impl;


import com.alibaba.fastjson.JSON;

import com.zhangyao.springboot.annotation.DataSourceServiceAop;

import com.zhangyao.springboot.mapper.test1.ArticleMapper;

import com.zhangyao.springboot.service.ArticleService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

import org.springframework.stereotype.Service;


/**

*

* @author: zhangyao

* @create:2020-04-06 22:46

**/


@Service

@Repository

public class ArticleServiceImpl implements ArticleService {



   @Autowired

   ArticleMapper articleMapper;


   @Override

   @DataSourceServiceAop

   public String queryAll() {

       return JSON.toJSONString(articleMapper.selectAll());

   }

}


加上@DataSourceServiceAop就会进入aop,切换不同的数据源


8.总结


aop动态切换数据源,实质上就是把继承了AbstractRoutingDataSource 的子类注入进spring,通过AbstractRoutingDataSource 类提供的方法来切换数据源


这是spring-jdbc提供的轻量级的多数据源切换解决方案


缺陷


  1. 需要引入分布式事务,生命式事务在操作时会有事务回滚问题
  2. 数据源较多时,重复代码冗余比较多,可以封装一个有参数的注解,把数据源传入
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
21小时前
|
移动开发 前端开发 NoSQL
ruoyi-nbcio从spring2.7.18升级springboot到3.1.7,java从java8升级到17(二)
ruoyi-nbcio从spring2.7.18升级springboot到3.1.7,java从java8升级到17(二)
|
1天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
1天前
|
XML Java 数据库连接
【JAVA基础篇教学】第十五篇:Java中Spring详解说明
【JAVA基础篇教学】第十五篇:Java中Spring详解说明
|
2天前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
20 2
|
3天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
7 0
|
4天前
|
Java
springboot项目出现Exception in thread “main“ java.lang.NoClassDefFoundError: javax/servlet/Filter
springboot项目出现Exception in thread “main“ java.lang.NoClassDefFoundError: javax/servlet/Filter
12 0
|
4天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
46 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
9天前
|
XML 监控 安全
18:面向切面编程-Java Spring
18:面向切面编程-Java Spring
27 5
|
9天前
|
缓存 NoSQL Java
17:缓存机制-Java Spring
17:缓存机制-Java Spring
23 5
|
9天前
|
Java 数据库连接 数据库
16:事务-Java Spring
16:事务-Java Spring
26 5