开发实战|第一篇:Spring Boot基于注解实现多数据源

1.说明

在开发过程中经常遇到使用多数据源的时候,我们可以基于配置实现多数据源,也可以基于注解实现多数据源。刚好最近项目做数据迁移中时使用到多数据源,在此以demo形式展示如何使用Spring Boot基于注解整合多数据源。整合步骤及方法主要借鉴了网上开源项目renren-fast

2.环境准备

  • 搭建物理环境(虚拟机安装Mysql)
服务 说明
192.168.1.100 db1(数据库)
192.168.1.101 db2(数据库)
Spring Boot 2.1.5.RELEASE
Mysql 8.0.16

说明:
1.分别在两台虚拟机中安装mysql数据库,在此基于docker安装(前提虚拟机安装docker),主要步骤如下

# 拉取镜像
$ docker pull mysql:latest
# 启动镜像,分别在主机挂载配置、日志、数据
$ docker run -d --name mysql -p 3306:3306 -v /home/milk/docker/mysql/conf:/etc/mysql/conf.d -v /home/milk/docker/mysql/logs:/logs -v /home/milk/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root mysql:latest
# 进行mysql容器
$ docker exec -it mysql /bin/bash
# 进行容器中mysql数据库,密码为上边设置的`root`
$ mysql -u root -p 
# 创建数据库
$ create database db1

2.创建两张数据表t_studyt_user

CREATE TABLE t_user
(
    id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
    name varchar(20),
    password varchar(20)
);

INSERT INTO `t_user` (`name`, `password`) VALUES ('user', 'qqwert35274');
-----------------------------------------------------------------------------------------------------------------------
CREATE TABLE t_study
(
    id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
    name varchar(20),
    password varchar(20)
);
INSERT INTO `t_study` (`name`, `password`) VALUES ('study', '1234543er');

完成上述操作,我们已经准备好mysql环境及数据库

  • 搭建项目环境

创建Maven工程,引入Spring Boot基本依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
  </parent>

  <dependencies>
    <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>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.20</version>
    </dependency>
     <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>RELEASE</version>
    </dependency>
 </dependencies>

完成上述步骤,我们的硬件环境和项目环境已经搭建完成,下边进行具体整合。

3.演示案例

  • 引入所需依赖

主要引入了druid、mybatisPlus、mysql依赖

 <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.20</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
    <exclusions>
      <exclusion>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  • 配置文件配置多数据源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      study:
        url: jdbc:mysql://192.168.1.100:3306/db1
        username: root
        password: root

      user:
        url: jdbc:mysql://192.168.100.102:3306/db2
        username: root
        password: root
  • 整合数据源

(1) 配置数据源定义

public interface DataSourceType {

  String dbOne = "db1";

  String dbTwo = "db2";

}

(2) 自定义数据源配置

@Configuration
public class DataSourceConfig {

  @Bean("dbOne")
  @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
  public DataSource dbOneDatasource(){
    return DruidDataSourceBuilder.create().build();
  }

  @Bean("dbTwo")
  @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
  public DataSource dbTwoDatasource(){
    return DruidDataSourceBuilder.create().build();
  }

  @Bean
  @Primary
  public RoutingDataSource dataSource(@Qualifier(value = "dbOne") DataSource dbOne,@Qualifier(value = "dbTwo") DataSource dbTwo){
    Map<Object,Object> datasourceMap = new HashMap<>();
    datasourceMap.put(DataSourceType.dbOne, dbOne);
    datasourceMap.put(DataSourceType.dbTwo, dbTwo);
    return new RoutingDataSource(dbOne,datasourceMap);
  }
}

说明

  1. 分别配置两个bean,通过@ConfigurationProperties配置前缀对应配置文件中配置的两个数据源前缀
  2. 配置RoutingDataSource,该对象在下边创建,主要通过其构造方法创建一个Map保存配置的数据源,key:设置的数据源名称,value:设置的数据源,在此对应着key:db1,value:dboneDatasource

(3) 书写RoutingDataSource

public class RoutingDataSource extends AbstractRoutingDataSource {

  private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

  public RoutingDataSource(DataSource dbOne, Map<Object, Object> datasourceMap) {
    super.setDefaultTargetDataSource(dbOne);
    super.setTargetDataSources(datasourceMap);
    super.afterPropertiesSet();
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return getDatasource();
  }

  public static String getDatasource() {
    return contextHolder.get();
  }

  public static void setDatasource(String datasource) {
    contextHolder.set(datasource);
  }

  public static void clearDatasource(){
    contextHolder.remove();
  }

}

说明:

  1. 通过构造方法,将上边配置中创建的MapdbOneDataSource传递对象RoutingDataSource
  2. 构造方法中使用了父类AbstractRoutingDataSource三个方法,分别是创建默认数据源dbOneDataSource,创建目标数据源Map,设置后续操作。
  3. determineCurrentLookupKey主要是从当前线程ThreadLocal中获取数据源

(4) 书写注解ExtDataSource

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExtDataSource{

  String value() default "";

}

说明:该注解可以作用在对象、方法上

(5) 书写切面ExtDataSourceAspect

@Order(Ordered.HIGHEST_PRECEDENCE)
@Aspect
@Component
public class ExtDataSourceAspect{

  @Pointcut("@annotation(com.study.db.datasource.annotion.ExtDataSource)"  +
      "|| @within(com.study.db.datasource.annotion.ExtDataSource)")
  public void datasourcePointcut() {
  }

  @Around("datasourcePointcut()")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    //获取目标对象
    Class targetClazz = pjp.getTarget().getClass();
    //获取目标方法
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    Method targetMethod = methodSignature.getMethod();

    //获取对象和方法上注解值 
    ExtDataSourcetargetDB = (ExtDataSource) targetClazz.getAnnotation(ExtDataSource.class);
    ExtDataSourcemethodDB = targetMethod.getAnnotation(ExtDataSource.class);

    //判断并处理
    if (Objects.nonNull(targetDB) || Objects.nonNull(methodDB)) {
      String value;
      if (Objects.nonNull(methodDB)) {
        value = methodDB.value();
      } else {
        value = targetDB.value();
      }
      RoutingDataSource.setDatasource(value);
    }
    try {
      return pjp.proceed();
    } finally {
      RoutingDataSource.clearDatasource();
    }
  }
}

说明:

  1. @Order:定义顺序,值越低表示优先级越高
  2. @Aspect表示为一切面,@Component交给Spring管理
  3. @Poingcut:定义切入点,@annotation表示带该注解的方法,@within表示该注解的对象都会进行切面
  4. 切面逻辑处理,主要判断方法或类上是否含有注解@ExtDataSource,如果存在,则获取注解的value值 ,该值对应配置的数据源key值。
  5. 通过后通RoutingDataSource.setDatasource(value);设置数据源,同时将该value值存入线程的ThreadLocalMap

(6) 测试多数据源

  • 创建两个实体
@TableName("t_study")
@Data
public class Study {
  /**
   * 用户ID
   */
  @TableId
  private Long id;
  /**
   * 用户名
   */
  private String name;
   /**
   * 密码
   */
  private String password;
 
}
--------------------------------------------------------------------------------------------------------------------
@TableName("t_user")
@Data
public class User {
  /**
   * 用户ID
   */
  @TableId
  private Long id;
  /**
   * 用户名
   */
  private String name;
 
  /**
   * 密码
   */
  private String password;
}
  • 启动类排出默认数据,并引入自定义数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@Import(DataSourceConfig.class)
public class DBApp {

  public static void main(String[] args) {
    SpringApplication.run(DBApp.class);
  }

}
  • 创建Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {

}
--------------------------------------------------------------------------------------------------------------------
@Mapper
public interface StudyMapper extends BaseMapper<Study> {

}
  • 创建两个controller
@RestController
@IDatasouce(value = DataSourceType.dbOne)
public class StudyController {

  @Resource
  private StudyMapper studyMapper;

  @GetMapping("/getStudy")
  public Study getStudyById(@RequestParam("id") Integer id){
    return studyMapper.selectById(id);
  }

}
--------------------------------------------------------------------------------------------------------------------
@RestController
public class UserController {

  @Resource
  private UserMapper userMapper;

  @GetMapping("/getUser")
  @IDatasouce(value = DataSourceType.dbTwo)
  public User getUserById(@RequestParam("id") Integer id){
    User user = userMapper.selectById(id);
    System.out.println(user);
    return user;
  }
}

测试结果,调用http://localhost:8888/getStudyById?id=1http://localhost:8888/getUserById?id=1分别显示如下,表示搭建成功

# 该数据来至dbOne中`t_user`表
{"id":1,"name":"user","password":"qqwert35274"} 

# 该数据来至dbTwo中`t_study`表
{"id":1,"name":"study","password":"1234543er"} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章