【Spring Boot】Doma2で複数データソースを使用する方法の紹介

今回は、「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サイトを確認してみてください。

Gradle - Plugin: org.springframework.boot

io.spring.dependency-management

「Spring Boot」を使用するアプリケーションを開発するために使用します。Mavenのような依存関係を管理するためのプラグインとして使用します。詳細については、次のWebサイトを確認してみてください。

Gradle - Plugin: io.spring.dependency-management

org.seasar.doma.compile

「Doma2」を使用するアプリケーションを開発するために使用します。アノテーションプロセッサでコンパイルを実施するために使用します。詳細については、次のWebサイトを確認してみてください。

Gradle - Plugin: org.seasar.doma.compile

プラグインを使用しない場合は、ビルドする時にリソースファイル(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」を指定しています。

実装方法

プログラム例で使用するプロジェクトの完成例は、次に登録しています。

GitHub - fumidzuki/example-springboot-doma2: 「SpringBoot」+「Doma2」の複数データソースを使用するときのプログラム例です。
「SpringBoot」+「Doma2」の複数データソースを使用するときのプログラム例です。. Contribute to fumidzuki/example-springboot-doma2 development by creating an account on GitHub.

データベース設定

データベースを設定します。各テーブルとスキーマを作成する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サイトを確認してみてください。

GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.
光 HikariCP・A solid, high-performance, JDBC connection pool at last. - GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection p...

アプリケーションクラス

アプリケーションクラスを作成します。プログラム例は、次のようになります。

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 classes — Doma documentation

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 interfaces — Doma documentation

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」ライブラリの公式サイト

Getting started — Doma documentation

プログラム例で使用するプロジェクトの完成例

https://github.com/fumidzuki/example-springboot-doma2