開發實戰|第一篇: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"} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章