Java小强个人技术博客站点    手机版
当前位置: 首页 >> DB >> dynamic动态数据源集成druid实现多数据源

dynamic动态数据源集成druid实现多数据源

12570 DB | 2023-6-30

dynamic特性

支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。

支持数据库敏感配置信息 加密(可自定义) ENC()。

支持每个数据库独立初始化表结构schema和数据库database。

支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。

支持 自定义注解 ,需继承DS(3.2.0+)。

提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。

提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。

提供 自定义数据源来源 方案(如全从数据库加载)。

提供项目启动后 动态增加移除数据源 方案。

提供Mybatis环境下的 纯读写分离 方案。

提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。

支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。

提供 基于seata的分布式事务方案 。

提供 本地多数据源事务方案。

约定

本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。

配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下

切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。

默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。

方法上的注解优先于类上注解。

DS支持继承抽象类上的DS,暂不支持继承接口上的DS


dynamic多数据源.jpg


为监控而生的数据库连接池

https://github.com/alibaba/druid 

一个基于springboot的快速集成多数据源的启动器

https://github.com/baomidou/dynamic-datasource-spring-boot-starter 


首先创建一个空的Maven项目,用于测试,在项目POM中配置需要的引入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.7.13</version>
       <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
       <java.version>1.8</java.version>
    </properties>
    <dependencies>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
          <groupId>com.mysql</groupId>
          <artifactId>mysql-connector-j</artifactId>
          <scope>runtime</scope>
       </dependency>
       <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-starter</artifactId>
          <version>1.2.6</version>
       </dependency>
       <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
          <version>3.4.0</version>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jdbc</artifactId>
       </dependency>
    </dependencies>
    <build>
       <plugins>
          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
       </plugins>
    </build>
</project>

治理直接使用JdbcTemplate进行数据库测试操作


配置YML配置文件

server:
  servlet:
    context-path: /
  port: 1088
# 数据源配置
spring:
  autoconfigure:
    # 排除 Druid 自动配置
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    # 指定使用 Druid 数据源
    type: com.alibaba.druid.pool.DruidDataSource
    dynamic:
      #设置默认的数据源或者数据源组,默认值即为 master
      primary: master
      datasource:
        # 主库数据源
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: 123456789
          druid:
            # 初始连接数
            initialSize: 5
            # 最小连接池数量
            minIdle: 10
            # 最大连接池数量
            maxActive: 20
            # 配置获取连接等待超时的时间
            maxWait: 60000
        # 从库数据源
        slave:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: 123456789
          druid:
            # 初始连接数
            initialSize: 2
            # 最小连接池数量
            minIdle: 5
            # 最大连接池数量
            maxActive: 20
            # 配置获取连接等待超时的时间
            maxWait: 60000
    druid:
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # 控制台管理用户名和密码
        login-username: admin
        login-password: 123456
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true

这里配置了两个数据源,这两个库中都有一张表

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;


编写Dao层代码

package com.example.demo;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class TestDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @DS("master")
    public String getDb1(){
        return jdbcTemplate.queryForMap("select name from user where id=1").get("name").toString();
    }
    @DS("slave")
    public String getDb2(){
        return jdbcTemplate.queryForMap("select name from user where id=1").get("name").toString();
    }
    @DS("master")
    public void update1(){
        jdbcTemplate.update("update user set name='修改 1' where id=1");
    }
    @DS("slave")
    public void update2(){
        jdbcTemplate.update("update user set name='修改 2' where id=1");
        // System.out.println(1/0);
    }
}

如果你使用的是MyBatis,Mappper中都是接口,按照官方说法,你应该把切换数据源的注解写到Service方法上。


编写Service层代码,为了演示,这里直接编写实现类,没有接口

package com.example.demo;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TestService {
    @Autowired
    private TestDao testDao;
    /**
     * 必须通过Spring代理的方式调用,同一个类中调用的话,不会切换数据源
     */
    public String getDb1(){
        return testDao.getDb1();
    }
    public String getDb2(){
        return testDao.getDb2();
    }
    /**
     * dynamic提供的事物控制,这里无需指定回滚异常
     */
    @DSTransactional
    public void update(){
        testDao.update1();
        testDao.update2();
    }
}


编写Controller接口,用于测试调用

package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private TestService testService;
    /**
     * http://localhost:1088/test/get
     */
    @GetMapping("/get")
    public String get(){
        System.out.println("第一次查询:" + testService.getDb1());
        System.out.println("第二次查询:" + testService.getDb2());
        return "当前时间:" + new Date().getTime();
    }
    /**
     * http://localhost:1088/test/update
     */
    @GetMapping("/update")
    public String update(){
        testService.update();
        return "当前时间:" + new Date().getTime();
    }
}


启动类

package com.example.demo;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * 多数据源测试示例
 */
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class DemoApplication {
    public static void main(String[] args) throws UnknownHostException {
       ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
       String serverPort = context.getEnvironment().getProperty("server.port");
       System.out.println("( 系统启动成功  \n" +
             "通过以下地址访问 http://localhost:" + serverPort + "\n" +
             "              http://" + InetAddress.getLocalHost().getHostAddress() + ":" + serverPort);
    }
}


默认druid的管理后台下面带广告,如果你不喜欢,可以增加如下配置类

package com.example.demo;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import java.io.IOException;
/**
 * druid 配置多数据源
 */
@Configuration
public class DruidConfig {
    /**
     * 去除监控页面底部的广告
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
        final String filePath = "support/http/resources/js/common.js";
        // 创建filter进行过滤
        Filter filter = new Filter() {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
            }
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                chain.doFilter(request, response);
                // 重置缓冲区,响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }
            @Override
            public void destroy() {
            }
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }
}


通过Controller接口访问查询和更新操作,注意注解

@DSTransactional

是dynamic用于控制多数据源事物的,可Dao中的异常放开查看效果。


参考工程代码

demo.zip


推荐您阅读更多有关于“ 事物 Druid dynamic datasource 多数据源 ”的文章

上一篇:内存队列Disruptor 下一篇:线程安全的List之CopyOnWriteArrayList

猜你喜欢

发表评论: