今回は、「Spring Boot
」+「Doma2
」の組み合わせで複数データソースを使用するときの方法を紹介していきます。
「Spring Boot
」では、単数データソースを使用するときのプログラム例を見かけることはありますが、複数データソースを使用した例をあまり見つけることができませんでした。本記事では、複数データソースのプログラム例などを含めた内容になっています。
検証環境
検証に使用した環境/ライブラリを次に記載します。
- Java
- バージョン:11
- Gradle
- バージョン:7.1.1
- Spring Boot
- バージョン:2.5.4
- PostgreSQL(データベースサーバー)
- バージョン:12.4
ビルドスクリプト
プログラム例で使用するビルドスクリプトは、次のようになります。
plugins {
id 'org.springframework.boot' version '2.5.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "org.seasar.doma.compile" version "1.1.0"
id 'java'
}
group = 'com.fumidzuki'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
// spring-boot
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// doma2
implementation 'org.seasar.doma:doma-core:2.47.1'
annotationProcessor 'org.seasar.doma:doma-processor:2.47.1'
// jdbc
runtimeOnly 'org.postgresql:postgresql'
}
plugins
org.springframework.boot
「Spring Boot」を使用するアプリケーションを開発するために使用します。詳細については、次のWebサイトを確認してみてください。
io.spring.dependency-management
「Spring Boot」を使用するアプリケーションを開発するために使用します。Mavenのような依存関係を管理するためのプラグインとして使用します。詳細については、次のWebサイトを確認してみてください。
org.seasar.doma.compile
「Doma2」を使用するアプリケーションを開発するために使用します。アノテーションプロセッサでコンパイルを実施するために使用します。詳細については、次のWebサイトを確認してみてください。
プラグインを使用しない場合は、ビルドする時にリソースファイル(SQLファイル)が参照できずにビルドエラーになります。
dependencies
springboot
「Spring Boot
」を使用するアプリケーションを開発するために使用します。
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
doma2
「Doma2
」を使用するアプリケーションを開発するために使用します。アノテーションプロセッサを使用するためのライブラリも指定しています。
implementation 'org.seasar.doma:doma-core:2.47.1'
annotationProcessor 'org.seasar.doma:doma-processor:2.47.1'
アノテーションプロセッサを指定しないと、実行するときに「Daoインタフェース」を実装したクラスが存在しないため、例外が発生します。
jdbc
データベースを使用するアプリケーションを開発するために使用します。接続するデータベースに合わせたライブラリを指定しています。
runtimeOnly 'org.postgresql:postgresql'
実行するときだけ必要なライブラリのため「runtimeOnly
」を指定しています。
実装方法
プログラム例で使用するプロジェクトの完成例は、次に登録しています。
データベース設定
データベースを設定します。各テーブルとスキーマを作成するSQLは、次のようになります。
sql/initial_one.sql
1つめのテーブルを作成するためのSQLは、次のようになります。
CREATE schema example_one;
CREATE TABLE example_one.account_one (
user_id int PRIMARY KEY,
username varchar ( 128 ) UNIQUE NOT NULL
);
INSERT INTO example_one.account_one values (1, 'username_one_01');
INSERT INTO example_one.account_one values (2, 'username_one_02');
INSERT INTO example_one.account_one values (3, 'username_one_03');
sql/initial_two.sql
2つめのテーブルを作成するためのSQLは、次のようになります。
CREATE schema example_two;
CREATE TABLE example_two.account_two (
user_id int PRIMARY KEY,
username varchar ( 128 ) UNIQUE NOT NULL
);
INSERT INTO example_two.account_two values (1, 'username_two_01');
INSERT INTO example_two.account_two values (2, 'username_two_02');
INSERT INTO example_two.account_two values (3, 'username_two_03');
設定ファイル
設定ファイルの作成例は、次のようになります。
application.properties
複数データベース接続をするための情報を設定しています。
# database.one
database.one.jdbcUrl=jdbc:postgresql://localhost:5432/postgres
database.one.username=postgres
database.one.password=
#database.two
database.two.jdbcUrl=jdbc:postgresql://localhost:5432/postgres
database.two.username=postgres
database.two.password=
データベースへの接続は「HikariCP
」を使用します。接続するURLは「jdbcUrl
」または「jdbc-url
」で設定してください。設定可能な値については、次のWebサイトを確認してみてください。
アプリケーションクラス
アプリケーションクラスを作成します。プログラム例は、次のようになります。
package com.fumidzuki;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
「@SpringBootApplication
」アノテーションには、「(exclude = {DataSourceAutoConfiguration.class})
」を指定します。「Spring Boot
」では、単一データソースを前提とする動作になるため、複数データソースを使用するため自動設定を無効にしています。
コンフィグクラス
複数データソースを取得するためのコンフィグクラスを作成します。
config/DatabaseOneConfig
プログラム例は、次のようになります。
package com.fumidzuki.config;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.PostgresDialect;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@Configuration("databaseOneConfig")
@ConfigurationProperties("database.one")
public class DatabaseOneConfig extends HikariConfig implements Config {
private DataSource dataSource;
@PostConstruct
public void postConstruct() {
this.dataSource = new TransactionAwareDataSourceProxy(new HikariDataSource(this));
}
@Override
public Dialect getDialect() {
return new PostgresDialect();
}
@Override
public DataSource getDataSource() {
return this.dataSource;
}
}
「@Configuration
」アノテーションは、「Doma2
」のDaoインタフェースで使用するため名前付きで指定します。他の名前と重複しないようにしてください。
「@ConfigurationProperties
」アノテーションは、設定ファイルを取得するために指定します。
「HikariConfig
」クラスを継承しているのは、設定ファイルから取得した情報を保持するために指定しています。
「Config
」インタフェースを実装しているのは、「Doma2
」を使用するために必須の実装になります。
「@PostConstruct
」アノテーションを指定したメソッドは、「Spring Boot
」の自動設定がすべて完了してから呼び出される処理になります。設定ファイルから取得した情報を使用して、データソースを作成する処理を実施しています。
config/DatabaseTwoConfig
プログラム例は、次のようになります。
package com.fumidzuki.config;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.PostgresDialect;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@Configuration("databaseTwoConfig")
@ConfigurationProperties("database.two")
public class DatabaseTwoConfig extends HikariConfig implements Config {
private DataSource dataSource;
@PostConstruct
public void postConstruct() {
this.dataSource = new TransactionAwareDataSourceProxy(new HikariDataSource(this));
}
@Override
public Dialect getDialect() {
return new PostgresDialect();
}
@Override
public DataSource getDataSource() {
return this.dataSource;
}
}
「@Configuration
」は、1つめのコンフィグクラスとは違う名前を指定します。それ以外は、ほとんど同じ処理をになります。
エンティティクラス
エンティティクラスを作成します。詳細については、次のWebサイトを確認してみてください。
entity/AccountOne
プログラム例は、次のようになります。
package com.fumidzuki.entity;
import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;
@Entity
@Table(schema = "example_one", name = "account_one")
public class AccountOne {
@Id
@Column(name = "user_id")
private Integer userId;
@Column(name = "username")
private String username;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
entity/AccountTwo
プログラム例は、次のようになります。
package com.fumidzuki.entity;
import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;
@Entity
@Table(schema = "example_two", name = "account_two")
public class AccountTwo {
@Id
@Column(name = "user_id")
private Integer userId;
@Column(name = "username")
private String username;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Daoクラス
データベースから情報を取得するためのDaoクラスを作成します。詳細については、次のWebサイトを確認してみてください。
dao/DatabaseOneDao
プログラム例は、次のようになります。
package com.fumidzuki.dao;
import java.util.List;
import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import com.fumidzuki.entity.AccountOne;
@Dao
@AnnotateWith(annotations = {@Annotation(target = AnnotationTarget.CLASS, type = Repository.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Autowired.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = Qualifier.class,
elements = "\"databaseOneConfig\"")})
public interface DatabaseOneDao {
@Select
public List<AccountOne> selectAll();
}
「@AnnotateWith
」アノテーションは、Daoインタフェースから実装クラスを作成するときに必要となる設定情報になります。コンフィグクラスの「@Configuration
」アノテーションで指定した名前を使用します。
META-INF/com/fumidzuki/dao/DatabaseOneDao/selectAll.sql
「Doma2
」は、メソッド単位でSQLを作成する必要があります。全件取得するためのSQL例は、次のようになります。
select * from example_one.account_one
dao/DatabaseTwoDao
プログラム例は、次のようになります。
package com.fumidzuki.dao;
import java.util.List;
import com.fumidzuki.entity.AccountTwo;
import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
@Dao
@AnnotateWith(annotations = {@Annotation(target = AnnotationTarget.CLASS, type = Repository.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Autowired.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = Qualifier.class,
elements = "\"databaseTwoConfig\"")})
public interface DatabaseTwoDao {
@Select
public List<AccountTwo> selectAll();
}
「DatabaseOneDao
」インタフェースとほとんど同じになります。「@AnnotateWith
」アノテーションに指定する名前を変更します。
META-INF/com/fumidzuki/dao/DatabaseTwoDao/selectAll.sql
「Doma2
」は、メソッド単位でSQLを作成する必要があります。全件取得するためのSQL例は、次のようになります。
select * from example_two.account_two
サービスクラス
コントローラークラスとDaoクラスの間の処理をするサービスクラスを作成します。プログラム例は、次のようになります。
package com.fumidzuki.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fumidzuki.dao.DatabaseOneDao;
import com.fumidzuki.dao.DatabaseTwoDao;
import com.fumidzuki.entity.AccountOne;
import com.fumidzuki.entity.AccountTwo;
@Service
public class AccountService {
@Autowired
DatabaseOneDao databaseOneDao;
@Autowired
DatabaseTwoDao databaseTwoDao;
public List<AccountOne> getAccountOne() {
return this.databaseOneDao.selectAll();
}
public List<AccountTwo> getAccountTwo() {
return this.databaseTwoDao.selectAll();
}
}
コントローラークラス
コントローラークラスのプログラム例は、次のようになります。
package com.fumidzuki.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fumidzuki.entity.AccountOne;
import com.fumidzuki.entity.AccountTwo;
import com.fumidzuki.service.AccountService;
@RestController
@RequestMapping("/example")
public class ExampleRestController {
@Autowired
AccountService accountService;
@GetMapping("one")
public List<AccountOne> one() {
return this.accountService.getAccountOne();
}
@GetMapping("two")
public List<AccountTwo> two() {
return this.accountService.getAccountTwo();
}
}
サービスクラスから取得した情報を返却する処理になります。URLごとの実行例は、次のようになります。
URL: /example/one
[{
"userId": 1,
"username": "username_one_01"
}, {
"userId": 2,
"username": "username_one_02"
}, {
"userId": 3,
"username": "username_one_03"
}]
URL: /example/two
[{
"userId": 1,
"username": "username_two_01"
}, {
"userId": 2,
"username": "username_two_02"
}, {
"userId": 3,
"username": "username_two_03"
}]
まとめ
「Spring Boot
」+「Doma2
」で複数データソースを使用するときの例を紹介しました。
参考資料
「Doma2
」ライブラリの公式サイト
プログラム例で使用するプロジェクトの完成例