以前このブログで紹介したMaven + Eclipseでマルチモジュールプロジェクトを作成するのなかでサンプルコードで示したものの特にマルチデータソースのやり方についての解説です。
この記事に書いてある通り、PostgreSQLとMongoDBを組み合わせる場合を例として書いてあります。
完成版のソースコードはこちらです。
ミドルウェア |
バージョン |
Spring Boot |
2 |
Java |
1.8 |
Apache Maven |
3 |
細かいバージョンはGitHubを参照してください。
Spring-Data-JPAとSpring-Data-MongoDBを利用した場合のやり方を解説します。
構成の概要
プロジェクト/モジュール/パッケージは次のようになっています。
2モジュールありmulti-module-batch
はサンプルを動かす利用側コード
multi-module-common
はデータアクセス系を提供する共通モジュールの位置づけです。
設定ファイル
PostgreSQLとMongoDBを利用する場合を考えてみます。
まずは設定ファイルの構成を考えます。
application.yml
spring:
datasource:
postgres:
url: jdbc:postgresql://localhost:5433/postgres
username: postgres
password: postgres
mongo:
host: localhost
port: 27018
database: mongo
profiles:
include: common
重要なところのみ抜粋です。
このように利用する側の設定で、ユーザ名、パスワード、データベースを指定できれば設定ファイルとしては十分かと思います。
また、このプロジェクトではマルチモジュールを採用していてデータベースアクセス部分はcommonのプロジェクトが別にいます。
例えばコネクションプール系の設定はだいたい一緒でいいだろうというのがあると思いますので、
commonの設定に切り出したいとします。
それがinclude: common
の部分です。この場合Springがapplication-common.ymlという設定ファイルを追加で読み込みます。
共通モジュール側のプロジェクトのapplication-common.yaml
packages:
common:
component: com.example.multiModule
postgres:
entity: com.example.multiModule.common.spring.postgres.entities
repository: com.example.multiModule.common.spring.postgres.repositories
mongo:
entity: com.example.multiModule.common.spring.mongo.entities
repository: com.example.multiModule.common.spring.mongo.repositories
spring:
datasource:
postgres:
driver-class-name: org.postgresql.Driver
hikari:
minimum-idle: 2
maximum-pool-size: 4
idle-timeout: 60000
connection-timeout: 30000
leak-detection-threshold: 5000
max-lifetime: 600000
使用するドライバとコネクションプールの設定です。
Mongoは今回特にこれに相当する設定をしないので、省略しています。
packagesの設定は@EnableJpaRepositories
, @EnableMongoRepositories
で利用します。
マルチデータソースの場合、Configを個別に用意してあげる必要があります。
マルチデータソースの場合、Configを個別に用意してあげる必要があります。
こちらのプロジェクトではデータアクセスにかかわる部分、RepositoryとEntittyも実装します。
また、Serviceも今回こちらに実装します。
基本的に定型の設定
をコードベースで書くことになります。ある意味覚えゲーです。
PostgreSQL設定
ポイントとなるのはPostgresConfig.java
とMongoConfig.java
です。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"${packages.common.postgres.repository}"},
entityManagerFactoryRef = "postgresEntityManagerFactory",
transactionManagerRef = "postgresTransactionManager"
)
@EntityScan(basePackages = { "${packages.common.postgres.entity}" })
public class PostgresConfig {
@Autowired
PackageConfig packageConfig;
@Bean
@Primary
@ConfigurationProperties("spring.datasource.postgres")
public DataSourceProperties postgresDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.jpa.postgres")
public JpaProperties postgresJpaProperties() {
return new JpaProperties();
}
@Bean(name = "postgresDataSource")
@ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean(name = "postgresEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean postgresEntityManagerFactory(
final EntityManagerFactoryBuilder builder,
@Qualifier("postgresDataSource") final DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(packageConfig.getCommon().getPostgres().getEntity())
.persistenceUnit("postgres")
.properties(postgresJpaProperties().getProperties())
.build();
}
@Bean(name = "postgresTransactionManager")
@Primary
public PlatformTransactionManager postgresTransactionManager(
@Qualifier("postgresEntityManagerFactory") final EntityManagerFactory entityManagerFactory) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
カスタムで設定を書くので@EnableTransactionManagement
, @EnableJpaRepositories
の指定は必須となります。
後者の@EnableJpaRepositoryですが@Repository
をついたクラスを検索してBeanとして登録するためのものです。
また、マルチモジュールにしている関係でEntityの場所がどこ?となってしまうので@EntityScan
を指定してやる必要があります。
各メソッドの説明
設定値を読む用のメソッドもあったりして厄介ですが、このクラスのメソッドで絶対に書かなければならないのが、3つあります。
@Bean(name = "postgresDataSource")
@ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
これが1つ目です。HikariCP
を使うようにしています。
さらにDataSourceProperties
経由でjdbcドライバ等の設定を読むようにしています。
名前が被るのでBean名をアノテーションで変更してあげます。ここではpostgresDataSourceとしています。
細かい内容はこの辺りの内容が参考になると思います。
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-data-access
2つ目が
@Bean(name = "postgresEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean postgresEntityManagerFactory(
final EntityManagerFactoryBuilder builder,
@Qualifier("postgresDataSource") final DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(packageConfig.getCommon().getPostgres().getEntity())
.persistenceUnit("postgres")
.properties(postgresJpaProperties().getProperties())
.build();
}
です。なんとも長ったらしいメソッドですが、
LocalContainerEntityManagerFactoryBeanを返すメソッドを作ってやらなければなりません。
エンティティ登録管理のファクトリを教えてくれってことですね。Springムズカシイ。
補足ですがアノテーションの@Qualifier
はどのDataSourceよってSpringがわかんないので、
さっき定義したpostgresDataSouceっすよってことで教えてあげます。
しばらく触ってないと忘れがちですが@Bean
によってクラスを返すメソッドを定義してSpring Beanにすると
@Autowired
で注入できるのですよね。
何を言っているのかわからねー、という人はここがわかりやすいかなと思います。
紛らわしいのが単にgetter/setterの塊のJava Beans。これとは違う用語なので混同しないでくださいね。
3つ目、
@Bean(name = "postgresTransactionManager")
@Primary
public PlatformTransactionManager postgresTransactionManager(
@Qualifier("postgresEntityManagerFactory") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
これも先ほど同様にな感じで先ほど定義したBeanを注入して設定する、な感じです。
MongoDBの設定
Spring-Data-MongoDBを利用する前提です。
こちらはずいぶんシンプルです。設定の読み込みをのぞけば2メソッド書けば終わりです。
また、@EnableMongoRepositories
と@EntityScan
を書く必要があります。後者はマルチモジュールにしているためですね。
@Configuration
@EnableMongoRepositories(basePackages = { "${packages.common.mongo.repository}" })
@EntityScan(basePackages = { "${packages.common.mongo.entity}" })
public class MongoConfig {
@Autowired
MongoMappingContext mongoMappingContext;
@Bean
@Primary
@ConfigurationProperties("spring.datasource.mongo")
public MongoProperties mongoProperties() {
return new MongoProperties();
}
@Bean
@Primary
public MongoClient mongo() {
return new MongoClient(mongoProperties().getHost(), mongoProperties().getPort());
}
@Bean
@Primary
public MongoTemplate mongoTemplate() throws Exception {
MongoDbFactory factory = new SimpleMongoDbFactory(mongo(), mongoProperties().getDatabase());
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(factory, converter);
}
}
各メソッドの説明
@Bean
@Primary
public MongoClient mongo() {
return new MongoClient(mongoProperties().getHost(), mongoProperties().getPort());
}
解説不要そうなこちらと
@Bean
@Primary
public MongoTemplate mongoTemplate() throws Exception {
MongoDbFactory factory = new SimpleMongoDbFactory(mongo(), mongoProperties().getDatabase());
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(factory, converter);
}
こちらが必要です。実はこちらについては
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(
new SimpleMongoDbFactory(new MongoClient(mongo.getHost(), mongo.getPort()), mongo.getDatabase()));
}
と書くだけでよいのですが、デフォルトのまま使うとすべてのコレクションのドキュメントに_class
と勝手にフィールドがつくのでそれが嫌だったので消す設定を追加しています。
以上で終わりです。
どう使えばいいのか
それで、このように定義した場合後はどうすればいいかというとEntityとRepositoryの置き場さえ守れば、
通常通りEntityとRepositoryを書けます。
PostgreSQLの場合、@EnableJpaRepositories
と設定ファイルで、
Entity: com.example.multiModule.common.spring.postgres.entities
Repository: com.example.multiModule.common.spring.postgres.repositories
とパッケージを指定してあげてます。
Mongoの場合もほぼ同様で、
Entity: com.example.multiModule.common.spring.mongo.entities
Repository: com.example.multiModule.common.spring.mongo.repositories
つまり、PostgreSQLはpostgesパッケージ配下、MongoDBはmongoパッケージ配下に書けばよいことになります。
詳細はGitHubのリンクを参照してください。
備考
multi-module-batch
にある利用する側のモジュールのコードです。
@SpringBootApplication
@ComponentScan(basePackages = { "${packages.common.component}", "${app.packages}" })
public class SampleDbAccessApp implements ApplicationRunner {
@Autowired
PosgresSampleService postgresSampleService;
@Autowired
MongoSampleService mongoSampleService;
public static void main(String[] args) {
SpringApplication.run(SampleDbAccessApp.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
PostgresSample postgre = new PostgresSample();
postgre.setName("John");
postgresSampleService.save(postgre);
List<PostgresSample> findResult1 = postgresSampleService.findAll();
findResult1.forEach(System.out::println);
MongoSample mongo = new MongoSample();
mongo.setName("Kate");
mongoSampleService.save(mongo);
List<MongoSample> findResult2 = mongoSampleService.findAll();
findResult2.forEach(System.out::println);
}
}
1点言うことがあるとすれば、マルチモジュールプロジェクトで、コンポーネントのScanする場所が変わっています。
この場合、multi-module-common
プロジェクト配下なので、@ComponentScan
で指定してあげる必要があります。
変数を使っていますが、これはAppConfig.javaにより定義して設定値から読み取っています。
使う側から見ると何の変哲もない感じで自然に扱えます。
参考
Spring bootでマルチデータソース対応の実装方法(MybatisとSpring Data JPA ) - Qiita
5.1. データベースアクセス(共通編) — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.0.1.RELEASE documentation
Multiple MongoDB connectors with Spring Boot