最近配置多数据源,也是bug频出,在参考了诸多文档,掉了些许头发之后,现在测试OK了,特此分享。本次采用注解的方式,通过AOP来切换不同数据源,也可以通过拦截方法来切换数据源。
!注意点:包的导入和注解的标注,避免jar冲突。
相关版本:jdk1.8,springboot 2.1.3
1》pom.xml导包
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
2》yml配置
spring: datasource: druid: db1: username: root password: 123456 driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai db2: username: root password: 123456 driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai db3: username: root password: 123456 driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db3?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
3》数据源相关设置
1、DatasourceConstants类:
/** * 数据源常量 */ public class DatasourceConstants { public static final String DATASOURCE1 = "datasource1"; public static final String DATASOURCE2 = "datasource2"; public static final String DATASOURCE3 = "datasource3"; }
2、DataSourceContextHolder类:
/** * 数据源容容器 * * */ public class DataSourceContextHolder { public static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); /** * 获取数据源名 * @return */ public static String getDataSource(){ return (String) HOLDER.get(); } /** * 设置数据源名 * @param dataSourceName */ public static void setDataSource(String dataSourceName){ HOLDER.set(dataSourceName); } /** * 移除数据源名 */ public static void removeDataSource(){ HOLDER.remove(); } }
3、DynamicDataSource类,这是很关键的一步,继承自AbstractRoutingDataSource抽象类,里面有个重要的方法:部分源码
/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */ 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; }
我们需要重写determineCurrentLookupKey()方法:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/** * 获取动态数据源 * * */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
4、自定义MyDataSource 注解:
/** * 数据源注解 * @author yangxiaoguang * @date 2020/5/14 */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface MyDataSource { String value() default ""; }
5、DynamicDataSourceAspect切面类:
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; /** * 数据源切面 需要在事务@Transactional之前运行 * * */ @Component @Aspect @Order(-1) public class DynamicDataSourceAspect { //切点表达式 @Pointcut("@annotation(com.springboot.dataSource.MyDataSource)") public void pointcut(){ } @Before("pointcut()") public void beforeMethod(JoinPoint joinPoint) throws NoSuchMethodException { //获取class对象 Class<?> clazz = joinPoint.getTarget().getClass(); //获取方法名 String methodName = joinPoint.getSignature().getName(); //获取方法参数类型 Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); //获取到Method类 Method method = clazz.getMethod(methodName, parameterTypes); //获取到注解 MyDataSource annotation = method.getAnnotation(MyDataSource.class); //获取注解上的值 String dataSourceName = annotation.value(); //设置数据源 DataSourceContextHolder.setDataSource(dataSourceName); } @After("pointcut()") public void afterSwitchDS(JoinPoint joinPoint) { DataSourceContextHolder.removeDataSource(); } }
4》MybatisPlusConfig 配置文件:
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.springboot.dataSource.DatasourceConstants; import com.springboot.dataSource.DynamicDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.JdbcType; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; 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.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @EnableTransactionManagement @Configuration @MapperScan("com.springboot.modules.*.mapper") public class MybatisPlusConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.db1") public DataSource db1() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.db2") public DataSource db2() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.db3") public DataSource db3() { return DruidDataSourceBuilder.create().build(); } /** * 动态数据源配置 * @return */ @Bean @Primary public DataSource setDynamicDataSource( DataSource db1, DataSource db2, DataSource db3) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); //设置默认的数据源,不设置会报错 dynamicDataSource.setDefaultTargetDataSource(db1); //配置多数据源,加入其他数据源到map中v Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DatasourceConstants.DATASOURCE1, db1); targetDataSources.put(DatasourceConstants.DATASOURCE2, db2); targetDataSources.put(DatasourceConstants.DATASOURCE3, db3); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(setDynamicDataSource(db1(), db2(),db3())); //数据库相关设置 MybatisConfiguration configuration = new MybatisConfiguration(); configuration.setJdbcTypeForNull(JdbcType.NULL); configuration.setMapUnderscoreToCamelCase(true); configuration.setCacheEnabled(false); sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml")); sqlSessionFactory.setConfiguration(configuration); return sqlSessionFactory.getObject(); } }
5》测试:
controller层:这里整合swagger ui
@ApiOperation(value = "查询个人学生信息") @PostMapping(value = "/findStudent") public ResponseData findStudent(@RequestBody StudentReq studentReq){ return ResponseData.okData(studentService.findStudent(studentReq)); }
service层:
@Override @MyDataSource("datasource2") public Student findStudent(StudentReq studentReq) { return studentMapper.selectOne(new QueryWrapper<>()); }
数据库:
db1:student表
db2:student表
db3:student表
测试结果:
默认数据源db1,切换数据源db2:
默认数据源db1,切换数据源db3:
以上就是多数据源的基本配置了,有啥不当可以交流交流。
参考文档: