我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring Boot配置多数据源的四种方式

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring Boot配置多数据源的四种方式

1、导读

在日常开发中我们都是以单个数据库进行开发,在小型项目中是完全能够满足需求的。
但是,当我们牵扯到像淘宝、京东这样的大型项目的时候,单个数据库就难以承受用户的CRUD操作。
那么此时,我们就需要使用多个数据源进行读写分离的操作,这种方式也是目前一种流行的数据管理方式。

2、所需的资源

  1. Spring boot
  2. Mybatis-plus
  3. Alibab Druid数据库连接池
  4. MySql 数据库

3、Spring Boot配置多数据源

数据库

在这里插入图片描述

在YAML文件中定义数据源所需的数据

spring:  datasource:    type: com.alibaba.druid.pool.DruidDataSource ## 声明数据源的类型    mysql-datasource1: ## 声明第一个数据源所需的数据      url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&serverTimezone=Asia/Shanghai      username: root      password: 123456      driver-class-name: com.mysql.cj.jdbc.Driver    mysql-datasource2: ## 声明第二个数据源所需的数据      url: jdbc:mysql://localhost:3306/bookstore?useSSL=true&serverTimezone=Asia/Shanghai      username: root      password: 123456      driver-class-name: com.mysql.cj.jdbc.Driver    druid: ## druid数据库连接池的基本初始化属性      initial-size: 5 ## 连接池初始化的大小      min-idle: 1 ## 最小空闲的线程数      max-active: 20 ## 最大活动的线程数mybatis-plus:  mapper-locations: classpath:/mapper/*.xml ## 配置MyBatis-Plus扫描Mapper文件的位置  type-aliases-package: com.example.sqlite.entity ## 创建别名的类所在的包

mysql-datasource1、mysql-datasource2是自定义的数据。

定义多个数据源

@Configurationpublic class DataSourceConfig {    @Bean(name = "mysqlDataSource1")    @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource1")    public DataSource dataSource1(){        DruidDataSource build = DruidDataSourceBuilder.create().build();        return build;    }    @Bean(name = "mysqlDataSource2")    @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource2")    public DataSource dataSource2(){        DruidDataSource build = DruidDataSourceBuilder.create().build();        return build;    }}

@ConfigurationProperties注解用于将YAML中指定的数据创建成指定的对象,但是,YAML中的数据必须要与对象对象中的属性同名,不然无法由Spring Boot完成赋值。

由于我们要定义多个数据源,所以在Spring Boot数据源自动配置类中就无法确定导入哪个数据源来完成初始化,所以我们就需要禁用掉Spring Boot的数据源自动配置类,然后使用我们自定义的数据源配置类来完成数据源的初始化与管理。

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class DatasourceDomeApplication {    public static void main(String[] args) {        SpringApplication.run(DatasourceDomeApplication.class, args);    }}

在启动类上声明需要禁用的自动配置类:exclude = {DataSourceAutoConfiguration.class}

3.1、实现DataSource接口

缺点:产生大量的代码冗余,在代码中存在硬编码。

3.1.1、代码

@Component@Primarypublic class DynamicDataSource implements DataSource {//使用ThreadLocal而不是String,可以在多线程的时候保证数据的可靠性    public static ThreadLocal<String> flag = new ThreadLocal<>();    @Resource    private DataSource mysqlDataSource1; // 注入第一个数据源    @Resource    private DataSource mysqlDataSource2; // 注入第二个数据源    public DynamicDataSource(){ // 使用构造方法初始化ThreadLocal的值        flag.set("r");    }    @Override    public Connection getConnection() throws SQLException {    // 通过修改ThreadLocal来修改数据源,    // 为什么通过修改状态就能改变已经注入的数据源? 这就得看源码了。        if(flag.get().equals("r")){             return mysqlDataSource1.getConnection();        }         return mysqlDataSource2.getConnection();    }    @Override    public Connection getConnection(String username, String password) throws SQLException {        return null;    }    @Override    public PrintWriter getLogWriter() throws SQLException {        return null;    }    @Override    public void setLogWriter(PrintWriter out) throws SQLException {    }    @Override    public void setLoginTimeout(int seconds) throws SQLException {    }    @Override    public int getLoginTimeout() throws SQLException {        return 0;    }    @Override    public Logger getParentLogger() throws SQLFeatureNotSupportedException {        return null;    }    @Override    public <T> T unwrap(Class<T> iface) throws SQLException {        return null;    }    @Override    public boolean isWrapperFor(Class<?> iface) throws SQLException {        return false;    }}

实现DataSource接口我们本质上只使用了一个方法,就是getConnection()这个无参的方法,但是DataSource接口中所有的方法我们也都需要实现,只是不用写方法体而已,也就是存在了很多的 “废方法” 。
@Primary注解 == @Order(1),用于设置此类的注入顺序。

3.1.2、使用

// 访问第一个数据库的t_user表@RestControllerpublic class UserController {    @Resource    private UserService userService;    @GetMapping(value = "/user_list")    public List<User> showUserList(){        DynamicDataSource.flag.set("read"); // 修改数据源的状态        List<User> list = userService.list();        return list;    }    }
// 访问第二个数据库的Book表@RestControllerpublic class BookController {    @Resource    private BookService BookService;    @GetMapping(value = "/Book_list")    public List<Book> getBookList(){        DynamicDataSource.flag.set("write"); // 修改数据源的状态        List<Book> list = BookService.list();        return list;    }}

3.2、继承AbstrictRoutingDataSource类

减少了代码的冗余,但是还是会存在硬编码。

3.2.1、代码

@Primary@Componentpublic class DynamicDataSource extends AbstractRoutingDataSource {    public static ThreadLocal<String> flag = new ThreadLocal<>();    @Resource    private DataSource mysqlDataSource1;    @Resource    private DataSource mysqlDataSource2;    public DynamicDataSource(){        flag.set("read");    }    @Override    protected Object determineCurrentLookupKey() { // 通过Key来得到数据源        return flag.get();    }    @Override    public void afterPropertiesSet() {        Map<Object,Object> targetDataSource = new ConcurrentHashMap<>();        targetDataSource.put("read",mysqlDataSource1);        // 将第一个数据源设置为默认的数据源。        super.setDefaultTargetDataSource(mysqlDataSource1);        targetDataSource.put("write",mysqlDataSource2);         // 将Map对象赋值给AbstrictRoutingDataSource内部的Map对象中。        super.setTargetDataSources(targetDataSource);                super.afterPropertiesSet();    }}

AbstrictRoutingDataSource的本质就是利用一个Map将数据源存储起来,然后通过Key来得到Value来修改数据源。

3.2.2、使用

// 访问第一个数据库的t_user表@RestControllerpublic class UserController {    @Resource    private UserService userService;    @GetMapping(value = "/user_list")    public List<User> showUserList(){        DynamicDataSource.flag.set("read"); // 修改数据源的状态        List<User> list = userService.list();        return list;    }    }
// 访问第二个数据库的Book表@RestControllerpublic class BookController {    @Resource    private BookService BookService;    @GetMapping(value = "/Book_list")    public List<Book> getBookList(){        DynamicDataSource.flag.set("write"); // 修改数据源的状态        List<Book> list = BookService.list();        return list;    }}

3.3、使用Spring AOP + 自定义注解的形式

Spring AOP + 自定义注解的形式是一种推荐的写法,减少代码的冗余且不存在硬编码。
此方法适合对指定功能操作指定数据库的模式。

3.3.1、导入依赖

<dependency>   <groupId>org.springframework.bootgroupId>   <artifactId>spring-boot-starter-aopartifactId>dependency>

3.3.2、开启AOP支持

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableAspectJAutoProxy //开启Spring Boot对AOP的支持public class AopDatasourceApplication {    public static void main(String[] args) {        SpringApplication.run(AopDatasourceApplication.class, args);    }}

3.3.3、定义枚举来表示数据源的标识

public enum DataSourceType {    MYSQL_DATASOURCE1,    MYSQL_DATASOURCE2,}

3.3.4、继承AbstractRoutingDataSource类

@Primary@Componentpublic class DataSourceManagement extends AbstractRoutingDataSource {    public static ThreadLocal<String> flag = new ThreadLocal<>();    @Resource    private DataSource mysqlDataSource1;    @Resource    private DataSource mysqlDataSource2;    public DataSourceManagement(){        flag.set(DataSourceType.MYSQL_DATASOURCE1.name());    }    @Override    protected Object determineCurrentLookupKey() {        return flag.get();    }    @Override    public void afterPropertiesSet() {        Map<Object,Object> targetDataSource = new ConcurrentHashMap<>();        targetDataSource.put(DataSourceType.MYSQL_DATASOURCE1.name(),mysqlDataSource1);        targetDataSource.put(DataSourceType.MYSQL_DATASOURCE2.name(),mysqlDataSource2);        super.setTargetDataSources(targetDataSource);        super.setDefaultTargetDataSource(mysqlDataSource1);        super.afterPropertiesSet();    }}

3.3.5、自定义注解

@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TargetDataSource {    DataSourceType value() default DataSourceType.MYSQL_DATASOURCE1;}

3.3.6、定义注解的实现类

@Component@Aspect@Slf4jpublic class TargetDataSourceAspect {    @Before("@within(TargetDataSource) || @annotation(TargetDataSource)")    public void beforeNoticeUpdateDataSource(JoinPoint joinPoint){        TargetDataSource annotation = null;        Class<? extends Object> target = joinPoint.getTarget().getClass();        if(target.isAnnotationPresent(TargetDataSource.class)){            // 判断类上是否标注着注解             annotation = target.getAnnotation(TargetDataSource.class);             log.info("类上标注了注解");        }else{            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();            if(method.isAnnotationPresent(TargetDataSource.class)){                // 判断方法上是否标注着注解,如果类和方法上都没有标注,则报错                annotation = method.getAnnotation(TargetDataSource.class);                log.info("方法上标注了注解");            }else{                throw new RuntimeException("@TargetDataSource注解只能用于类或者方法上, 错误出现在:[" +                        target.toString() +" " + method.toString() + "];");            }        }        // 切换数据源        DataSourceManagement.flag.set(annotation.value().name());    }    }

在有的博客中也会使用@Around环绕通知的方式,但是环绕通知需要执行joinPoint.process()方法来调用目标对象的方法,最后返回执行的值,不然得不到所需要的数据。
我这里使用了@Before前置通知,效果是一样的,因为@Around就会包含@Before。

 @Around("@within(TargetDataSource) || @annotation(TargetDataSource)")    public Object beforeNoticeUpdateDataSource(ProceedingJoinPoint joinPoint){       // 省略逻辑代码       Object result = null;       try {            result = joinPoint.proceed();        } catch (Throwable e) {            e.printStackTrace();        }        return result;    }

ProceedingJoinPoint 对象只能在@Around环绕通知中使用,在其他通知中使用就会报错。

3.3.7、使用

// 访问第一个数据源。@RestController// 将注解标注在类上,表示本类中所有的方法都是使用数据源1@TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE1)public class UserController {    @Resource    private UserService userService;    @GetMapping(value = "/user_list")    public List<User> showUserList(){        System.out.println(DataSourceType.MYSQL_DATASOURCE1.name());        List<User> list = userService.list();        return list;    }}
// 访问第二个数据源@RestControllerpublic class BookController {    @Resource    private BookService BookService;    @GetMapping(value = "/Book_list")    // 将注解标注在方法上,表示此方法使用数据源2    @TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE2)    public List<Book> getBookList(){        List<Book> list = BookService.list();        return list;    }}

3.4、通过SqlSessionFactory指定的数据源来操作指定目录的XML文件

使用此方法则不会与上面所述的类有任何关系,本方法会重新定义类。
本方法也是一种推荐的方法,适用于对指定数据库的操作,也就是适合读写分离。不会存在代码冗余和存在硬编码。

3.4.1、项目的目录结构

对所需要操作的数据库的Mapper层和dao层分别建立一个文件夹。

在这里插入图片描述

3.4.2、配置YAML文件

spring:  datasource:    type: com.alibaba.druid.pool.DruidDataSource    mysql-datasource:      jdbc-url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&serverTimezone=Asia/Shanghai      username: root      password: 123456      driver-class-name: com.mysql.cj.jdbc.Driver    sqlite-datasource:      jdbc-url: jdbc:mysql://localhost:3306/bookstore?useSSL=true&serverTimezone=Asia/Shanghai      username: root      password: 123456      driver-class-name: com.mysql.cj.jdbc.Driver    druid:      initial-size: 5      min-idle: 1      max-active: 20mybatis-plus:  mapper-locations: classpath:/mapper/*.xml  type-aliases-package: com.example.sqlite.entity

3.4.3、针对Mapper层通过SqlSessionFactory指定数据源来操作

3.4.3.1、创建MySql数据源
@Configuration@MapperScan(basePackages = "com.example.sqlite.dao.mysql", sqlSessionFactoryRef = "MySQLSqlSessionFactory")public class MySQLDataSourceConfig {    @Bean(name = "MySQLDataSource")    @Primary    @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource")    public DataSource getDateSource1() {        return DataSourceBuilder.create().build();    }    @Bean(name = "MySQLSqlSessionFactory")    @Primary    public SqlSessionFactory test1SqlSessionFactory(            @Qualifier("MySQLDataSource") DataSource datasource) throws Exception {        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean ();        bean.setDataSource(datasource);        bean.setMapperLocations(// 设置mybatis的xml所在位置                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/mysql/*.xml"));        return bean.getObject();    }    @Bean("MySQLSqlSessionTemplate")    @Primary    public SqlSessionTemplate test1SqlSessionTemplate(            @Qualifier("MySQLSqlSessionFactory") SqlSessionFactory sessionFactory) {        return new SqlSessionTemplate(sessionFactory);    }    @Bean    public PlatformTransactionManager transactionManager(@Qualifier("MySQLDataSource")DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }}
3.4.3.2、创建Sqlite数据源
@Configuration@MapperScan(basePackages = "com.example.sqlite.dao.sqlite", sqlSessionFactoryRef = "SqliteSqlSessionFactory")public class SqliteDataSourceConfig {    @Bean(name = "SqliteDateSource")    @ConfigurationProperties(prefix = "spring.datasource.sqlite-datasource")    public DataSource getDateSource1() {        return DataSourceBuilder.create().build();    }    @Bean(name = "SqliteSqlSessionFactory")    public SqlSessionFactory test1SqlSessionFactory(            @Qualifier("SqliteDateSource") DataSource datasource) throws Exception {        MybatisSqlSessionFactoryBean  bean = new MybatisSqlSessionFactoryBean();        bean.setDataSource(datasource);        bean.setMapperLocations(                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/sqlite/*.xml"));        return bean.getObject();    }    @Bean("SqliteSqlSessionTemplate")    public SqlSessionTemplate test1SqlSessionTemplate(            @Qualifier("SqliteSqlSessionFactory") SqlSessionFactory sessionFactory) {        return new SqlSessionTemplate(sessionFactory);    }    @Bean    public PlatformTransactionManager transactionManager(@Qualifier("SqliteDateSource")DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }}
  1. @MapperScan注解中的basePackages指向的是指定的Dao层。
  2. @MapperScan注解中sqlSessionFactoryRef 用来指定使用某个SqlSessionFactory来操作数据源。
  3. bean.setMapperLocations(
    new PathMatchingResourcePatternResolver()
    .getResources(“classpath*:mapper/sqlite/*.xml”)); 指向的是操作执行数据库的Mapper层。

如果使用SQLite数据库,那么就必须在项目中内嵌SQLite数据库,这个一个轻量级的数据库,不同于Mysql,SQLite不需要服务器,SQLite适合使用于移动APP开发。
像微信,用户的聊天记录就是使用这个数据库进行存储。SQLite也可以使用在Web端,只是不太方便。

3.4.4、使用

// 访问第一个数据库@RestControllerpublic class UserController {    @Resource    private UserService userService;    @GetMapping(value = "/user_list")    public List<User> showUserList(){        List<User> list = userService.list();        return list;    }}
// 访问第二个数据库@RestControllerpublic class AddressController {    @Resource    private AddressService addressService;    @GetMapping(value = "/address_list")    public List<Address> getAddressList(){        List<Address> list = addressService.list();        return list;    }}

使用此种方法不会存在任何代码的冗余以及硬编码的存在,但是需要分层明确。
唯一的不足就是添加一个数据源就需要重新写一个类,而这个类中的代码大部分又是相同的。

4、总结

  1. 实现DataSource接口这种写法是不推荐的。
  2. 推荐使用Spring Boot + 自定义注解的方式与SqlSessionFactory方式。

另外,Spring AOP中各种通知的执行顺序如下图所示:在这里插入图片描述

来源地址:https://blog.csdn.net/qq_45515182/article/details/126330084

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spring Boot配置多数据源的四种方式

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring Boot配置多数据源的四种方式

1、导读 在日常开发中我们都是以单个数据库进行开发,在小型项目中是完全能够满足需求的。 但是,当我们牵扯到像淘宝、京东这样的大型项目的时候,单个数据库就难以承受用户的CRUD操作。 那么此时,我们就需要使用多个数据源进行读写分离的操作,这种
2023-08-20

Spring配置数据源的三种方式是什么

这篇文章主要介绍“Spring配置数据源的三种方式是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring配置数据源的三种方式是什么”文章能帮助大家解决问题。一、数据源的作用数据源(连接池)
2023-06-26

解决spring boot 1.5.4 配置多数据源的问题

spring boot 已经支持多数据源配置了,无需网上好多那些编写什么类的,特别麻烦,看看如下解决方案,官方的,放心!1.首先定义数据源配置#=====================multiple database config===
2023-05-31

Spring Boot多数据源及其事务管理配置方法

准备工作先给我们的项目添加Spring-JDBC依赖和需要访问数据库的驱动依赖。配置文件spring.datasource.prod.driverClassName=com.mysql.jdbc.Driverspring.datasourc
2023-05-31

详解基于Spring Boot与Spring Data JPA的多数据源配置

由于项目需要,最近研究了一下基于spring Boot与Spring Data JPA的多数据源配置问题。以下是传统的单数据源配置代码。这里使用的是Spring的Annotation在代码内部直接配置的方式,没有使用任何XML文件。@Con
2023-05-31

Spring Boot异步线程间数据传递的四种方式

这篇文章主要为大家介绍了Spring Boot异步线程间数据传递的四种方式详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-06

多数据源怎么利用spring boot进行配置

本篇文章给大家分享的是有关多数据源怎么利用spring boot进行配置,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。多数据源配置创建一个Spring配置类,定义两个DataS
2023-05-31

Mybatis-plus多数据源配置的两种方式总结

这篇文章主要为大家详细介绍了Mybatis-plus中多数据源配置的两种方式,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起了解一下
2022-11-13

详解Spring Boot整合Mybatis实现 Druid多数据源配置

一、多数据源的应用场景目前,业界流行的数据操作框架是 Mybatis,那 Druid 是什么呢?Druid 是 Java 的数据库连接池组件。Druid 能够提供强大的监控和扩展功能。比如可以监控 SQL ,在监控业务可以查询慢查询 SQL
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录