JAVA—Spring—SpringBoot—springboot多数据源

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
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. 数据源较多时,重复代码冗余比较多,可以封装一个有参数的注解,把数据源传入
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
19天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
7天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
1月前
|
Java 数据库连接
SpringBoot配置多数据源实战
第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision
59 8
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
109 2
|
1月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
45 0
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
39 0
下一篇
DataWorks