对于SpringMVC,多数据源是通过aop去实现。Springboot也大同小异。
除了常规的springboot依赖之外,再加上aop,数据连接池等依赖。
1.8
2.9.2
1.2.5
1.1.14
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
org.springframework.boot
spring-boot-devtools
runtime
true
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
com.alibaba
druid-spring-boot-starter
${druid.version}
org.springframework.boot
spring-boot-starter-aop
com.github.pagehelper
pagehelper-spring-boot-starter
${pagehelper.boot.version}
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
这里,测试的情况使用的是同一个库,只要数据源会切换过去就行。
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 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触发那里缓存当前选择的数据源,要线程安全。
/**
* com.fengzhen.datasources.config
* Created by ZhiLiSteven
* 动态切换数据源
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map
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
此处,配置了两个数据源,并通过读取yml参数配置初始化。DynamicDataSource就是上面覆写AbstractRoutingDataSource的类。
/**
* 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 selectMenuAll1() {
return sysMenuService.selectMenuAll();
}
@DataSource(value = DataSourceType.SLAVE)
@RequestMapping(value = "list2")
@ResponseBody
public List selectMenuAll2() {
return sysMenuService.selectMenuAll();
}
}
常规的@DataSource使用默认的数据源。value可以设置其他数据源。