SpringBoot プロジェクトは読み取りと書き込みの分離をエレガントに実装します



1. 読み書き分離の概要

Spring Boot を使用してデータベース アプリケーションを開発する場合、読み取りと書き込みを分離するのが一般的な最適化戦略です。 読み取りと書き込みを分離すると、読み取り操作と書き込み操作が異なるデータベース インスタンスに割り当てられ、システムのスループットとパフォーマンスが向上します。
読み書き分離の実装は主に、実行時にデータベース接続を動的に切り替える仕組みであるダイナミックデータソース機能によって実現されます。これにより、アプリケーションはさまざまな条件や構成に基づいてさまざまなデータ ソースを選択し、より柔軟でスケーラブルなデータベース アクセスを実現できます。


2. 読み書き分離の実現 - 基本

1. マスターデータベースとスレーブデータベースの接続情報を設定する

  
  
  
  
  
# 主库配置spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falsespring.datasource.master.username=masterspring.datasource.master.password=123456spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
# 从库配置spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falsespring.datasource.slave.username=slavespring.datasource.slave.password=123456spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver

2. マスター データベースとスレーブ データベースのデータ ソース構成クラスを作成します。

マスター/スレーブだけでなく、複数の異なるデータベースも含め、さまざまな条件制限と構成ファイルのプレフィックスを使用して、さまざまなデータ ソースの作成を完了できます。
メインデータベースのデータソース構成
  
  
  
  
  
@Configuration@ConditionalOnProperty("spring.datasource.master.jdbc-url")public class MasterDataSourceConfiguration {    @Bean("masterDataSource")    @ConfigurationProperties(prefix = "spring.datasource.master")    public DataSource masterDataSource() {        return DataSourceBuilder.create().build();    }}
データベースデータソースからの設定
  
  
  
  
  
@Configuration@ConditionalOnProperty("spring.datasource.slave.jdbc-url")public class SlaveDataSourceConfiguration {    @Bean("slaveDataSource")    @ConfigurationProperties(prefix = "spring.datasource.slave")    public DataSource slaveDataSource() {        return DataSourceBuilder.create().build();    }}

3. マスター/スレーブ データ ソース列挙の作成

  
  
  
  
  
public enum DataSourceTypeEnum {    /**     * 主库     */    MASTER,
/** * 从库 */ SLAVE, ; }

4. 動的ルーティング データ ソースを作成する

ここでスイッチが作成され、読み取り/書き込み分離の開始と終了を制御し、すべての操作をメイン ライブラリに切り替えることができます。次に、コンテキスト内のデータ ソース タイプに基づいて、異なるデータ ソース タイプの列挙を返します。
  
  
  
  
  
@Slf4jpublic class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Value("${DB_RW_SEPARATE_SWITCH:false}") private boolean dbRwSeparateSwitch; @Override protected Object determineCurrentLookupKey() { if(dbRwSeparateSwitch && DataSourceTypeEnum.SLAVE.equals(DataSourceContextHolder.getDataSourceType())) { log.info("DynamicRoutingDataSource 切换数据源到从库"); return DataSourceTypeEnum.SLAVE; } log.info("DynamicRoutingDataSource 切换数据源到主库"); // 根据需要指定当前使用的数据源,这里可以使用ThreadLocal或其他方式来决定使用主库还是从库 return DataSourceTypeEnum.MASTER; }}

5. 動的データソース構成クラスを作成する

動的データソースにマスターデータベースとスレーブデータベースのデータソースを追加し、列挙によりデータソースマップを作成し、上記ルートで返された列挙によりデータソースを切り替えられるようにします。
  
  
  
  
  
@Configuration@ConditionalOnProperty("spring.datasource.master.jdbc-url")public class DynamicDataSourceConfiguration {    @Bean("dataSource")    @Primary    public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {        Map<Object, Object> targetDataSources = new HashMap<>();        targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);        targetDataSources.put(DataSourceTypeEnum.SLAVE, slaveDataSource);
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; }}

6. DatasourceContextHolder クラスを作成し、ThreadLocal を使用して現在のスレッドのデータ ソース タイプを保存します。

新しいスレッドを作成するときに、ThreadLocal のデータを正しく読み取ることができないという潜在的なリスクがあることに注意してください。新しいスレッドの開始が必要な場合は、TransmittableThreadLocal を使用して、親スレッドと子スレッドのデータを同期できます。Git アドレス: https://github .com/alibaba/transmittable-thread-local
  
  
  
  
  
public class DataSourceContextHolder {    private static final ThreadLocal<DataSourceTypeEnum> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(DataSourceTypeEnum dataSourceType) { contextHolder.set(dataSourceType); }
public static DataSourceTypeEnum getDataSourceType() { return contextHolder.get(); }
public static void clearDataSourceType() { contextHolder.remove(); }}

7. マスターおよびスレーブ データ ソースをマークするカスタム アノテーションを作成する

  
  
  
  
  
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MasterDataSource {}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface SlaveDataSource {}

   
   
   
   
   

8. アスペクトクラスを作成し、データベース操作をインターセプトし、アノテーション設定に従ってデータソースパラメータを切り替えます。

  
  
  
  
  
@Aspect@Componentpublic class DataSourceAspect {
@Before("@annotation(xxx.MasterDataSource)") public void setMasterDataSource(JoinPoint joinPoint) { DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER); }
@Before("@annotation(xxx.SlaveDataSource)") public void setSlaveDataSource(JoinPoint joinPoint) { DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.SLAVE); }
@After("@annotation(xxx.MasterDataSource) || @annotation(xxx.SlaveDataSource)") public void clearDataSource(JoinPoint joinPoint) { DataSourceContextHolder.clearDataSourceType(); }}

9. サービス レイヤー メソッドでカスタム アノテーション タグを使用してデータ ソースをクエリします

  
  
  
  
  
@Servicepublic class TestService {    @Autowired    private TestDao testDao;
@SlaveDataSource public Test test() { return testDao.queryByPrimaryKey(11L); }}

10. データソース自動構成クラスを除外する

自動構成クラスを除外しないと、複数の dataSource オブジェクトの初期化で問題が発生します。
  
  
  
  
  
SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})


3. 高度な読み書き分離を実現

1. 接続プールを使用する (例: ヒカリ)

リンク構成を変更し、リンクプール関連の構成を追加します。
  
  
  
  
  
# 主库配置spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falsespring.datasource.master.username=masterspring.datasource.master.password=123456spring.datasource.master.driver-class-name=com.mysql.jdbc.Driverspring.datasource.master.type=com.zaxxer.hikari.HikariDataSourcespring.datasource.master.hikari.name=masterspring.datasource.master.hikari.minimum-idle=5spring.datasource.master.hikari.idle-timeout=30spring.datasource.master.hikari.maximum-pool-size=10spring.datasource.master.hikari.auto-commit=truespring.datasource.master.hikari.pool-name=DatebookHikariCPspring.datasource.master.hikari.max-lifetime=1800000spring.datasource.master.hikari.connection-timeout=30000spring.datasource.master.hikari.connection-test-query=SELECT 1
# 从库配置spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falsespring.datasource.slave.username=rootspring.datasource.slave.password=123456spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driverspring.datasource.slave.type=com.zaxxer.hikari.HikariDataSourcespring.datasource.slave.hikari.name=masterspring.datasource.slave.hikari.minimum-idle=5spring.datasource.slave.hikari.idle-timeout=30spring.datasource.slave.hikari.maximum-pool-size=10spring.datasource.slave.hikari.auto-commit=truespring.datasource.slave.hikari.pool-name=DatebookHikariCPspring.datasource.slave.hikari.max-lifetime=1800000spring.datasource.slave.hikari.connection-timeout=30000spring.datasource.slave.hikari.connection-test-query=SELECT 1

2. mybatis を統合し、書き込み時にメイン ライブラリに強制的に切り替える

設定は不要で、通常通りmybatisを組み込むことで読み書き分離機能を利用できます。
mybatis のインターセプターを使用すると、書き込み操作中にメイン ライブラリに強制的に切り替えることができます。
  
  
  
  
  
@Intercepts({        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),})@Componentpublic class WriteInterceptor implements Interceptor {    @Override    public Object intercept(Invocation invocation) throws Throwable {        // 获取 SQL 类型        DataSourceTypeEnum dataSourceType = DataSourceContextHolder.getDataSourceType();        if(DataSourceTypeEnum.SLAVE.equals(dataSourceType)) {            DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);        }        try {            // 执行 SQL            return invocation.proceed();        } finally {            // 恢复数据源  考虑到写入后可能会反查,后续都走主库            // DataSourceContextHolder.setDataSourceType(dataSourceType);        }    }}
-終わり-

この記事は、WeChat パブリック アカウント - JD Cloud Developers (JDT_Developers) から共有されています。
侵害がある場合は、削除について [email protected] までご連絡ください。
この記事は「OSC ソース作成計画」に参加していますので、読んでいる方もぜひ参加し、共有してください。

OpenAI が ChatGPT Voice Vite 5 をすべてのユーザーに無料で公開、正式にリリース オペレーターの魔法の操作: バックグラウンドでネットワークを切断、ブロードバンド アカウントを非アクティブ化、ユーザーに光モデムの変更を強制 Microsoft オープン ソースの ターミナル チャット プログラマーが ETC 残高を改ざんし、年間 260 万元以上を横領 Redis の父が使用する Pure C 言語コードは、Telegram Bot フレームワークを実装しています あなたがオープンソース プロジェクトのメンテナである場合、この種の返答にどこまで耐えることができますか? Microsoft Copilot Web AI は 12 月 1 日に正式にリリースされ、中国の OpenAI をサポートします 元 CEO 兼社長の Sam Altman 氏と Greg Brockman 氏が Microsoft に加わりました Broadcom は VMware の買収に成功したと発表しました
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10151005