这几天一直在研究SpringBoot配置多数据源的方法,网上方法和轮子都挺多的,我没选择使用网上的轮子,自己手把手网上找资源,配置的,今天终于实现了配置多数据源,先贴出一篇博客地址,本来是有两篇的,这两篇博客写的都很nice,在这里先感谢下两篇博客的博主,还有一篇找不到了,就只能先贴出一篇了。
先发一下我yml文件的配置
spring:
datasource:
master: #这个master和other是自定义的,名称随便取即可
#数据库连接地址,因为我本地的MySQL版本是8.0所以需要配置下时区为serverTimezone=UTC,其他版本的MySQL需不需要配置我不太清楚。
jdbc-url: jdbc:mysql://localhost:3306/masterdatabase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
#用户名
username: sakura
#密码
password: sakura
#连接驱动
driver-class-name: com.mysql.cj.jdbc.Driver
#数据库类型
database: mysql
other:
jdbc-url: jdbc:mysql://localhost:3306/otherdatabase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: sakura
password: sakura
driver-class-name: com.mysql.cj.jdbc.Driver
database: mysql
jpa:
#指定数据类型
database: mysql
第一种方式:注入SqlSessionTemplate
这一种方式局限性比较大,但是配置较少,使用起来也只能配置mapper.xml使用,配置如下。
- 创建一个DataMasterSourceConfig类,该类为主数据的配置类,除了创建该类之外还需要创建一个DataOtherSourceConfig类,该类为从数据源配置类,如果还有第三个第四个数据源还需要创建其他的从数据类,代码如下
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
- @author 李七夜
- @version 1.0
- Created by 李七夜 on 2020/6/12 17:35
*/
//@Configuration:用于定义配置类,在Spring启动的时候,这个会被注入到Spring中,会替换掉原本的DataSource配置,优先从我们配置的这个类里取
//@MapperScan 扫描Dao层所在的包,并且将我们配置好的sqlSessionFactoryRef注入进去
@Configuration
@MapperScan(basePackages = "com.sakura.tm.dao1.tm", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class DataMasterSourceConfig {
/**
*@Primary,优先取master数据源配置
*@Bean将这个方法声明为一个bean,在Spring启动后可以注入这个bean
*@ConfigurationProperties,获取yml文件的配置,前缀就选我们自定义配置的master
*/
@Primary
@Bean(name = "dataMasterSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource getDataMasterSourceConfig() {
return DataSourceBuilder.create().build();
}
/**
*自定义的sqlSessionFactory
*/
@Primary
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("dataMasterSource") DataSource datasource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
// 设置mybatis的xml所在位置
new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/*.xml"));
return bean.getObject();
}
/**
*自定义的sqlSessionTemplate
*/
@Primary
@Bean("masterSqlSessionTemplate")
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sessionfactory) {
return new SqlSessionTemplate(sessionfactory);
}
}
其他从数据源配置跟数据源配置一样,只是不需要加入@Primary注解,这样会导致Spring启动异常,不知道优先注入那个数据源。
- 到这一步其实多数据源配置已经好了,接下来可以写个测试类测试一下,先注入SqlSessionTemplate,然后使用@Qualifier注解注入你想要访问的数据源
第二种方式:使用AOP
使用AOP灵活性较高,但是配置也比较繁琐,不过局限性不大,无论是使用注解、通用mapper、mapper.xml都可以使用。
- 先编写一个DynamicDataSource类,这个类要继承AbstractRoutingDataSource类,这个AbstractRoutingDataSource类型是实现aop多数据源的关键,这里我直接引用我看的那篇博主的一段话描述
spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。
```java
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/15 11:22
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
//数据源路由,此方用于产生要选取的数据源逻辑名称
@Override
protected Object determineCurrentLookupKey() {
//从线程共享中获取数据源名称
return DynamicDataSourceHolder.getDataSource();
}
}
- 然后在编写一个DynamicDataSourceHolder类,在注入DataSource过程中这一步骤处于单线程状态,所以需要加入一个锁来保证线程安全。
/**
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/16 10:39
*/
public class DynamicDataSourceHolder {
//本地线程共享对象
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void putDataSouce(String name){
THREAD_LOCAL.set(name);
}
public static String getDataSource(){
return THREAD_LOCAL.get();
}
public static void removeDataSource(){
THREAD_LOCAL.remove();
}
}
- 在创建一个DBProperties类,这个类用于存放多数据源的配置信息,在Spring启动完成后会从yml文件中读取,跟第一种操作类似,一个HikariDataSource代表一个数据源配置
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/16 10:46
*/
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DBProperties {
private HikariDataSource master;
private HikariDataSource other;
}
- 接下来就是创建DataSourceConfig配置类,这个类会注入我们想要的DataSource
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.Map;
/**
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/16 10:48
*/
@Slf4j
@Configuration
@EnableScheduling
public class DataSourceConfig {
@Autowired
private DBProperties dbProperties;
/**
* 设置动态数据源,通过@Primary 来确定主DataSource
*
*/
@Bean(name = "dataSource")
public DynamicDataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//1.设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(dbProperties.getMaster());
//2.配置多数据源
Map<Object, Object> map = Maps.newHashMap();
map.put("master", dbProperties.getMaster());
map.put("other", dbProperties.getOther());
//3.存放数据源集
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}
- 接下来还需要个自定义注解标签,用于动态的选择数据源
```java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 目标数据源注解,注解在方法上指定数据源的名称
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/16 10:50
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
//此处接收数据源名称
String value();
}
- 最后就是编写AOP层了,在访问Dao层的根据方法注解判断注入什么类型的DataSource,使用该类型注入datasource可以笼罩整个Dao层,根据注入的@DataSource选择数据源
import com.sakura.tm.config.dao.DataSource;
import com.sakura.tm.config.dao.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author 李七夜
* @version 1.0
* Created by 李七夜 on 2020/6/16 10:51
*/
@Component
@Aspect
@Slf4j
public class DataSourceAspect {
//切入点在dao层的方法上,配置aop的切入点
@Pointcut("execution( * com.sakura.tm.dao1..*(..))")
public void dataSourcePointCut1() {
}
@Before("dataSourcePointCut1() ")
public void before(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
String method = joinPoint.getSignature().getName();
Class<?>[] clazz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
try {
Method m = clazz[0].getMethod(method, parameterTypes);
//如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource annotation = m.getAnnotation(DataSource.class);
String dataSourceName = annotation.value();
DynamicDataSourceHolder.putDataSouce(dataSourceName);
log.debug("-----current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal-----");
} else {
log.debug("switch datasource fail, use default");
}
} catch (NoSuchMethodException e) {
log.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
}
}
//执行完切面后,清空线程共享中的数据源名称
@After("dataSourcePointCut1()")
public void after(JoinPoint joinPoint){
DynamicDataSourceHolder.removeDataSource();
}
}
mapper方法