注意:笔者使用的是 Mybatis ,MyBatisPlus自带的有多数据源
先看下配置类一个是本地的IP一个是我的服务器上docker容器中的mysql数据库。
server.port=8080
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis.mapper-locations=classpath:mapper/*.xml
spring.datasource.aa.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.aa.username=root
spring.datasource.aa.password=123
spring.datasource.aa.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false
spring.datasource.bb.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.bb.username=root
spring.datasource.bb.password=123
spring.datasource.bb.url=jdbc:mysql://39.1xx.1xx.1xx:3306/test?serverTimezone=UTC&useSSL=false
然后最主要的就是先编写一个DynamicDatasourceConfig类去继承,AbstractRoutingDataSource这个抽象类。至于为什么继承这个类后续会为大家讲解。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceChange.getDataSourceKey();
}
}
然后就是这个DynamicDataSourceChange的切换类,这个类负责切换数据源。
import java.util.ArrayList;
import java.util.List;
public class DynamicDataSourceChange {
/**
* 这个ThreadLocal 是用来储存当前线程中的数据源的key
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return "aa";
}
};
/**
* 切换数据源
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取数据源
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
这个类中的ThreadLocal用来存储当前进程中的数据源的key。上面的额initValue 是我自己设置的默认数据源的key。然后就是数据源的注册,这里也是很关键的一步。
package com.llq.datasource.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
@Configuration
public class DataSourceRegister {
private static final Logger logger = LoggerFactory.getLogger(DataSourceRegister.class);
@Bean(name = "aa")
@Primary
public static DataSource registerDataSource() throws IOException {
InputStream asStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties");
Properties properties = new Properties();
properties.load(asStream);
String username = properties.getProperty("spring.datasource.aa.username");
String password = properties.getProperty("spring.datasource.aa.password");
String url = properties.getProperty("spring.datasource.aa.url");
String driverClass = properties.getProperty("spring.datasource.aa.driver-class-name");
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Bean(name = "bb")
public static DataSource registerDataSource2() throws IOException {
InputStream asStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties");
Properties properties = new Properties();
properties.load(asStream);
String username = properties.getProperty("spring.datasource.bb.username");
String password = properties.getProperty("spring.datasource.bb.password");
String url = properties.getProperty("spring.datasource.bb.url");
String driverClass = properties.getProperty("spring.datasource.bb.driver-class-name");
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Bean("dynamicDataSource")
public DataSource registerAllDataSource() throws IOException {
DataSource dataSource1 = registerDataSource();
DataSource dataSource2 = registerDataSource2();
DynamicDataSource dynamicDataSource = new DynamicDataSource();
HashMap<Object, Object> map = new HashMap<>();
map.put("aa",dataSource1);
map.put("bb",dataSource2);
//这里敲一下黑板,这里必须要指定默认的数据源不然,在注册时候会出现发现两个数据源的异常问题,所以需要去设置默认数据源,也就是setDefalutTargetDataSource(),这个方法是哪儿来的呢?这个方法是因为DynamicDataSource继承了AbstractRoutingDataSource这个类。
dynamicDataSource.setDefaultTargetDataSource(dataSource1);
//这里是将所有的数据源放入
dynamicDataSource.setTargetDataSources(map);
logger.info("数据源注册成功,一共注册{}个数据源",map.size());
return dynamicDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception{
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(registerAllDataSource());
sessionFactory.setTypeAliasesPackage("com.llq.pojo");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return sessionFactory;
}
}
然后这样就好了,其余的就是自己写代码测试了。还有就是这里再强调一下,在手动切换数据源的时候你需要去先去清除一下数据源,然后再进行设置。
@RequestMapping("getUsersAA")
public String shardingCrudAA(){
DynamicDataSourceChange.clearDataSourceKey();
DynamicDataSourceChange.setDataSourceKey("aa");
List<User> allTables = userService.getAllTables();
return allTables.toString();
}
@RequestMapping("getUsersBB")
public String shardingCrudBB(){
DynamicDataSourceChange.clearDataSourceKey();
DynamicDataSourceChange.setDataSourceKey("bb");
List<User> allTables = userService.getAllTablesbb();
return allTables.toString();
}
动态数据源的话其实和手动的大差不差,只不过动态的是交给了AOP根据切入点的时机去实现切换数据源。
上面的代码可以不用动,先去创建一个枚举类来配置需要用到的数据源的key以及自定义注解。
public enum DataSourceKey {
DB_MASTER,
DB_SLAVE1,
DB_SLAVE2,
DB_OTHER
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
DataSourceKey dataSourceKey() default DataSourceKey.DB_MASTER;
}
然后就是AOP切面实现类
import com.apedad.example.annotation.TargetDataSource;
import org.apache.log4j.Logger;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
private static final Logger LOG = Logger.getLogger(DynamicDataSourceAspect.class);
@Pointcut("execution(* com.apedad.example.service.*.list*(..))")
public void pointCut() {
}
/**
* 执行方法前更换数据源
*
* @param joinPoint 切点
* @param targetDataSource 动态数据源
*/
@Before("@annotation(targetDataSource)")
public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) {
DataSourceKey dataSourceKey = targetDataSource.dataSourceKey();
if (dataSourceKey == DataSourceKey.DB_OTHER) {
LOG.info(String.format("设置数据源为 %s", DataSourceKey.DB_OTHER));
DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_OTHER);
} else {
LOG.info(String.format("使用默认数据源 %s", DataSourceKey.DB_MASTER));
DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_MASTER);
}
}
/**
* 执行方法后清除数据源设置
*
* @param joinPoint 切点
* @param targetDataSource 动态数据源
*/
@After("@annotation(targetDataSource)")
public void doAfter(JoinPoint joinPoint, TargetDataSource targetDataSource) {
LOG.info(String.format("当前数据源 %s 执行清理方法", targetDataSource.dataSourceKey()));
DynamicDataSourceChange.clearDataSourceKey();
}
@Before(value = "pointCut()")
public void doBeforeWithSlave(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取当前切点方法对象
Method method = methodSignature.getMethod();
if (method.getDeclaringClass().isInterface()) {//判断是否为借口方法
try {
//获取实际类型的方法对象
method = joinPoint.getTarget().getClass()
.getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
LOG.error("方法不存在!", e);
}
}
if (null == method.getAnnotation(TargetDataSource.class)) {
DynamicDataSourceChange.setDataSourceKey(DataSourceKey.DB_MASTER);
}
}
}