前言
Java项目中多个数据源,相应配置拿些方法哪些类访问
类似JDBC每个类都要写一遍会比较冗余,有没有集中式管理呢??
看这篇文章之前推荐阅读:
对于DS注解切换数据源,踩了很多坑,一一尝试之后,对此总结其基本知识
对于DS注解的基本知识推荐阅读:
尝试了好几次版本,发现一直失效,一开始以为是用错了,但是后面发现一定要经过AOP的切面类,也就是@EnableAspectJAutoProxy(exposeProxy = true)
这种方式
1. 基本知识
动态数据源(Dynamic Datasource)是指在运行时根据不同的条件动态切换数据源的技术
在分布式系统、微服务架构和多租户系统中尤为重要
-
数据源:数据库连接信息的集合,通常包含数据库的URL、用户名、密码、驱动类等
-
动态数据源:根据业务逻辑或其他条件动态切换到不同的数据源,以实现读写分离、负载均衡、容灾备份等功能
其应用场景有如下:
- 读写分离:将读操作和写操作分配到不同的数据库上,提高系统的读写性能
- 负载均衡:通过多数据源分担负载,防止单个数据库过载
- 多租户系统:为每个租户配置独立的数据源,隔离数据,保障安全性
- 容灾备份:在主数据库出现故障时,自动切换到备份数据库,保证系统的高可用性
再来说说DS注解的基本概念:
@DS
注解用于动态数据源切换,用于指定方法或类使用特定的数据源
@DS
注解一般来自MyBatis-Plus中的Dynamic DataSource模块,它允许在方法级别进行数据源切换
需要注意下核心的功能:
- 方法级别的数据源切换:@DS注解可以直接应用在方法上,使该方法在执行时使用指定的数据源
- 类级别的数据源切换:@DS注解可以应用在类上,使该类中的所有方法在执行时使用指定的数据源
- 优先级:方法级别的@DS注解优先于类级别的@DS注解
引入对应的依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
配置对应的数据源:
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx)
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
基本的切换数据源方式如下:
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
@DS("slave_1")
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
2. 源码分析
查看其源码:
package com.baomidou.dynamic.datasource.annotation;
import java.lang.annotation.*;
/**
* The core Annotation to switch datasource. It can be annotate at class or method.
*
* @author TaoYu Kanyuxia
* @since 1.0.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
/**
* groupName or specific database name or spring SPEL name.
*
* @return the database you want to switch
*/
String value();
}
-
@Target({ElementType.TYPE, ElementType.METHOD})
:指定了注解@DS
可以应用在哪些地方,指定了@DS
注解可以应用在类和方法上 -
@Retention(RetentionPolicy.RUNTIME)
:指定了注解@DS
在程序运行时保留,可以通过反射来访问并解析@DS
注解的信息 -
@Documented
:标记这个注解是应该被 javadoc 工具记录的,当生成 API 文档时,如果一个类用@Documented
注解修饰,那么它的注解将出现在生成的文档中 -
String value()
;:属性,返回一个字符串,用于指定要切换到的数据库的名称
3. Demo
对应的版本号此处使用的4.3.0
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
其数据源配置如下:
spring:
datasource:
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 1 # 初始连接数
min-idle: 1 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
name: manong
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
# url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
username: root
password: root
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# username: SYSDBA # DM 连接的示例
# password: SYSDBA # DM 连接的示例
slave: # 模拟从库,可根据自己需要修改
name: manong
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
username: root
password: root
在写代码的时候有两个注意事项:
- 单机 + 单数据源:@Transactional 注解
- 单机 + 多数据源:@DSTransactional 注解(新版本支持,旧版本可以使用
@Transactional(propagation = Propagation.REQUIRES_NEW)
) - 多机 + 单/多数据源:Seata 分布式事务
3.1 成功案例
基本的代码如下:
- 通过其他实现类来实现:(此处为GoodsStoragePlanServiceImpl 类)
@Service
@Validated
public class GoodsStoragePlanServiceImpl implements GoodsStoragePlanService {
@Autowired
private JdbcTemplate oracleJdbcTemplate;
@Autowired
private EnterpriseRegistryServiceImpl enterpriseRegistryServiceimpl;
@DS("slave")
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void selectGoodsStoragePlan() {
enterpriseRegistryServiceimpl.selectGoodsStoragePlan();
}
}
引用EnterpriseRegistryServiceImpl 类来触发
@Service
@Validated
public class EnterpriseRegistryServiceImpl implements EnterpriseRegistryService {
@Autowired
private JdbcTemplate oracleJdbcTemplate;
@DS("tos200")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void selectGoodsStoragePlan() {
String sql = "SELECT * FROM C_OPER_PATH";
oracleJdbcTemplate.queryForList(sql);
System.out.println(oracleJdbcTemplate.queryForList(sql));
}
}
对于成功的例子 说明是走了AOP切面类,从而触发DS的注解,而失败案例是没有触发到,直接在使用类上触发不到
此处为了说明AOP切面类的执行过程,并不是很规范
- 规范一些的话可以通过一个CommonImpl类来做一个切口,规定这是某个数据库的
- Impl实现类调用Mapper,Mapper使用注解来触发
3.2 失败案例
impl中直接调用另外一个DS注解的方法,AOP切面类无法执行成功
@Service
@Validated
public class GoodsStoragePlanServiceImpl implements GoodsStoragePlanService {
@Autowired
private JdbcTemplate oracleJdbcTemplate;
@Override
public void selectGoodsStoragePlan() {
select();
}
@DS("slave")
@Transactional(propagation = Propagation.REQUIRES_NEW)
publivc void select(){
String sql = "SELECT * FROM C_OPER_PATH";
oracleJdbcTemplate.queryForList(sql);
System.out.println(oracleJdbcTemplate.queryForList(sql));
}
}
切记,必须只有经过AOP的切面才可执行成功,否则单纯加个注解DS,不一定能成功
另外一种动态切换数据源的方式推荐阅读:详细分析Java中DynamicDataSourceContextHolder动态数据源切换(附Demo)
4. 实战
以下实战与上述Demo类似,只不过用在了方法上 (与正常开发一样,只不过在此处添加了该注解)
@Service
@DS("slave")
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public class TemperatureAlarmHistServiceImpl implements ITemperatureAlarmHistService {
@Resource
private TemperatureAlarmHistMapper temperatureAlarmHistMapper;
public boolean saveForList(List<TemperatureAlarmHist> temperatureAlarmHistList) {
return temperatureAlarmHistMapper.saveForList(temperatureAlarmHistList);
}
}
对应的数据源配置如下:
#数据源配置
spring:
# 排除DruidDataSourceAutoConfigure
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/db_master
username: root
password: root
slave:
url: jdbc:oracle:manong:@//localhost:1521/GIS
username: root
password: ROOTGPS1
ep:
url: jdbc:sqlserver://localhost:1433;databaseName=manong
username: root
password: root
提供的配置中,exclude 属性指定了要排除的自动配置类 com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
这样做的目的是排除Druid连接池的自动配置,避免它与手动配置的数据源发生冲突
另外一种排除的方式:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
前提是 上述这两种方式本身已经引入了该依赖包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>