来源:https://blog.csdn.net/acquaintanceship/article/details/75350653
SpringBoot多数据源切换,先上配置文件:
1.pom:
4.0.0
com.river
goalintl
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.5.4.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-freemarker
org.springframework.boot
spring-boot-devtools
org.springframework.boot
spring-boot-starter-aop
org.projectlombok
lombok
org.springframework.boot
spring-boot-configuration-processor
com.alibaba
druid
1.1.6
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
com.baomidou
mybatis-plus
2.1.1
com.baomidou
mybatisplus-spring-boot-starter
1.0.4
org.springframework.boot
spring-boot-starter-jdbc
2.application.yaml
spring:
freemarker:
cache: false
charset: utf-8
enabled: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
primary:
driverClassName: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/as
type: com.alibaba.druid.pool.DruidDataSource
local:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/test
type: com.alibaba.druid.pool.DruidDataSource
prod:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/guns
type: com.alibaba.druid.pool.DruidDataSource
server:
port: 8085
diy.user:
id: 12
logging:
file: /log.txt
level: trace
mybatis-plus:
mapper-locations: classpath:mapper/*Mapper.xml
typeAliasesPackage:
global-config:
id-type: 3
refresh-mapper: true
capital-mode: true
field-strategy: 2
db-column-underline: false
3.configuration
package com.river.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import com.river.common.ContextConst;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class MutiplyDataSource {
@Bean(name = "dataSourcePrimary")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource(){
return new DruidDataSource();
}
@Bean(name = "dataSourceLocal")
@ConfigurationProperties(prefix = "spring.datasource.local")
public DataSource localDataSource(){
return new DruidDataSource();
}
@Bean(name = "dataSourceProd")
@ConfigurationProperties(prefix = "spring.datasource.prod")
public DataSource prodDataSource(){
return new DruidDataSource();
}
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//配置默认数据源
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
//配置多数据源
HashMap
4.数据源持有类
package com.river.datasource;
import lombok.extern.log4j.Log4j;
@Log4j
public class DataSourceContextHolder {
private static final String DEFAULT_DATASOURCE = "PRIMARY_DATASOURCE";
private static final ThreadLocal contextHolder = new ThreadLocal();
public static void setDataSource(String dbType){
log.info("切换到["+dbType+"]数据源");
contextHolder.set(dbType);
}
public static String getDataSource(){
return contextHolder.get();
}
public static void clearDataSource(){
contextHolder.remove();
}
}
5.定义切换数据源的注解和切面
package com.river.annotation;
import com.river.common.ContextConst;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
ContextConst.DataSourceType value() default ContextConst.DataSourceType.PRIMARY;
}
package com.river.aspect;
import com.river.annotation.DataSource;
import com.river.common.ContextConst;
import com.river.datasource.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
public class DynamicDataSourceAspect {
@Before("execution(* com.river.service..*.*(..))")
public void before(JoinPoint point){
try {
DataSource annotationOfClass = point.getTarget().getClass().getAnnotation(DataSource.class);
String methodName = point.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
DataSource methodAnnotation = method.getAnnotation(DataSource.class);
methodAnnotation = methodAnnotation == null ? annotationOfClass:methodAnnotation;
ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() !=null ? methodAnnotation.value() :ContextConst.DataSourceType.PRIMARY ;
DataSourceContextHolder.setDataSource(dataSourceType.name());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@After("execution(* com.river.service..*.*(..))")
public void after(JoinPoint point){
DataSourceContextHolder.clearDataSource();
}
}
package com.river.common;
public interface ContextConst {
enum DataSourceType{
PRIMARY,LOCAL,PROD,TEST
}
}
6.数据源路由实现类
package com.river.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
7.使用时,通过注解指定数据源
package com.river.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.river.annotation.DataSource;
import com.river.common.ContextConst;
import com.river.entity.User;
import com.river.mapper.PrimaryUserMapper;
import com.river.service.ParmaryUserService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ParmaryUserServiceImpl extends ServiceImpl implements ParmaryUserService{
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Override
public List sel(){
return primaryUserMapper.selectList(null);
}
@DataSource(ContextConst.DataSourceType.PROD)
@Override
public List sell() {
return primaryUserMapper.selectList(null);
}
@DataSource(ContextConst.DataSourceType.LOCAL)
@Override
public List selle() {
return primaryUserMapper.selectList(null);
}
}
7.另外,补充一下其他部分代码;
entity:
@Data
@TableName("ACT_USER")
public class User {
@TableId
@TableField
private Integer id;
@TableField("USERNAME")
private String username;
@TableField("PASSWORD")
private String password;
@TableField("ROLE_ID")
private Integer roleId;
}
mapper:
package com.river.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.river.entity.User;
public interface PrimaryUserMapper extends BaseMapper{
}
controller:
@RestController
public class DataController {
@Autowired
private ParmaryUserService parmaryUserService;
@RequestMapping("login")
public List login(Integer type){
switch (type){
case 1:
return parmaryUserService.sel();
case 2:
return parmaryUserService.sell();
default:
return parmaryUserService.selle();
}
}
}
入口类:
package com.river;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@MapperScan("com.river.mapper")
//排除DataSource自动配置类,否则会默认自动配置,不会使用我们自定义的DataSource,并且启动报错
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GoalintlApplication implements CommandLineRunner,ApplicationContextAware{
public static void main(String[] args) {
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(GoalintlApplication.class);
springApplicationBuilder.profiles("dev").logStartupInfo(true).run(args);
}
@Override
public void run(String... args) throws Exception {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}
说明一下实现思路:
springboot有提供AbstractRoutingDataSource#determineCurrentLookupKey抽象方法去指定数据源,我们要做的就是实现切换数据源的逻辑;通过AOP在调用数据库之前切换数据源;
本来在切面内做了一个缓存,记录上一次使用的数据源,如果这一次使用相同的就不用切换了,但是发现初始化数据连接才是消耗大的,后面切换数据源其实就是去指定用哪个数据库连接而已,不再消耗资源了;
下面的代码显示了切换数据源时只是通过key去拿对应的dataSource,而相关的dataSource在第一次调用时就初始化一次就可以了;
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
类似文章很多,这里自己实现了一把;