说明
公司项目需要多数据源选择,尝试过以下方式配置静态数据源,但是项目已经使用了接口和sql一起的方式,使用以下方式需要sql写为xml分离,故使用AbstractRoutingDataSource尝试动态修改数据源
MyBatisConfig.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 
 | package com.sichuan.sichuanproject.config;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.SqlSessionTemplate;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 
 import javax.annotation.Resource;
 import javax.sql.DataSource;
 
 @Slf4j
 public class MyBatisConfig {
 
 
 
 @Configuration
 @MapperScan(basePackages = {"com.sichuan.sichuanproject.mapper.primary"},
 sqlSessionFactoryRef = "sqlSessionFactoryOne",
 sqlSessionTemplateRef = "sqlSessionTemplateOne")
 public static  class DBOne{
 @Resource
 DataSource dbOneDataSource;
 @Bean
 public SqlSessionFactory sqlSessionFactoryOne() throws Exception {
 log.info("sqlSessionFactoryOne 创建成功。");
 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
 factoryBean.setDataSource(dbOneDataSource);
 
 return factoryBean.getObject();
 }
 @Bean
 public SqlSessionTemplate sqlSessionTemplateOne() throws Exception {
 SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactoryOne());
 return template;
 }
 }
 
 
 
 @Configuration
 @MapperScan(basePackages = {"com.sichuan.sichuanproject.mapper.second"},
 sqlSessionFactoryRef = "sqlSessionFactoryTwo",
 sqlSessionTemplateRef ="sqlSessionTemplateTwo" )
 public static class DBTwo{
 @Resource
 DataSource dbTwoDataSource;
 @Bean
 public SqlSessionFactory sqlSessionFactoryTwo() throws Exception {
 log.info("sqlSessionFactoryTwo 创建成功。");
 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
 factoryBean.setDataSource(dbTwoDataSource);
 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/two/*.xml"));
 return factoryBean.getObject();
 }
 @Bean
 public SqlSessionTemplate sqlSessionTemplateTwo() throws Exception {
 SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactoryTwo());
 return template;
 }
 }
 }
 
 
 | 
思路:在service层使用mapper之前修改datasource(通过注解和aop)
1.配置数据源yum
spring工程resource目录下application.yum
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | #application.yml
 server:
 port: 8080
 
 spring:
 datasource:
 jdbc-url: jdbc:mysql://59.225.206.13:3711/sc_fxyj?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
 username: root
 password: Scfxyjpasswd741*
 driver-class-name: com.mysql.cj.jdbc.Driver
 second-datasource:
 jdbc-url: jdbc:mysql://59.225.206.13:3501/isgs-home?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
 username: rewi
 password: Schlw+Rewis@201907
 driver-class-name: com.mysql.cj.jdbc.Driver
 
 mybatis:
 configuration:
 map-underscore-to-camel-case: true
 
 
 | 
2.关闭数据源自动扫描
springboot启动文件工程名Application.java 添加
@SpringBootApplication(exclude = {         DataSourceAutoConfiguration.class })
如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | package com.sichuan.sichuanproject;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.RestTemplate;
 
 
 @SpringBootApplication(exclude = {
 DataSourceAutoConfiguration.class
 })
 @RestController
 public class SichuanprojectApplication {
 
 @Bean
 public RestTemplate restTemplate() {
 return new RestTemplate();
 }
 
 public static void main(String[] args) {
 SpringApplication.run(SichuanprojectApplication.class, args);
 }
 
 }
 
 
 | 
3.配置注解和aop拦截器
1.先配置一个enum解耦
DsEnum.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 
 | package com.sichuan.sichuanproject.config;
 
 
 
 
 
 public enum DsEnum {
 
 
 
 FIRST_DS("firstDataSource","001","first数据库"),
 SECOND_DS("secondDataSource","002","second数据库"),
 AUTO_DS("auto","003","预留字段后期自动获取数据库"),
 NONE("error","999","BASE ERROR")
 ;
 
 private String ds;
 private String baseid;
 private String message;
 
 public static DsEnum createDSBybaseid(String cid) {
 for(DsEnum val : DsEnum.values()) {
 if(val.getBaseid().equalsIgnoreCase(cid)) {
 return val;
 }
 }
 return DsEnum.NONE;
 }
 
 DsEnum(String ds, String baseid) {
 this.ds = ds;
 this.baseid = baseid;
 }
 
 DsEnum(String ds, String baseid, String message) {
 this.ds = ds;
 this.baseid = baseid;
 this.message = message;
 }
 
 public String getDs() {
 return ds;
 }
 
 public String getBaseid() {
 return baseid;
 }
 
 public String getMessage() {
 return message;
 }
 
 
 }
 
 
 | 
2.配置一个DynamicDataSource(重要):java文件只要继承    AbstractRoutingDataSource即可。spring在选择时会走AbstractRoutingDataSource代码选择一个datasource。需要config提前设置一个内部map
DynamicDataSource.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | package com.sichuan.sichuanproject.config;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 import org.springframework.lang.Nullable;
 
 
 
 
 
 
 
 
 
 
 @Slf4j
 public class DynamicDataSource extends AbstractRoutingDataSource {
 @Nullable
 @Override
 protected Object determineCurrentLookupKey(){
 log.info("数据源为:{}",DataSourceContextHolder.getDB());
 String db = DataSourceContextHolder.getDB();
 return db;
 }
 }
 
 
 | 
3.设置一个用于存放key的线程安全类
DataSourceContextHolder.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | package com.sichuan.sichuanproject.config;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 
 
 
 
 
 @Slf4j
 @Component
 public class DataSourceContextHolder {
 
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
 public static final DsEnum DEFAULT_DS = DsEnum.FIRST_DS;
 public static void setDB(DsEnum db) {
 log.info("切换到{}数据源",db.getMessage());
 contextHolder.set(db.getDs());
 }
 
 public static String getDB() {
 return contextHolder.get();
 }
 
 public static void clearDB() {
 contextHolder.remove();
 }
 }
 
 
 | 
4.配置aop拦截器,作用:所有使用DS注释的service前切换数据库
DataAspect.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 
 | package com.sichuan.sichuanproject.config;
 import com.sichuan.sichuanproject.service.DS;
 import lombok.extern.slf4j.Slf4j;
 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.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import java.lang.reflect.Method;
 
 
 
 
 
 
 @Aspect
 @Order(-1)
 @Component
 @Slf4j
 public class DataAspect {
 
 @Before("@annotation(com.sichuan.sichuanproject.service.DS)")
 public void beforeDSAnnotation(JoinPoint point){
 
 Class<?> className = point.getTarget().getClass();
 
 String methodName = point.getSignature().getName();
 
 Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
 DsEnum dsEnum = null;
 try {
 
 Method method = className.getMethod(methodName, argClass);
 
 if (method.isAnnotationPresent(DS.class)) {
 DS annotation = method.getAnnotation(DS.class);
 
 if(DsEnum.AUTO_DS == annotation.value()) {
 
 
 String baseid = "200";
 dsEnum = DsEnum.createDSBybaseid(baseid);
 }
 
 dsEnum = annotation.value();
 
 DataSourceContextHolder.setDB(dsEnum);
 }
 } catch (Exception e) {
 e.printStackTrace();
 log.error("{} 类出现了异常,异常信息为:{}",getClass().getSimpleName(),e.getMessage());
 }
 }
 
 @After("@annotation(com.sichuan.sichuanproject.service.DS)")
 public void afterDSAnnotation() {
 
 DataSourceContextHolder.clearDB();
 }
 }
 
 
 | 
5.配置注解
DS.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | package com.sichuan.sichuanproject.service;
 import com.sichuan.sichuanproject.config.DsEnum;
 
 import java.lang.annotation.*;
 
 
 
 
 
 
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
 @Target({ElementType.METHOD})
 public @interface DS {
 DsEnum value() default DsEnum.FIRST_DS;
 }
 
 
 | 
4.spring启动配置数据源
DatasourceConfig.java
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 
 | package com.sichuan.sichuanproject.config;
 import org.mapstruct.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;
 
 
 
 
 
 
 
 @Configuration
 public class DataSourceConfig {
 
 @Bean(name = "firstDataSource")
 @ConfigurationProperties(prefix = "spring.datasource")
 public DataSource firstDataSrouce(){
 return DataSourceBuilder.create().build();
 }
 
 @Bean(name="sendDataSource")
 @ConfigurationProperties(prefix = "spring.second-datasource")
 public DataSource secondDatasource(){
 return DataSourceBuilder.create().build();
 }
 
 
 
 
 
 @Primary
 @Bean(name = "dynamicDataSource")
 public DataSource dynamicDataSource(){
 DynamicDataSource dynamicDataSource = new DynamicDataSource();
 
 dynamicDataSource.setDefaultTargetDataSource(firstDataSrouce());
 
 HashMap<Object, Object> dsMap = new HashMap<>();
 dsMap.put("firstDataSource", firstDataSrouce());
 dsMap.put("secondDataSource", secondDatasource());
 dynamicDataSource.setTargetDataSources(dsMap);
 return dynamicDataSource;
 }
 }
 
 
 | 
service层调用流程
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 
 | package com.sichuan.sichuanproject.service.impl;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.sichuan.sichuanproject.config.DsEnum;
 import com.sichuan.sichuanproject.domain.WarningSignal;
 import com.sichuan.sichuanproject.dto.StaRewiDTO;
 import com.sichuan.sichuanproject.mapper.primary.WarningSignalMapper;
 import com.sichuan.sichuanproject.mapper.second.StaRewiMapper;
 import com.sichuan.sichuanproject.service.DS;
 import com.sichuan.sichuanproject.service.WarningSignalService;
 import com.sichuan.sichuanproject.vo.WarningSignalVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
 
 
 
 
 
 @Component
 public class WarningSignalServiceImpl implements WarningSignalService {
 
 @Autowired
 private WarningSignalMapper warningSignalMapper;
 
 @Autowired
 private StaRewiMapper staRewiMapper;
 
 @DS(value = DsEnum.FIRST_DS)
 @Override
 public Integer addWarningSignal(WarningSignal warningSignal) {
 Integer result = warningSignalMapper.addWarningSignal(warningSignal);
 return result;
 }
 
 
 @Override
 public PageInfo<WarningSignalVO> getWarningSignal(Integer pageNum, Integer pageSize) {
 PageHelper.startPage(pageNum, pageSize);
 List<WarningSignalVO> warningSignalVOList = warningSignalMapper.getWarningSignal();
 PageInfo pageInfo = new PageInfo(warningSignalVOList);
 
 return pageInfo;
 }
 
 @DS(value = DsEnum.SECOND_DS)
 @Override
 public List<StaRewiDTO> getStaRewi() {
 return staRewiMapper.getWarningSignal();
 }
 }
 
 
 | 
总结说明:
- Spring启动时执行@Configuration的java:由于停用了自动扫描且配置了动态数据库链接- DataSourceConfig.java所以spring连接数据库会优先使用@Primary注解的dynamicDataSource,在代码中创建不同数据库的两个datasource,且将这两个对象放入spring框架map数据结构里,map的key是DsEnum里的key。
 
- service层调用时:由于启动了注解,(么得注解就使用默认数据源)扫描到注解后执行DataAspect,将DataContextHolder里面存放的key改为现在需要数据源的key。执行完毕,执行DynamicDataSource,由于继承了框架AbstractRoutingDataSource,spring自动执行内部的resolveSpecifiedLookupKey方法,这个方法通过DataContextHolder的key取出内部map的数据源。