对于SpringMVC,多数据源是通过aop去实现。Springboot也大同小异。
1.配置依赖
除了常规的springboot依赖之外,再加上aop,数据连接池等依赖。
<properties>
<java.version>1.8</java.version>
<swagger.version>2.9.2</swagger.version>
<pagehelper.boot.version>1.2.5</pagehelper.boot.version>
<druid.version>1.1.14</druid.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.boot.version}</version>
</dependency>
</dependencies>
取消自动数据源配置
DatasourcesApplication.java
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class DatasourcesApplication {
public static void main(String[] args) {
SpringApplication.run(DatasourcesApplication.class, args);
}
}
配置参数
这里,把数据源相关的配置单独放到datasource profile里。
application.yml
server:
port: 8235
servlet:
# 应用的访问路径
context-path: /
tomcat:
uri-encoding: UTF-8
threads:
max: 400
min-spare: 20
spring:
thymeleaf:
mode: HTML
encoding: UTF-8
cache: false
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
profiles:
# 额外读取datasource配置
active: datasource
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
devtools:
restart:
# 热部署开关
enabled: true
mybatis:
# 搜索指定包别名
type-aliases-package: com.fengzhen.**.entity
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapper-locations: classpath:/mybatis/**/*Mapper.xml
# 加载全局的配置文件
config-location: classpath:mybatis-config.xml
# PageHelper分页插件
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
application-datasource.yml
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
# 主数据库
master:
jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
# 其他数据库(从数据库)
slave:
enabled: true
jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
# 初始连接数
initial-size: 5
# 最小连接池数量
min-idle: 10
# 最大连接池数量
max-active: 20
# 配置获取连接等待超时的时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
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
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 取消自动提交
default-auto-commit: false
这里,测试的情况使用的是同一个库,只要数据源会切换过去就行。
通过AbstractRoutingDataSource实现数据源动态切换
Springboot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,切换到需要的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
配置枚举
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 数据源枚举类
*
*/
public enum DataSourceType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}
此处,枚举的名称其实没太大关系。只要自己知道哪个名称对应哪个数据源就行。
配置数据源切换注解
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.MASTER;
}
此处设置了类和方法都可配的注解。方便接口开发时同一个数据源,只需一个类的注解。
缓存数据源选择
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 数据源切换处理
*
*/
public class DynamicDataSourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dataSourceType) {
logger.info("切换到{}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
在aop触发那里缓存当前选择的数据源,要线程安全。
继承AbstractRoutingDataSource
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 动态切换数据源
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}
/**
* 根据Key获取数据源的信息
* @return
*/
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
determineCurrentLookupKey在每次取数据源时,决定当前取哪个数据源。
配置多数据源
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 配置多数据源(Druid)
*
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
此处,配置了两个数据源,并通过读取yml参数配置初始化。DynamicDataSource就是上面覆写AbstractRoutingDataSource的类。
配置aop
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 数据源注解AOP拦截并切换
*
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.fengzhen.datasources.config.DataSource) || @within(com.fengzhen.datasources.config.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
* @param point
* @return
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (dataSource != null) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
通过注解配置的切入点,进行注解读取,然后选择对应的数据源。
接口层配置
通过上述配置,多数据源配置完成了。接着就可以在controller里使用了。
/**
* com.fengzhen.datasources.web
* Created by ZhiLiSteven
*/
@Controller
@RequestMapping(value = "sysMenu")
public class SysMenuController {
@Autowired
private SysMenuService sysMenuService;
@DataSource
@RequestMapping(value = "list1")
@ResponseBody
public List<SysMenu> selectMenuAll1() {
return sysMenuService.selectMenuAll();
}
@DataSource(value = DataSourceType.SLAVE)
@RequestMapping(value = "list2")
@ResponseBody
public List<SysMenu> selectMenuAll2() {
return sysMenuService.selectMenuAll();
}
}
常规的@DataSource使用默认的数据源。value可以设置其他数据源。