前言
这里讲解两种版本:第一种基于spring,第二种基于spring boot注解。
一、基于spring实现双数据源动态切换
1.引入依赖
<!-- aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
2.代码明细
(1)配置双数据源
import com.alibaba.druid.pool.DruidDataSource;
import com.cmft.set.dbpwd.annotation.EnableDbPasswordResolver;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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;
import java.util.Map;
@Configuration
@EnableDbPasswordResolver
public class Dbconfig {
@Value("${master.jdbc_url}")
private String masterJdbcUrl;
@Value("${master.jdbc_user}")
private String masterJdbcUser;
@Value("${master.jdbc_password}")
private String masterJdbcpwd;
@Value("${slave.jdbc_url}")
private String slaveJdbcUrl;
@Value("${slave.jdbc_user}")
private String slaveJdbcUser;
@Value("${slave.jdbc_password}")
private String slaveJdbcpwd;
@Bean("masterDs")
@Primary
public DataSource masterDataSource() {
//从url自动识别驱动类
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(masterJdbcUrl);
dataSource.setUsername(masterJdbcUser);
dataSource.setPassword(masterJdbcpwd);
dataSource.setMaxActive(20);
dataSource.setInitialSize(1);
dataSource.setMaxWait(60000);
dataSource.setMinIdle(1);
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(30000);
dataSource.setValidationQuery("select 1");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(true);
dataSource.setTestOnReturn(true);
return dataSource;
}
@Bean("masterTransactionManager")
public PlatformTransactionManager masterTransactionManager(@Qualifier("masterDs") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean("slaveDs")
public DataSource slaveDataSource() {
//从url自动识别驱动类
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(slaveJdbcUrl);
dataSource.setUsername(slaveJdbcUser);
dataSource.setPassword(slaveJdbcpwd);
dataSource.setMaxActive(20);
dataSource.setInitialSize(1);
dataSource.setMaxWait(60000);
dataSource.setMinIdle(1);
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(30000);
dataSource.setValidationQuery("select 1");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(true);
dataSource.setTestOnReturn(true);
return dataSource;
}
@Bean("slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(@Qualifier("slaveDs") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Bean(name = "dynamicDs")
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap<>(5);
dsMap.put("masterDs", masterDataSource());
dsMap.put("slaveDs", slaveDataSource());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
}
(2)配置mapper文件
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
public class MapperConfig {
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dynamicDs")DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//dto所在package
bean.setTypeAliasesPackage("com.***.***");
// 添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try{
bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return bean.getObject();
} catch (Exception e) {
throw new IllegalArgumentException("org.mybatis.spring.SqlSessionFactoryBean init error ", e);
}
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//配置多个的时候,最后一个需要以分号结尾,一个的时候是不需要分号的
mapperScannerConfigurer.setBasePackage("com.***.***.mapper");
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
Properties properties = new Properties();
//properties.setProperty("mappers", "tk.mybatis.mapper.common.Mapper");
properties.setProperty("mappers", "com.***.***.basic.config.db.CommonMapper");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}
}
(3)加入自定义注解:用于标记需要切换的方法
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER
})
@Documented
public @interface DS {
String value() default "masterDs";
}
(4)动态数据源类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
LOGGER.info("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
(5)装双数据源的静态类:用于获取数据源、设置数据源、清除数据源
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSourceContextHolder {
public static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "masterDs";
/**
* 线性安全
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
LOGGER.debug("切换到{}数据源", dbType);
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get() == null ? DEFAULT_DS : contextHolder.get() );
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
(6)最后,自定义切面类:用于方法拦截前后,进行相关设置
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;
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
二、基于spring boot注解实现双数据源动态切换
1.引入依赖
<!-- 添加JDBC启动器、引入数据库的包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
2.代码详情
(1)配置双数据源
package com.example.demo.base.config.db;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfig {
@Bean("primaryDS")// 将这个对象放入容器中
@ConfigurationProperties(prefix = "spring.datasource.primary") // prefix表示参数的前缀
public DataSource primaryDS(){
return DataSourceBuilder.create().build();
}
@Bean("secondaryDS")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDS(){
return DataSourceBuilder.create().build();
}
@Bean("dynamicDataSource")
@Primary // 表示这是默认数据源
public DynamicDataSource dataSource(@Qualifier("primaryDS")DataSource primaryDS, @Qualifier("secondaryDS") DataSource secondaryDS) {
Map<Object, Object> targetDataSources = new HashMap<>(5);
targetDataSources.put(DataSourceType.DataBaseType.primaryDS, primaryDS);
targetDataSources.put(DataSourceType.DataBaseType.secondaryDS, secondaryDS);
return new DynamicDataSource(primaryDS, targetDataSources);
}
}
(2)配置mapper文件,此处spring boot 注解 已经帮忙自动配置
(3)加入自定义注解:用于标记需要切换的方法
package com.example.demo.base.config.db;
import java.lang.annotation.*;
/**
* -------------------------------------
* 取值(ElementType)有:
*
* 1.CONSTRUCTOR:用于描述构造器
* 2.FIELD:用于描述域
* 3.LOCAL_VARIABLE:用于描述局部变量
* 4.METHOD:用于描述方法
* 5.PACKAGE:用于描述包
* 6.PARAMETER:用于描述参数
* 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
*/
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default "primaryDS";
}
(4)动态数据源类
package com.example.demo.base.config.db;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.info("-------数据源为-------[{}]",DataSourceType.getDataBaseType());
return DataSourceType.getDataBaseType();
}
/**
* 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
*
* @param defaultTargetDataSource 默认数据源
* @param targetDataSources 目标数据源
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
}
(5)装双数据源的静态类:用于获取数据源、设置数据源、清除数据源
public class DataSourceType {
// 内部枚举类,用于选择特定的数据源类型
public enum DataBaseType{
primaryDS,secondaryDS
}
// 使用ThreadLocal能保证线程隔离,起到线程安全的作用
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType){
if (null == dataBaseType) {
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 获取数据源类型
public static DataBaseType getDataBaseType(){
return TYPE.get() == null ? DataBaseType.primaryDS : TYPE.get();
}
// 清空数据类型
public static void clearDataBaseType() {
TYPE.remove();
}
}
(6)最后,自定义切面类:用于方法拦截前后,进行相关设置
package com.example.demo.base.config.interceptor;
import com.example.demo.base.config.db.DS;
import com.example.demo.base.config.db.DataSourceType;
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.springframework.stereotype.Component;
@Component
@Aspect
public class DynamicDataSourceAspect {
@Before("@annotation(dataSource)") // 拦截自定义的数据源注解
public void changeDataSource(JoinPoint joinPoint, DS dataSource){
String value = dataSource.value();
if (value.equals("primaryDS")) {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.primaryDS);
}else if (value.equals("secondaryDS")) {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.secondaryDS);
}else {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.primaryDS);
}
}
@After("@annotation(dataSource)") // 清除数据源配置
public void clearDataSource(JoinPoint joinPoint, DS dataSource){
DataSourceType.clearDataBaseType();
}
}
总结
第一种方法适合版本较低的应用;
第二种基于注解的代码简洁,适合版本比较新的应用。