前提

  • SpringBoot本身是可以配置多个数据源的,但是SpringBoot的多数据做不到动态的切换,只能在代码里面通过注解或写死。

  • 一般的情况下作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程

  • Mybatis配置多数据源是使用多个DataSource 然后创建多个SessionFactory,在使用Dao层的时候通过不同的SessionFactory进行处理,这样的入侵性比较明显

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Autowired private UserMapperPrimary userMapperPrimary;
    @Autowired private UserMapperSecondary userMapperSecondary;

    // 往Primary数据源插入一条数据
    userMapperPrimary.insert("AAA", 20);

    // 从Primary数据源查询刚才插入的数据,配置正确就可以查询到
    UserPrimary userPrimary = userMapperPrimary.findByName("AAA");

    // 从Secondary数据源查询刚才插入的数据,配置正确应该是查询不到的
    UserSecondary userSecondary = userMapperSecondary.findByName("AAA");
  • 使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低,非常好的满足使用的需求。

场景

  • 使用场景

  • 单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)

  • 多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求,不过这样的处理带来了很多的不便之处,所有很多情况下我们宁愿直接使用封装的JDBC编程,或者使用Mybatis处理这样的业务场景

  • 使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,这样的编写方式比较好

    • 编写AbstractRoutingDataSource的实现类
    • HandlerDataSource就是提供给我们动态选择数据源的数据的信息
    • 我们编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以类中手动的设置当前的数据源

实现

配置依赖

  • Springboot+mybatis+jdbc+druid

    1
    2
    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
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- 引入 mybatis 场景启动器 -->
    <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 引入JDBC场景启动器 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!-- 数据库连接依赖 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- 引入 druid 场景启动器 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <!-- druid如果启用日志记录时,需要导入Log4j 依赖-->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
    </dependency>

    </dependencies>

配置文件

  • yaml文件如下

    1
    2
    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
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    spring:
    aop:
    # 决定是基于接口的还是基于类的代理被创建
    # 默认为false,表示使用JDK动态代理织入增强;当值为true时,表示使用CGLib动态代理织入增强
    # 如果目标类没有生命接口,则Spring将自动使用CGLib动态代理.
    proxy-target-class: true
    datasource:
    #readSize为从库数量
    readSize: 1
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
    aop-patterns: tk.fulsun.demo.service.*,tk.fulsun.demo.dao.*,tk.fulsun.demo.controller.*,tk.fulsun.demo.mapper.*
    master:
    url: jdbc:mysql://192.168.56.101:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    # 5.7版本是 com.mysql.jdbc.Driver
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: test
    password: 123456
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    min-evictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: true
    testOnReturn: false
    poolPreparedStatements: true
    useGlobalDataSourceStat: true
    maxPoolPreparedStatementPerConnectionSize: 20
    filters: stat,wall,log4j2
    WebStatFilter:
    enabled: true
    exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
    urlPattern: '/*'
    StatViewServlet:
    enabled: true
    urlPattern: '/druid/*'
    loginUsername: druid
    loginPassword: druid
    reset-enable: false
    allow: 127.0.0.1
    slave:
    url: jdbc:mysql://192.168.56.101:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.jdbc.Driver
    username: test
    password: 123456
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    min-evictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: true
    testOnReturn: false
    poolPreparedStatements: true
    useGlobalDataSourceStat: true
    maxPoolPreparedStatementPerConnectionSize: 20
    filters: stat,wall
    WebStatFilter:
    exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
    enabled: true
    urlPattern: '/*'
    StatViewServlet:
    enabled: true
    urlPattern: '/druid/*'
    loginUsername: druid
    loginPassword: druid
    server:
    port: 8888
    mybatis:
    # 映射实体地址
    type-aliases-package: tk.fulsun.demo.entity
    # xml配置文件地址
    mapper-locations: classpath:mapper/*.xml
    # mybatis全局配置,与configuration 不能同时存在
    # config-location: classpath:mybatis/mybatis-config.xml
    configuration:
    # 开启驼峰命名
    map-underscore-to-camel-case: true
    #当传入null的时候对应的jdbctype
    jdbc-type-for-null: null
    #用map接受查询结果时,会自动将查询结果为null的字段忽略
    #查询到0条记录时 会接收到一个所有key值都为null的map
    #只查询一个字段,而用map接收 会接收到一个为null的map
    call-setters-on-nulls: true

动态数据源选择

  • AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

  • 多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

    /**
    * @Description AbstractRoutingDataSource(每执行一次数据库,动态获取DataSource)
    * @Date 2021/6/14
    * @Created by 凉月-文
    */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getDataSourceType();
    }
    }

管理数据源

  • 设置动态选择的Datasource,这里的Set方法可以留给AOP调用,或者留给我们的具体的Dao层或者Service层中手动调用,在执行SQL语句之前。

    1
    2
    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
    import java.util.ArrayList;
    import java.util.List;

    /**
    * @Description 动态数据源上下文管理
    * @Date 2021/6/14
    * @Created by 凉月-文
    */

    public class DynamicDataSourceContextHolder {

    //存放当前线程使用的数据源类型信息
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    //存放数据源id
    public static List<String> dataSourceIds = new ArrayList<String>();

    //设置数据源:提供给AOP去设置当前的线程的数据源的信息
    public static void setDataSourceType(String dataSourceType) {
    contextHolder.set(dataSourceType);
    }

    //获取数据源: 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
    public static String getDataSourceType() {
    return contextHolder.get();
    }

    //清除数据源: 使用默认的数据源
    public static void clearDataSourceType() {
    contextHolder.remove();
    }

    //判断当前数据源是否存在
    public static boolean isContainsDataSource(String dataSourceId) {
    return dataSourceIds.contains(dataSourceId);
    }
    }

数据源的注解

  • 设置数据源的注解,在具体的类上,或者在具体的方法上,dataSource是当前数据源的一个别名用于标识我们的数据源的信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import java.lang.annotation.*;

    /**
    * @Description 多数据源注解
    * @Date 2021/6/14
    * @Created by 凉月-文
    */

    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
    String name() default "";
    }

AOP拦截类

  • 通过拦截上面的注解,在其执行之前处理, 设置当前执行SQL的数据源的信息

    1
    2
    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
    67
    68
    69
    package tk.fulsun.demo.databases.aspect;

    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import tk.fulsun.demo.databases.DynamicDataSourceContextHolder;
    import tk.fulsun.demo.databases.annotation.DataSource;

    import java.lang.reflect.Method;

    /**
    * @Description 多数据源,切面处理类
    * @Date 2021/6/14
    * @Created by 凉月-文
    */
    @Aspect
    @Component
    @Slf4j
    //@Order(1)
    public class DataSourceAspect implements Ordered {
    //@within在类上设置
    //@annotation在方法上进行设置
    @Pointcut("@within(tk.fulsun.demo.databases.annotation.DataSource)||@annotation(tk.fulsun.demo.databases.annotation.DataSource)")
    public void dataSourcePointCut() {

    }


    @Around("dataSourcePointCut()")
    public Object before(ProceedingJoinPoint point) throws Throwable {
    MethodSignature signature = (MethodSignature) point.getSignature();
    Method method = signature.getMethod();
    //获取方法上的注解
    DataSource ds = method.getAnnotation(DataSource.class);
    if (ds == null || !DynamicDataSourceContextHolder.isContainsDataSource(ds.name())) {
    //获取类上面的注解
    ds = point.getTarget().getClass().getAnnotation(DataSource.class);
    if (ds != null && DynamicDataSourceContextHolder.isContainsDataSource(ds.name())) {
    DynamicDataSourceContextHolder.setDataSourceType(ds.name());
    }else{
    DynamicDataSourceContextHolder.setDataSourceType("master");
    }
    } else {
    DynamicDataSourceContextHolder.setDataSourceType(ds.name());
    }
    log.info("AOP动态切换数据源,className:" + point.getTarget().getClass().getName() + ";methodName" + method.getName() + ";dataSourceKey:" + (StringUtils.isEmpty(ds.name()) ? "默认数据源" : ds.name()));

    try {
    return point.proceed();
    } finally {
    //清理掉当前设置的数据源,让默认的数据源不受影响
    DynamicDataSourceContextHolder.clearDataSourceType();
    }
    }


    @Override
    public int getOrder() {
    return 1;
    }

    }

注册动态数据源

  • 初始化数据源和提供了执行动态切换数据源的工具类

  • 实现该ImportBeanDefinitionRegistrar接口的类拥有注册bean的能力。EnvironmentAware(获取配置文件配置的属性值)

  • 使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,则会调用接口方法,将其中要注册的类注册成bean。

    1
    2
    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
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    package tk.fulsun.demo.databases;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;

    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @Description 注册动态数据源
    * @Date 2021/6/14
    * @Created by 凉月-文
    */
    @Slf4j
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar , EnvironmentAware {
    private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
    //默认数据源
    private DataSource defaultDataSource;
    //用户自定义数据源
    private Map<String, DataSource> slaveDataSources = new HashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
    initDefaultDataSource(environment);
    initslaveDataSources(environment);
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
    //添加默认数据源
    targetDataSources.put("dataSource", this.defaultDataSource);
    DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
    //添加其他数据源
    targetDataSources.putAll(slaveDataSources);
    for (String key : slaveDataSources.keySet()) {
    DynamicDataSourceContextHolder.dataSourceIds.add(key);
    }

    //创建DynamicDataSource
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClass(DynamicDataSource.class);
    beanDefinition.setSynthetic(true);
    MutablePropertyValues mpv = beanDefinition.getPropertyValues();
    mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
    mpv.addPropertyValue("targetDataSources", targetDataSources);
    //注册 - BeanDefinitionRegistry
    registry.registerBeanDefinition("dataSource", beanDefinition);

    log.info("Dynamic DataSource Registry");
    }

    private void initDefaultDataSource(Environment env) {
    // 读取主数据源
    Map<String, Object> dsMap = new HashMap<>();
    String master = "spring.datasource.druid.master";
    dsMap.put("driver", env.getProperty(master + ".driver-class-name"));
    dsMap.put("url", env.getProperty(master + ".url"));
    dsMap.put("username", env.getProperty(master + ".username"));
    dsMap.put("password", env.getProperty(master + ".password"));
    defaultDataSource = buildDataSource(dsMap);
    }

    private void initslaveDataSources(Environment env) {
    Map<String, Object> dsMap = new HashMap<>();
    String slave = "spring.datasource.druid.slave";
    dsMap.put("driver", env.getProperty(slave + ".driver-class-name"));
    dsMap.put("url", env.getProperty(slave + ".url"));
    dsMap.put("username", env.getProperty(slave + ".username"));
    dsMap.put("password", env.getProperty(slave + ".password"));
    DataSource ds = buildDataSource(dsMap);
    slaveDataSources.put("slave", ds);
    }

    public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
    try {
    Object type = dataSourceMap.get("type");
    if (type == null) {
    type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
    }
    Class<? extends DataSource> dataSourceType;
    dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
    String driverClassName = dataSourceMap.get("driver").toString();
    String url = dataSourceMap.get("url").toString();
    String username = dataSourceMap.get("username").toString();
    String password = dataSourceMap.get("password").toString();
    // 自定义DataSource配置
    DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
    .username(username).password(password).type(dataSourceType);
    return factory.build();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    return null;
    }
    }

  • 导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package tk.fulsun.demo;

    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Import;
    import tk.fulsun.demo.databases.DynamicDataSourceRegister;

    /**
    * @Description 主启动类
    * @Date 2021/6/14
    * @Created by 凉月-文
    */
    @SpringBootApplication
    @Import({DynamicDataSourceRegister.class})
    @MapperScan(basePackages = "tk.fulsun.demo.mapper")
    public class DynamicDataSourceApplication{
    public static void main(String[] args) {
    SpringApplication.run(DynamicDataSourceApplication.class, args);
    }
    }

测试

  • 使用Mybatis逆向工程生成对应的mapper文件接口和实体类

Service类

  • service接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface UserService {

    int deleteByPrimaryKey(Integer id);

    int insert(User record);

    User selectByPrimaryKey(Integer id);

    List<User> selectAll();

    int updateByPrimaryKey(User record);

    }

  • 实现类

    1
    2
    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
    @Service
    public class UserServiceImpl extends BaseService<User> implements UserService {
    @Autowired
    private UserMapper mapper;

    @Override
    public int deleteByPrimaryKey(Integer id) {
    return mapper.deleteByPrimaryKey(id);
    }

    @Override
    public int insert(User record) {
    return mapper.insert(record);
    }

    @Override
    public User selectByPrimaryKey(Integer id) {
    return mapper.selectByPrimaryKey(id);
    }

    @Override
    public List<User> selectAll() {
    return mapper.selectAll();
    }

    @Override
    public int updateByPrimaryKey(User record) {
    return mapper.updateByPrimaryKey(record);
    }
    }

指定DataSource

  • 代码如下

    1
    2
    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
    package tk.fulsun.demo.service;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import tk.fulsun.demo.config.DataSourceNames;
    import tk.fulsun.demo.databases.annotation.DataSource;
    import tk.fulsun.demo.entity.User;

    /**
    * 测试多数据源
    */
    @Service
    public class DataSourceTestService {

    @Autowired
    private UserService userService;

    @DataSource(name = DataSourceNames.MASTER)
    public User queryUser(Integer userId) {
    return userService.selectByPrimaryKey(userId);
    }

    @DataSource(name = DataSourceNames.SLAVE)
    public User queryUser2(Integer userId) {
    return userService.selectByPrimaryKey(userId);
    }

    public User queryUser3(Integer userId) {
    return userService.selectByPrimaryKey(userId);
    }

    }

测试

  • 测试代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @SpringBootTest
    class UserServiceImplTest {
    @Autowired
    private DataSourceTestService dataSourceTestService;

    @Test
    @DataSource(name = DataSourceNames.MASTER)
    public void queryUser() {
    System.out.println(dataSourceTestService.queryUser(1));
    }

    @Test
    @DataSource(name = DataSourceNames.SLAVE)
    public void queryUser2() {
    System.out.println(dataSourceTestService.queryUser2(1));
    }

    @Test
    public void queryUser3() {
    System.out.println(dataSourceTestService.queryUser3(1));
    }
    }

AbstractRoutingDataSource

  • AbstractRoutingDataSource类结构,继承了AbstractDataSource,AbstractRoutingDataSource使用模板类的模式,在父类定义了determineCurrentLookupKey()虚拟方法,获取lookupkey对象;其子类必须实现该方法。源码如下:

    1
    2
    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

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    /**
    *配置的数据源
    */
    @Nullable
    private Map<Object, Object> targetDataSources;

    /**
    *默认数据源
    */
    @Nullable
    private Object defaultTargetDataSource;

    // ......

    /**
    * spring InitializingBean 实现方法,bean初始化时调用
    */
    @Override
    public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
    throw new IllegalArgumentException("Property 'targetDataSources' is required");
    }
    this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
    this.targetDataSources.forEach((key, value) -> {
    Object lookupKey = resolveSpecifiedLookupKey(key);
    DataSource dataSource = resolveSpecifiedDataSource(value);
    this.resolvedDataSources.put(lookupKey, dataSource);
    });
    if (this.defaultTargetDataSource != null) {
    this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    }
    }

    /**
    *获取jdbc链接时,调用determineTargetDataSource,获取指定的数据
    */
    @Override
    public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
    }


    /**
    *determineCurrentLookupKey方法通过子类自定义实现,获取lookupKey,然后从resolvedDefaultDataSource map对象中获取数据源
    */
    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;
    }

    /**
    *子类必须实现的获取lookupKey的方法
    */
    protected abstract Object determineCurrentLookupKey();
    }
    • 分析AbstractRoutingDataSource可知,defaultTargetDataSource,表示默认的数据源;targetDataSources表示配置的所有数据源集合;
    • afterPropertiesSet方法spring bean对象初始化方法,会把targetDataSources和defaultTargetDataSource,设置为resolvedDataSources和resolvedDefaultDataSource。
    • getConnection()获取jdbc的连接,并通过determineTargetDataSource()获取指定的数据源
    • determineTargetDataSource用到了我们需要进行实现的抽象方法determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource。

参考