公司某项目做大屏展示,但数据来源自7个不同的数据库,需要涉及跨库查询,要求。
本项目采用SpringBoot+MybatisPlus做服务端提供RESTful接口,前后端分离开发,总结一下项目中实现的动态数据源切换的实现方式。
首先在application文件中配置数据源
spring: aop: proxy-target-class: true auto: true datasource: druid: # 一期数据源 db1: url: jdbc:oracle:thin:@172.16.3.131:1521:orcl username: ZHJGDSJ password: ZHJGDSJ driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 诚信监管 db2: url: jdbc:oracle:thin:@172.17.2.119:1521:orcl username: cxjg_test1 password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 协同监管 db3: url: jdbc:oracle:thin:@172.17.2.119:1521:orcl username: xtjg_test password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 公示系统 db4: url: jdbc:oracle:thin:@172.17.2.119:1521:orcl username: GSGS password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 省局-浪潮库 db5: url: jdbc:oracle:thin:@172.16.1.26:1521:orcl username: GSYW password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 市局-浪潮库 db6: url: jdbc:oracle:thin:@172.16.1.65:1521:orcl username: GSYWSJ password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20 # 二期 正元 db7: url: jdbc:oracle:thin:@172.17.2.119:1521:orcl username: ZHJGDSJ password: 123456 driver-class-name: oracle.jdbc.driver.OracleDriver initialSize: 5 minIdle: 5 maxActive: 20
创建一个数据源枚举类
package com.rexen.di.management.common.type; /** * @Author Jimmy * @date 2019/2/7. */ public enum DBTypeEnum { db1("db1"), db2("db2"), db3("db3"), db4("db4"), db5("db5"), db6("db6"), db7("db7"); private String value; DBTypeEnum(String value) { this.value = value; } public String getValue() { return value; } }
创建一个上下文来缓存当前数据源
package com.rexen.di.management.boot.config; import com.rexen.di.management.common.type.DBTypeEnum; /** * @Author Jimmy * @date 2019/2/7. */ public class DbContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal<>(); /** * 设置数据源 * @param dbTypeEnum */ public static void setDbType(DBTypeEnum dbTypeEnum) { contextHolder.set(dbTypeEnum.getValue()); } /** * 取得当前数据源 * @return */ public static String getDbType() { return (String) contextHolder.get(); } /** * 清除上下文数据 */ public static void clearDbType() { contextHolder.remove(); } }
创建AbstractRoutingDataSource 的实现类实现切换
package com.rexen.di.management.boot.config; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @Author Jimmy * @date 2019/2/7. */ public class DynamicDataSource extends AbstractRoutingDataSource { /** * 取得当前使用哪个数据源 * @return */ @Override protected Object determineCurrentLookupKey() { return DbContextHolder.getDbType(); } }
通过AOP方式实现拦截和切换,根据Mapper路径来决定切换到哪个数据源
package com.rexen.di.management.boot.advice; import com.rexen.di.management.boot.config.DbContextHolder; import com.rexen.di.management.common.annotation.DataSourceSwitch; import com.rexen.di.management.common.type.DBTypeEnum; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; 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.util.Objects; /** * @Author Jimmy * @date 2019/2/7. */ @Component @Aspect @Order(-100) public class DataSourceSwitchAspect { Logger log = Logger.getLogger(DataSourceSwitchAspect.class); @Pointcut("execution(* com.rexen.di.management.mapper..*.*(..))") private void db1Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom2.mapper..*.*(..))") private void db2Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom3.mapper..*.*(..))") private void db3Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom4.mapper..*.*(..))") private void db4Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom5.mapper..*.*(..))") private void db5Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom6.mapper..*.*(..))") private void db6Aspect() { } @Pointcut("execution(* com.rexen.di.management.custom7.mapper..*.*(..))") private void db7Aspect() { } @Before( "db1Aspect()" ) public void db1(JoinPoint joinPoint) { log.info("切换到db1 数据源..."); setDataSource(joinPoint, DBTypeEnum.db1); } @Before("db2Aspect()" ) public void db2 (JoinPoint joinPoint) { log.info("切换到db2 数据源..."); setDataSource(joinPoint,DBTypeEnum.db2); } @Before("db3Aspect()" ) public void db3 (JoinPoint joinPoint) { log.info("切换到db3 数据源..."); setDataSource(joinPoint,DBTypeEnum.db3); } @Before("db4Aspect()" ) public void db4 (JoinPoint joinPoint) { log.info("切换到db4 数据源..."); setDataSource(joinPoint,DBTypeEnum.db4); } @Before("db5Aspect()" ) public void db5 (JoinPoint joinPoint) { log.info("切换到db5 数据源..."); setDataSource(joinPoint,DBTypeEnum.db5); } @Before("db6Aspect()" ) public void db6 (JoinPoint joinPoint) { log.info("切换到db6 数据源..."); setDataSource(joinPoint,DBTypeEnum.db6); } @Before("db7Aspect()" ) public void db7 (JoinPoint joinPoint) { log.info("切换到db7 数据源..."); setDataSource(joinPoint,DBTypeEnum.db7); } /** * 添加注解方式,如果有注解优先注解,没有则按传过来的数据源配置 * @param joinPoint * @param dbTypeEnum */ private void setDataSource(JoinPoint joinPoint, DBTypeEnum dbTypeEnum) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); DataSourceSwitch dataSourceSwitch = methodSignature.getMethod().getAnnotation(DataSourceSwitch.class); if (Objects.isNull(dataSourceSwitch) || Objects.isNull(dataSourceSwitch.value())) { DbContextHolder.setDbType(dbTypeEnum); }else{ log.info("根据注解来切换数据源,注解值为:"+dataSourceSwitch.value()); switch (dataSourceSwitch.value().getValue()) { case "db1": DbContextHolder.setDbType(DBTypeEnum.db1); break; case "db2": DbContextHolder.setDbType(DBTypeEnum.db2); break; case "db3": DbContextHolder.setDbType(DBTypeEnum.db3); break; case "db4": DbContextHolder.setDbType(DBTypeEnum.db4); break; case "db5": DbContextHolder.setDbType(DBTypeEnum.db5); break; case "db6": DbContextHolder.setDbType(DBTypeEnum.db6); break; case "db7": DbContextHolder.setDbType(DBTypeEnum.db7); break; default: DbContextHolder.setDbType(dbTypeEnum); } } } }
创建MybatisPlus的配置文件
package com.rexen.di.management.boot.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.baomidou.mybatisplus.MybatisConfiguration; import com.baomidou.mybatisplus.entity.GlobalConfiguration; import com.baomidou.mybatisplus.mapper.LogicSqlInjector; import com.baomidou.mybatisplus.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.plugins.PerformanceInterceptor; import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean; import com.rexen.di.management.common.type.DBTypeEnum; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.JdbcType; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @Author Jimmy * @date 2019/2/6. */ @Configuration @MapperScan({"com.rexen.di.management.mapper","com.rexen.di.management.custom2.mapper", "com.rexen.di.management.custom3.mapper","com.rexen.di.management.custom4.mapper", "com.rexen.di.management.custom5.mapper","com.rexen.di.management.custom6.mapper","com.rexen.di.management.custom7.mapper"}) public class MybatisPlusConfig { /** * mybatis-plus分页插件
* 文档:http://mp.baomidou.com
*/ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); //paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持 return paginationInterceptor; } /** * mybatis-plus SQL执行效率插件【生产环境可以关闭】 */ @Bean public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); } @Bean(name = "db1") @ConfigurationProperties(prefix = "spring.datasource.druid.db1" ) public DataSource db1 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db2") @ConfigurationProperties(prefix = "spring.datasource.druid.db2" ) public DataSource db2 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db3") @ConfigurationProperties(prefix = "spring.datasource.druid.db3" ) public DataSource db3 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db4") @ConfigurationProperties(prefix = "spring.datasource.druid.db4" ) public DataSource db4 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db5") @ConfigurationProperties(prefix = "spring.datasource.druid.db5" ) public DataSource db5 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db6") @ConfigurationProperties(prefix = "spring.datasource.druid.db6" ) public DataSource db6 () { return DruidDataSourceBuilder.create().build(); } @Bean(name = "db7") @ConfigurationProperties(prefix = "spring.datasource.druid.db7" ) public DataSource db7 () { return DruidDataSourceBuilder.create().build(); } /** * 动态数据源配置 * @return */ @Bean @Primary public DataSource multipleDataSource (@Qualifier("db1") DataSource db1, @Qualifier("db2") DataSource db2,@Qualifier("db3") DataSource db3,@Qualifier("db4") DataSource db4, @Qualifier("db5") DataSource db5,@Qualifier("db6") DataSource db6,@Qualifier("db7") DataSource db7) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map< Object, Object > targetDataSources = new HashMap<>(); targetDataSources.put(DBTypeEnum.db1.getValue(), db1 ); targetDataSources.put(DBTypeEnum.db2.getValue(), db2); targetDataSources.put(DBTypeEnum.db3.getValue(), db3); targetDataSources.put(DBTypeEnum.db4.getValue(), db4); targetDataSources.put(DBTypeEnum.db5.getValue(), db5); targetDataSources.put(DBTypeEnum.db6.getValue(), db6); targetDataSources.put(DBTypeEnum.db7.getValue(), db7); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(db1); return dynamicDataSource; } @Bean("sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(multipleDataSource(db1(),db2(),db3(),db4(),db5(),db6(),db7())); sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/**/*Mapper.xml")); MybatisConfiguration configuration = new MybatisConfiguration(); //configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); configuration.setJdbcTypeForNull(JdbcType.NULL); configuration.setMapUnderscoreToCamelCase(true); configuration.setCacheEnabled(false); sqlSessionFactory.setConfiguration(configuration); sqlSessionFactory.setPlugins(new Interceptor[]{ //PerformanceInterceptor(),OptimisticLockerInterceptor() paginationInterceptor() }); sqlSessionFactory.setGlobalConfig(globalConfiguration()); return sqlSessionFactory.getObject(); } @Bean public GlobalConfiguration globalConfiguration() { GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector()); conf.setLogicDeleteValue("-1"); conf.setLogicNotDeleteValue("1"); conf.setIdType(0); conf.setMetaObjectHandler(new MyMetaObjectHandler()); conf.setDbColumnUnderline(true); conf.setRefresh(true); return conf; } }