引言
本篇文章讲一下如何实现多数据源实现读写分离
,不涉及JTA
和微服务的分布式事务
,如果需要了解分布式事务
的可以在分布式专栏
下找到相关文章
。
使用dynamic-datasource-spring-boot-starter
pom 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
yaml 配置文件
server:
port: 8080
# 建立公共配置 下面可以直接引入
druid-common: &druid-common #以下均为默认值
initial-size: 10
min-idle: 10
max-active: 20
max-wait: 10
validation-query: SELECT 1
validation-query-timeout: 30000
test-on-borrow: true
test-on-return: true
test-while-idle: true
keep-alive: true
filters: stat,wall,log4j2
spring:
datasource:
dynamic:
primary: slave # 默认用哪个数据源
datasource:
master:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
druid: #以下均为默认值
<<: *druid-common
slave:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
druid: #以下均为默认值
<<: *druid-common
druid:
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: ["*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico"]
session-stat-max-count: 1000
session-stat-enable: true
profile-enable: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: root
login-password: root
allow: 0.0.0.0
reset-enable: true
filter: # 如果不是使用log4j2请注释掉
log4j2:
enabled: true
使用@DS注解
注意:Service方法
加上@DS注解
举个栗子:
//默认使用从库查询
public Account findAccountByName(String name) {
return accountDao.findAccountByName(name);
}
@DS("master") //使用主库更新
public int updateAccount(Account account) {
return accountDao.updateAccount(account);
}
手动实现理解原理
还是之前那个yaml配置文件
,这里手动实现
,我们需要实现
AbstractRoutingDataSource
的实现类。DS注解
。AOP
将对应的dataSourceKey
设置到AbstractRoutingDataSource
的实现类。
AbstractRoutingDataSource
AbstractRoutingDataSource
是一个抽象类
,当需要调用数据源时,getConnection()
方法根据determineCurrentLookupKey
返回的key(string类型)
查找不同的目标数据源(bean id)
,通常我们会写一个ContextHolder
,但是为了让代码看起来更直观
,我们直接继承后省略掉ContextHolder
。
package com.doub1.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDatasource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void setDataSourceKey(String dataSourceKey) {
holder.set(dataSourceKey);
}
public static string getDataSourceKey() {
return holder.get();
}
//我们只需要实现这玩意即可
@Override
protected Object determineCurrentLookupKey() {
return getDataSourceKey();
//return DataSourceContextHandler.getDataSource();
}
}
实现@DS注解
package com.doub1.datasource;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DS {
String value();
}
基于AOP给RoutingDatasource设置KEY
package com.doub1.datasource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component("chooseDataSourceAspect")
public class ChooseDataSourceAspect {
@Value("${spring.datasource.dynamic.primary}")
private string primary;
// 对service中的实现类找@DS注解,如果找到就设置对应的KEY
@Before(value="execution(* com.doub1.service.impl..*.*(..))")
public void before(JoinPoint point)
{
Object target = point.getTarget();
String methodName = point.getSignature().getName();
//懒得对接口检查了,如果需要自己写一下
//Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = target.getClass().getMethod(methodName, parameterTypes);
if (m != null && m.isAnnotationPresent(DS.class)) {
DS ds = m.getAnnotation(DS.class);
RoutingDatasource.setDataSourceKey(ds.value());
}else{
RoutingDatasource.setDataSourceKey(primary);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建不同数据源
@Bean("master")
@ConfigurationProperties("spring.datasource.dynamic.datasource.master")
DruidDataSource master(){
return DruidDataSource();
}
@Bean("slave")
@ConfigurationProperties("spring.datasource.dynamic.datasource.slave")
DruidDataSource slave(){
return DruidDataSource();
}