Spring Boot のマルチデータ ソース構成とアノテーションによるデータ ソースの動的切り替え

1. AbstractRoutingDataSource クラスの概要

Spring Boot は、ユーザー定義のルールに従って使用するデータ ソースを選択するための AbstractRoutingDataSource を提供します。これにより、各データベース操作の前に使用するデータ ソースを設定して、動的にルーティングされるデータ ソースを実現できます。その抽象メソッド決定CurrentLookupKey()は、使用するデータ ソースを決定します。
ここに画像の説明を挿入
getConnection() はデータベース接続を取得し、ルックアップ キーに従ってさまざまなターゲット データ ソースを呼び出します。通常は (必ずではありませんが)、スレッドにバインドされたトランザクション コンテキストを通じて行われます。これにより、「データソースの動的切り替え」が実現できることがわかりました。プログラム実行時にデータソースが動的にプログラムに組み込まれ、データソースを柔軟に切り替えることができるため、読み書き分離機能が実現されます。ミドルウェアに依存せずに実現できます。

AbstractRoutingDataSource はロジックを実装します。

  • 抽象クラス AbstractRoutingDataSource を継承し、determineCurrentLookupKey() メソッドを実装します。LookupKey の選択ルールをカスタマイズします。

  • 構成された複数のデータ ソースを AbstractRoutingDataSource の targetDataSources およびdefaultTargetDataSource に配置し (setDefaultTargetDataSource メソッドと setTargetDataSources メソッドを使用)、afterPropertiesSet() メソッドを使用して、それぞれデータ ソースを AbstractRoutingDataSource のsolvedDataSources プロパティとresolvedDefaultDataSource プロパティにコピーします。
    ここに画像の説明を挿入

  • AbstractRoutingDataSource の getConnection() メソッドを呼び出す場合は、まず、determineTargetDataSource() メソッドを呼び出して DataSource を返し、getConnection() を実行します。

ここに画像の説明を挿入
terminalTargetDataSource() メソッドは、determineCurrentLookupKey() メソッドによって返された lookupKey を呼び出すことによって、使用するデータ ソースを決定します。
ここに画像の説明を挿入

2. ThreadLocal クラスの紹介

https://blog.csdn.net/qq_38293564/article/details/80459827

3. 環境の準備

3.1 データベースの準備

ローカル環境の MySQL データベース (データベース mydb) は、テーブル t_user を作成します。

CREATE TABLE `t_user` (
  `c_id` varchar(20) NOT NULL,
  `c_username` varchar(20) DEFAULT NULL,
  `c_password` varchar(20) DEFAULT NULL,
  `c_gender` tinyint(2) DEFAULT NULL,
  PRIMARY KEY (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `mydb`.`t_user`(`c_id`, `c_username`, `c_password`, `c_gender`) VALUES ('1', '思思', '123', 1);

クラウド サーバーの MySQL データベースで、データベース book_db を作成し、テーブル t_userinfo を作成します。

CREATE TABLE `t_user_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) DEFAULT NULL COMMENT '登录密码',
  `areaObj` varchar(255) DEFAULT NULL COMMENT '所在学院',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `sex` tinyint(255) DEFAULT NULL COMMENT '性别',
  `user_photo` varchar(255) DEFAULT NULL COMMENT '学生照片',
  `birthday` varchar(20) DEFAULT NULL COMMENT '出生日期',
  `telephone` varchar(20) DEFAULT NULL COMMENT '联系电话',
  `address` varchar(255) DEFAULT NULL COMMENT '家庭地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `book_db`.`t_user_info`(`id`, `user_name`, `password`, `areaObj`, `name`, `sex`, `user_photo`, `birthday`, `telephone`, `address`) VALUES (1, '张三', '123', '哈尔滨', '张三散', 1, '123', '02-16', '15756892458', '黑龙江省哈尔滨市');

データベース チャットルームを作成し、テーブル管理者を作成します。

CREATE TABLE `admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL COMMENT '登录账号',
  `nickname` varchar(20) NOT NULL COMMENT '昵称',
  `password` varchar(255) NOT NULL COMMENT '密码',
  `user_profile` varchar(255) DEFAULT NULL COMMENT '管理员头像',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

INSERT INTO `chatroom`.`admin`(`id`, `username`, `nickname`, `password`, `user_profile`) VALUES (1, 'admin', '系统管理员', '$2a$10$PyloUEVGuO0fUZdfeIaROOTluRmccl.Scifa8S7Os0Wt.s4bDkb', 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1784117537,3335593911&fm=26&gp=0.jpg');

3.2 プロジェクトの作成

SpringBoot プロジェクトを作成し、MyBatis-Plus を統合します。pom.xml によって導入された依存関係:

<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<!--mybatis plus-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--druid-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

	</dependencies>

リソースフォルダー内のマッパーファイルを読み込むように設定します

	<build>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>

		</plugins>
	</build>

4. 具体的な実装

4.1 データソース列挙クラスの定義

データ ソース列挙クラス DataSourceTypeEnum を定義する

public enum DataSourceTypeEnum {
    
    
    /**
     * chatroom
     */
    CHATROOM("chatroom"),
    /**
     * book_db
     */
    BOOK_DB("book_db"),
    /**
     * mydb
     */
    MY_DB("mydb");

    private final String name;

    DataSourceTypeEnum(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }

}

4.2 動的なマルチデータ ソース クラスの作成

動的マルチデータ ソース クラス DynamicDataSource を定義して、異なるスレッド間での複数のデータ ソースの選択と切り替えを管理し、Spring によって提供される AbstractRoutingDataSource 抽象クラスを拡張し、determineCurrentLookupKey メソッドを書き換えます。使用します。

public class DynamicDataSource extends AbstractRoutingDataSource {
    
    

    /**
     * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
     * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
     *
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources       目标数据源
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
    
    
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * determineCurrentLookupKey决定使用哪个数据库
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
    
    
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
    
    
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
    
    
        return CONTEXT_HOLDER.get();
    }
    
    public static void clearDataSource() {
    
    
        CONTEXT_HOLDER.remove();
    }
}

4.3 動的なマルチデータソース構成クラスの作成

DynamicDataSourceConfig クラスは、設定ファイル内の 3 つのデータソースの設定を読み取り、DataSource タイプに対応する Bean を作成するための設定クラスとして使用されます。

@Configuration
public class DynamicDataSourceConfig {
    
    

    @Bean(name="chatroom")
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource dataSource1(){
    
    
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name ="book_db")
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource dataSource2(){
    
    
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name="mydb")
    @ConfigurationProperties("spring.datasource.druid.third")
    public DataSource dataSource3(){
    
    
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name="dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource() {
    
    
        Map<Object, Object> targetDataSources = new HashMap<>(5);
        targetDataSources.put(DataSourceTypeEnum.CHATROOM.getName(), dataSource1());
        targetDataSources.put(DataSourceTypeEnum.BOOK_DB.getName(), dataSource2());
        targetDataSources.put(DataSourceTypeEnum.MY_DB.getName(), dataSource3());
        return new DynamicDataSource(dataSource1(), targetDataSources);
    }
}

4.4 カスタム アノテーションを使用してデータ ソースを指定する

カスタム アノテーション @SpecifyDataSource は、サービス レイヤー メソッドで使用するデータ ソースをマークするために使用されます。ここでの定義では、デフォルトでデータ ソース DataSourceType.CHATROOM が使用されます。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpecifyDataSource {
    
    

    /**
     * @return
     */
    DataSourceTypeEnum value() default DataSourceTypeEnum.CHATROOM;
}

4.5 AOP はデータソースの動的な切り替えを実装します

データ ソース インターフェイス クラス DataSourceAspect を定義します。これは、SpecifyDataSource アノテーションでマークされたメソッドを実装する前に、アノテーションで指定されたデータ ソースを切り替えるために使用されます。

@Aspect
@Component
@Order(value = 1)
public class DataSourceAspect {
    
    

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("@annotation(top.javahai.datasource.annotation.SpecifyDataSource)")
    public void dataSourcePointCut() {
    
    

    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
    
    
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        SpecifyDataSource ds = method.getAnnotation(SpecifyDataSource.class);
        if (ds == null) {
    
    
            DynamicDataSource.setDataSource(DataSourceType.CHATROOM.getName());
            logger.info("set datasource is " + DataSourceType.CHATROOM);
        } else {
    
    
            DynamicDataSource.setDataSource(ds.value().getName());
            logger.info("set datasource is " + ds.value().getName());
        }

        try {
    
    
            return point.proceed();
        } finally {
    
    
            DynamicDataSource.clearDataSource();
            logger.info("clean datasource");
        }
    }
}

5. テスト使用

5.1 データソースの構成

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
# 数据源1
spring.datasource.druid.first.url=jdbc:mysql://158.156.444.68:3306/chatroom?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.druid.first.username=root
spring.datasource.druid.first.password=123456
# 数据源2
spring.datasource.druid.second.url=jdbc:mysql://158.156.444.68:3306/book_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.druid.second.username=root
spring.datasource.druid.second.password=123456

#数据源3
spring.datasource.druid.third.url=jdbc:mysql:///mydb?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.druid.third.username=root
spring.datasource.druid.third.password=123456

mybatis-plus.mapper-locations=classpath:mapper/*.xml


#输出sql执行日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

5.2 エンティティクラスの作成

エンティティクラス管理者の作成

public class Admin implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 登录账号
     */
    private String username;

    /**
     * 昵称
     */
    private String nickname;

    /**
     * 密码
     */
    private String password;

    /**
     * 管理员头像
     */
    private String userProfile;

//省略getter/setter方法

エンティティクラス TUser を作成する

public class TUser implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @TableId(value = "c_id", type = IdType.AUTO)
    private Integer cId;

    private String cUsername;

    private String cPassword;

    private Integer cGender;
}

エンティティクラスTUserinfoの作成

@TableName(value = "t_user_info")
public class TUserinfo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    /**
     * user_name
     */
    private String userName;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 所在学院
     */
    @TableField("areaObj")
    private String areaObj;

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private Integer sex;

    /**
     * 学生照片
     */
    private String userPhoto;

    /**
     * 出生日期
     */
    private String birthday;

    /**
     * 联系电话
     */
    private String telephone;

    /**
     * 家庭地址
     */
    private String address;
}

テスト用のUserVOを作成する

public class UserVO {
    
    

    private List<Admin> adminList;
    private List<TUserinfo> tUserinfos;
    private List<TUser> tUsers;
}

5.3 サービス層コード

@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {
    
    

    public List<Admin> getAll(){
    
    
        return this.list(null);
    }
}
@Service
public class TUserinfoServiceImpl extends ServiceImpl<TUserinfoMapper, TUserinfo> implements ITUserinfoService {
    
    

    @SpecifyDataSource(value = DataSourceTypeEnum.BOOK_DB)
    public List<TUserinfo> selectAll(){
    
    
        return this.list(null);
    }

}
@Service
public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements ITUserService {
    
    

    @SpecifyDataSource(value = DataSourceTypeEnum.MY_DB)
    public List<TUser> selectAll(){
    
    
        return this.list(null);
    }
}
public interface AdminMapper extends BaseMapper<Admin> {
    
    

}

public interface TUserinfoMapper extends BaseMapper<TUserinfo> {
    
    

}

public interface TUserMapper extends BaseMapper<TUser> {
    
    

}

5.4 制御層コード

テスト用のインターフェース /test/list を作成する

@RestController
@RequestMapping("/test")
public class TestController {
    
    

    @Autowired
    private AdminServiceImpl adminService;

    @Autowired
    private TUserinfoServiceImpl userinfoService;

    @Autowired
    private TUserServiceImpl userService;

    @GetMapping("/list")
    public UserVO list(){
    
    
        List<Admin> adminList= adminService.getAll();
        List<TUserinfo> tUserinfos = userinfoService.selectAll();
        List<TUser> tUsers = userService.selectAll();
        UserVO userVO = new UserVO();
        userVO.setAdminList(adminList);
        userVO.settUserinfos(tUserinfos);
        userVO.settUsers(tUsers);
        return userVO;
    }
}

ブラウザは /test/list をリクエストして
ここに画像の説明を挿入
、コンソール出力を表示し、データ ソースの切り替えログを表示します。

、

完全なデモ コードのアドレス: https://github.com/JustCoding-Hai/learn-everyday/tree/master/learn-multi_data_source

おすすめ

転載: blog.csdn.net/huangjhai/article/details/115024273