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小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: