エキサイト株式会社エンジニアの佐々木です。SpringBoot x MyBatis でDBのReader/Writerの接続設定をご紹介します。
application.ymlの設定
application.ymlを下記のように
spring: datasource: writer: username: aaa password: bbb hikari: max-lifetime: 600000 maximum-pool-size: 5 connection-test-query: "SELECT 1;" keep-alive-time: 60000 reader: username: aaa password: bbb hikari: max-lifetime: 600000 maximum-pool-size: 10 connection-test-query: "SELECT 1;" keep-alive-time: 60000
Reader/Writerの設定
Reader/Writerの設定は下記のようにコードを書きます。他DBに接続するときにも使えるの汎用的なコードになります。@Bean
に名前をつけ、@Qualify
で取り出しながらDataSourceの設定を行います。
// DataSourceWriterConfig.java @Configuration public class DataSourceWriterConfig { @Bean("hikariWriter") @ConfigurationProperties(prefix = "spring.datasource.writer.hikari") // spring.datasource.writer.hikariのHikariCP読み込みを設定 public HikariConfig hikariConfig() { return new HikariConfig(); } @Bean(name = "dbWriterProperties") @ConfigurationProperties(prefix = "spring.datasource.writer") // spring.datasource.writer の読み込み設定 public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean(name = "dbWriter") @DependsOn("dbWriterProperties") public DataSource dataSource( @Qualifier("hikariWriter") HikariConfig hikariConfig, @Qualifier("dbWriterProperties") DataSourceProperties dataSourceProperties) { hikariConfig.setDataSource( dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build() ); return new HikariDataSource(hikariConfig); } } // DataSourceReaderConfig.java @Configuration public class DataSourceReaderConfig { @Bean("hikariReader") @ConfigurationProperties(prefix = "spring.datasource.reader.hikari") // spring.datasource.reader.hikariのHikariCP読み込みを設定 public HikariConfig hikariConfig() { return new HikariConfig(); } @Bean(name = "dbReaderProperties") @ConfigurationProperties(prefix = "spring.datasource.reader") // spring.datasource.reader の読み込みを設定 public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean(name = "dbReader") @DependsOn("dbReaderProperties") public DataSource dataSource( @Qualifier("hikariReader") HikariConfig hikariConfig, @Qualifier("dbReaderProperties") DataSourceProperties dataSourceProperties) { hikariConfig.setDataSource( dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build() ); return new HikariDataSource(hikariConfig); }
トランザクションまわりの全体コード
下記がDBの接続設定を処理するときのトランザクションの設定になります。複数DBに接続する必要があるときは、この設定が増えていきます。(参照用、書き込み用、集計用など...)
// DataSourceTransactionConfig.java @Configuration @RequiredArgsConstructor @MapperScan(basePackages = "jp.co.excite.sample.sql") public class DataSourceTransactionConfig { private final MybatisAutoConfiguration mybatisAutoConfiguration; @Bean("replicationResolver") public ReplicationResolver routingDataSource(@Qualifier(DataSourceWriterConfig.DATASOURCE_NAME) DataSource dataSourceWriter, @Qualifier(DataSourceReaderConfig.DATASOURCE_NAME) DataSource dataSourceReader) { ReplicationResolver replicationDataSource = new ReplicationResolver(); replicationDataSource.setTargetDataSources(Map.of(DataSourceType.READER, dataSourceReader, DataSourceType.WRITER, dataSourceWriter)); replicationDataSource.setDefaultTargetDataSource(dataSourceReader); return replicationDataSource; } @Bean("mainTransactionManager") @Primary public PlatformTransactionManager transactionManager(@Qualifier("readerDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean("mainDataSource") @Primary public DataSource dataSource(@Qualifier("replicationResolver") ReplicationResolver dataSource) { return new LazyConnectionDataSourceProxy(dataSource); } @Bean public SqlSessionFactory sessionFactory(@Qualifier("mainDataSource") DataSource dataSource) throws Exception { return mybatisAutoConfiguration.sqlSessionFactory(dataSource); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } static class ReplicationResolver extends AbstractRoutingDataSource { @Override protected DataSourceType determineCurrentLookupKey() { return TransactionSynchronizationManager .isCurrentTransactionReadOnly() ? DataSourceType.READER : DataSourceType.WRITER; } } static enum DataSourceType { WRITER, READER } }
一つ一つずつみていきます。
ReplicationResolver, DataSourceType
下記コードをから、トランザクションの設定を行います。
static class ReplicationResolver extends AbstractRoutingDataSource { @Override protected DataSourceType determineCurrentLookupKey() { return TransactionSynchronizationManager .isCurrentTransactionReadOnly() ? DataSourceType.READER : DataSourceType.WRITER; } } static enum DataSourceType { WRITER, READER }
やっていることは簡単で、DataSourceType
は、Enumを利用し、書き込みと読み込みを区別する目印を設定します。ReplicationResolver
は、AbstractRoutingDataSource
を継承して、Routingの条件設定などを行います。シャードキーやリードオンリーのようなせていが入っているので、シンプルな要件だと実装は簡単だと思います。今回は、isCurrentTransactionReadOnly()
のときに、参照用DBか更新用DBかの判定いれるために上記のように設定します。
ReplicationResolverのBean化
先ほど宣言したReplicationResolver
を、Bean化します。今回は、ReaderDBとWriterDBに振り分けますので、その設定も行います。
@Bean("replicationResolver") public ReplicationResolver routingDataSource(@Qualifier("dbWriter") DataSource dataSourceWriter, @Qualifier("dbReader") DataSource dataSourceReader) { ReplicationResolver replicationDataSource = new ReplicationResolver(); replicationDataSource.setTargetDataSources(Map.of( DataSourceType.READER, dataSourceReader, DataSourceType.WRITER, dataSourceWriter )); replicationDataSource.setDefaultTargetDataSource(dataSourceReader); return replicationDataSource; }
setTargetDataSources()
に、振り分けたいデータソースを設定します。デフォルトのデータソースも設定をします。
DataSource
Reader/WriterのときにもDataSource
は宣言したが、これはトランザクション用のDataSource
になります。ReplicationResolver
でセットしたものから生成します。@Primary
をつけているのは、複数のDataSource定義があると起動時にエラーになりますので、デフォルトで接続する先を指定するためにトランザクション設定がされているものに@Primary
をつけておきます。
@Bean("mainDataSource") @Primary public DataSource dataSource(@Qualifier("replicationResolver") ReplicationResolver replicationResolver) { return new LazyConnectionDataSourceProxy(replicationResolver); }
PlatformTransactionManager
トランザクション管理するAPIを提供するインターフェースで、今回はMyBatisを使うので、DataSourceTransactionManager
クラスを使用します。今回は1つしか設定しませんので、@Primary
は不要ですが今後のことを考えてつけておきます。
@Bean("mainTransactionManager") @Primary public PlatformTransactionManager transactionManager(@Qualifier(DATASOURCE_MAIN) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
複数のTransactionManager
の設定がある場合は、利用時に@Transactional
の引数で@Transactional( transactionManager = "mainTransactionManager")
のような形で明示的に呼び出しが可能です。指定がない場合は、@Primary
が設定されているTransactionManager
が使用されます。
SqlSessionFactory
application.ymlからMyBatisの設定を読み込み、そこからSqlSessionFactory
とDataSource
をつなぎ設定を行います。
@Bean public SqlSessionFactory sessionFactory(@Qualifier("mainDataSource") DataSource dataSource) throws Exception { return mybatisAutoConfiguration.sqlSessionFactory(dataSource); }
SqlSessionTemplate
上記で作成した、SqlSessionFactoryをSqlSessionTemplateのコンストラクタに渡しBean化します。
@Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); }
まとめ
DataSourceが合計3つでてくるので、最初は混乱すると思いますが、Bean名をつけて区別しながら設定すればうまくいくと思います。AOPで切り替える方法もネットには転がっていますが、ブラックボックスになりがちなので、公式に提供されているものをなるべく駆使しております。
ご参考になればと思います。