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。

为监控而生的数据库连接池
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中的异常放开查看效果。
参考工程代码:
推荐您阅读更多有关于“ 事物 Druid dynamic datasource 多数据源 ”的文章
Java小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: