SpringBoot项目配置多数据源
在工作中你一定遇到过这么一个问题,一个功能涉及到多张表的CRUD,而这些表又来源于不同的数据库,关键是可能数据库的类型也可能不同,可能是mysql,也可能是oracle、postgre这样的数据源。而传统使用mybatis配置数据源默认只支持配置一个数据库,这显然不能满足我们的需求,所以我们现在需要了解一下怎么配置多数据源。
配置多数据源的方式其实有很多,我这里先暂时说一下我常使用的两种方式,第一种就是不使用mybatis这类orm框架,而是使用原生的JDBCTemplate来配置多数据源。第二种就是使用aop的方式来修改mybatis默认配置数据源的,自己自定义数据源,达到动态配置数据源的目的。
一、JDBCTemplate 配置多数据源
第一种使用jdbctemplate具体步骤如下:
-
导入maven包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
在application.yml里面配置数据源四大金刚
spring: datasource: druid: pethome: jdbc-url: jdbc:mysql:///pethome?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: xxx driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource book: jdbc-url: jdbc:mysql:///book_manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
-
配置了四大金刚过后,那么就可以直接引用四大金刚,配置数据源了。首先就是需要先创建一个配置类,打上@Configuration注解,然后写入以下代码。这里@Primary表示使用pethome为默认的数据源,如果不加此注解,会报有两个数据源异常。
@Bean(name = "pethome") @Primary//主数据源 @ConfigurationProperties(prefix = "spring.datasource.druid.pethome") public DataSource pethome() { return DataSourceBuilder.create().build(); } @Bean(name = "book") @ConfigurationProperties(prefix = "spring.datasource.druid.book") public DataSource book() { return DataSourceBuilder.create().build(); }
-
配置完过后就可以直接使用@Resource(name=“名”)注入你想使用的数据源啦。
@Resource(name = "pethome") private DataSource pethome; @Resource(name = "book") private DataSource book; @RequestMapping(value="/test1",method= RequestMethod.POST) @ApiOperation(value = "测试jdbc", notes = "测试") public List<Map<String,Object>> test(){ JdbcTemplate jdbcTemplate = new JdbcTemplate(pethome); List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * FROM 表"); System.out.println(maps); return maps; } @RequestMapping(value="/test2",method= RequestMethod.POST) @ApiOperation(value = "测试jdbc2", notes = "测试") public List<Map<String,Object>> test2(){ JdbcTemplate jdbcTemplate = new JdbcTemplate(book); List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * FROM 表"); System.out.println(maps); return maps; }
二、SpringBoot+Mybatis+AOP配置多数据源
第二种的主要配置如下:
-
导入maven包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 增加postgresql的支持 --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <!-- 增加oracle的支持 --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.1.8</version> </dependency>
-
在application.yml里面配置数据源四大金刚
spring: datasource: druid: pethome: jdbc-url: jdbc:mysql:///pethome?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: xxx driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource book: jdbc-url: jdbc:mysql:///book_manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
-
创建Datasource注解
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { DataSourceEnum value() default DataSourceEnum.DB1; }
-
创建DataSourceEnum常量
public enum DataSourceEnum { DB1("book"),DB2("pethome"); private String value; DataSourceEnum(String value){this.value=value;} public String getValue() { return value; } }
-
创建DataSourceContextHolder
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>(); /** * 设置数据源 * @param db */ public static void setDataSource(String db){ contextHolder.set(db); } /** * 取得当前数据源 * @return */ public static String getDataSource(){ return contextHolder.get(); } /** * 清除上下文数据 */ public static void clear(){ contextHolder.remove(); } }
-
设置MultipleDataSource,里面的determineCurrentLookupKey是决定当前使用数据源,然后返回当前数据源的key。
public class MultipleDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
-
为注解增加aop配置
@Component @Aspect @Order(-1) public class DataSourceAspect { @Pointcut("@within(cn.antu.dynamic.annotation.DataSource) || @annotation(cn.antu.dynamic.annotation.DataSource)") public void pointCut(){ } @Before("pointCut() && @annotation(dataSource)") public void doBefore(DataSource dataSource){ DataSourceContextHolder.setDataSource(dataSource.value().getValue()); } @After("pointCut()") public void doAfter(){ DataSourceContextHolder.clear(); } }
-
修改mybatis的默认配置
@Configuration // 注解到spring容器中 @MapperScan(basePackages = "cn.antu.dynamic.mapper.*") public class MybatisDataSource { /* * 分页插件,自动识别数据库类型 多租户,请参考官网【插件扩展】 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 开启 PageHelper 的支持 paginationInterceptor.setLocalPage(true); return paginationInterceptor; } /** * SQL执行效率插件 */ // @Bean // @Profile({ "dev", "qa" }) // 设置 dev test 环境开启 // public PerformanceInterceptor performanceInterceptor() { // PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); // performanceInterceptor.setMaxTime(1000); // performanceInterceptor.setFormat(true); // return performanceInterceptor; // } /** * 返回data1数据库的数据源 * * @return */ @Bean(name = "pethome") @Primary//主数据源 @ConfigurationProperties(prefix = "spring.datasource.druid.pethome") public DataSource pethome() { return DataSourceBuilder.create().build(); } @Bean(name = "book") @ConfigurationProperties(prefix = "spring.datasource.druid.book") public DataSource book() { return DataSourceBuilder.create().build(); } @Bean("sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(multipleDataSource(primary(), secondary())); // 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(); } /** * 动态数据源配置 * * @return */ @Bean @Primary public DataSource multipleDataSource(@Qualifier("book") DataSource db1, @Qualifier("pethome") DataSource db2) { MultipleDataSource multipleDataSource = new MultipleDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceEnum.DB1.getValue(), db1); targetDataSources.put(DataSourceEnum.DB2.getValue(), db2); // 添加数据源 multipleDataSource.setTargetDataSources(targetDataSources); // 设置默认数据源 multipleDataSource.setDefaultTargetDataSource(db1); return multipleDataSource; } }
具体流程逻辑
上面这些代码网上随处可见,我一时copy过来的,直接用就可以了,具体流程我分析了一下,如下:
当我们使用了@DataSource(“book”)注解过后,切面类的@Before方法就会把当前指定的book数据源设置到当前线程的DataSourceContextHolder里面,然后MultipleDataSource里面的determineCurrentLookupKey()方法就是指定当前数据源为@DataSource(“book”)指定的book数据源。又因为我们在MybatisDataSource里面配置了multipleDataSource.setTargetDataSources(targetDataSources);targetDataSources是一个map,key为数据源名称,value为数据源bean对象。而determineCurrentLookupKey返回是key,系统最终会通过key找到对应的数据源。