JAVA—Spring—SpringBoot—springboot多数据源

简介: 分包在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. 数据源较多时,重复代码冗余比较多,可以封装一个有参数的注解,把数据源传入
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
10月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
10月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
10月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
10月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
800 2
|
10月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
844 8
|
11月前
|
人工智能 Java API
构建基于Java的AI智能体:使用LangChain4j与Spring AI实现RAG应用
当大模型需要处理私有、实时的数据时,检索增强生成(RAG)技术成为了核心解决方案。本文深入探讨如何在Java生态中构建具备RAG能力的AI智能体。我们将介绍新兴的Spring AI项目与成熟的LangChain4j框架,详细演示如何从零开始构建一个能够查询私有知识库的智能问答系统。内容涵盖文档加载与分块、向量数据库集成、语义检索以及与大模型的最终合成,并提供完整的代码实现,为Java开发者开启构建复杂AI智能体的大门。
5769 58
|
11月前
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
1081 117
|
11月前
|
人工智能 Java 机器人
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
Spring AI Alibaba集成Ollama,基于Java构建本地大模型应用,支持流式对话、knife4j接口可视化,实现高隐私、免API密钥的离线AI服务。
7478 2
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
存储 JSON Java
995 0
|
11月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
3009 0