一、代码(这里举例两个数据源)
1.pom.xml文件需要引入的依赖
<!-- Spring Boot整合Spring MVC的依赖项,包含Spring Boot的基础依赖项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL依赖项,仅运行时需要 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok依赖包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
2.xml配置文件
配置读取resource文件夹下的mapper文件
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.定义enum枚举类
定义数据源枚举类DataSourceTypeEnum,mysql-mrf和mysql-mrf-two为数据库名称
public enum DataSourceTypeEnum {
/**
* MEF1
* 数据源1
*/
MRF1("mysql-mrf"),
/**
* MRF2
* 数据源2
*/
MRF2("mysql-mrf-two");
private final String name;
DataSourceTypeEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
4.定义切换数据源注解
我这里设置的默认使用MREF1数据源
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpecifyDataSource {
/**
* @return
*/
DataSourceTypeEnum value() default DataSourceTypeEnum.MRF1;
}
5.定义动态多数据源类
定义一个动态多数据源类DynamicDataSource用于管理不同线程间多个数据源的选择和切换,扩展 Spring 提供的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法,其中的determineCurrentLookupKey() 方法用于决定使用哪个数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
* 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
*
* @param defaultTargetDataSource 默认数据源
* @param targetDataSources 目标数据源
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/**
* determineCurrentLookupKey决定使用哪个数据库
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
6.定义多数据源配置类
读取配置文件的两个数据源的配置,创建对应DataSource类型的Bean。
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfig {
@Bean(name="mysql-mrf")
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource dataSource1(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name ="mysql-mrf-two")
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource dataSource2(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name="dynamicDataSource")
@Primary
public DynamicDataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>(5);
targetDataSources.put(DataSourceTypeEnum.MRF1.getName(), dataSource1());
targetDataSources.put(DataSourceTypeEnum.MRF2.getName(), dataSource2());
return new DynamicDataSource(dataSource1(), targetDataSources);
}
}
5.通过AOP设置注解执行位置
定义数据源界面类DataSourceAspect,用于实现有@SpecifyDataSource(value = DataSourceTypeEnum.MRF2)注解标注的方法前切换注解指定的数据源,未添加注解时ds为空,则指定数据源为MRF1
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Order(value = 1)
@Slf4j
public class DataSourceAspect {
@Pointcut("@annotation(com.example.javamrf.config.dataSource.SpecifyDataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SpecifyDataSource ds = method.getAnnotation(SpecifyDataSource.class);
if (ds == null) {
DynamicDataSource.setDataSource(DataSourceTypeEnum.MRF1.getName());
log.info("切换数据源:{}", DataSourceTypeEnum.MRF1);
} else {
DynamicDataSource.setDataSource(ds.value().getName());
log.info("切换数据源:{}", ds.value().getName());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
log.info("clean datasource");
}
}
}
7.yml配置文件
配置文件可以根据自己使用的数据库进行修改
# Spring配置
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
first:
# 连接数据库的URL
url: jdbc:mysql://localhost:3306/mysql-mrf?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 连接数据库的用户名
username: root
# 连接数据库的密码
password: root
# 连接数据库的URL
second:
url: jdbc:mysql://localhost:3306/mysql-mrf-two?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 连接数据库的用户名
username: root
# 连接数据库的密码
password: root
#输出sql执行日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
8.测试
@SpecifyDataSource(value = DataSourceTypeEnum.MRF2)注解可以在方法和类上使用,注解中的value为定义数据源名称,会根据所选择的数据源名称查询数据库
@Mapper
public interface TestMapper {
/**
* 根据id查询
* @param id
* @return
*/
@SpecifyDataSource(value = DataSourceTypeEnum.MRF2)
Test getTest(Integer id);
}
二、 异常问题
1.循环依赖错误
Description:
The dependencies of some of the beans in the application context form a cycle:
bookInfoController (field private com.croot.portal.service.PortalBookInfoService com.croot.portal.controller.BookInfoController.portalBookInfoService)
↓
portalBookInfoService
↓
commonService
↓
commonMapper defined in file [D:\workspace\masterportal\rims\global-module\target\classes\com\croot\global\dao\CommonMapper.class]
↓
sqlSessionFactory defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]
┌─────┐
| dynamicDataSource defined in class path resource [com/croot/portal/config/DataSourceConfig.class]
↑ ↓
| datasource1 defined in class path resource [com/croot/portal/config/DataSourceConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
解决方法一
在配置类添加懒加载注解@Lazy
@Configuration
public class DynamicDataSourceConfig {
@Lazy
@Bean(name = "mysql-mrf")
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource dataSource1() {
return DruidDataSourceBuilder.create().build();
}
@Lazy
@Bean(name = "mysql-mrf-two")
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource dataSource2() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>(5);
targetDataSources.put(DataSourceTypeEnum.MRF1.getName(), dataSource1());
targetDataSources.put(DataSourceTypeEnum.MRF2.getName(), dataSource2());
return new DynamicDataSource(dataSource1(), targetDataSources);
}
}
解决方法二
修改yml配置文件
spring:
datasource:
initialize: false # 默认为true
解决方法三
在启动类给@SpringBootApplication注解添加exclude属性
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MrfApplication {
public static void main(String[] args) {
SpringApplication.run(MrfApplication.class, args);
System.out.println("项目启动成功,欢迎回来!");
}
}