はじめに
最近Kotlinアツいです!
どこでもKotlinが定期的に開催されています!私も参加しています!
Kotlinに興味のある方は是非!
業務でKotlinを使ったREST APIを開発した際に複数DB接続をするのに結構ハマったので備忘録です!!
この記事が今後みんなの役に立ったらうれしい!!!
参考にさせていただいた記事
Spring BootでJPAを使用した複数データベース接続
環境
Spring boot
Kotlin
JPA
MySQL
DB2
Maven
Docker
IntelliJ IDEA
macOS Sierra
環境構築
手順1 SPRING INITIALIZRでプロジェクトを作成
参考URL
SPRING INITIALIZR
まずは Group
と Artifact
を任意の値に変更して Generate Project
※1 Dependenciesを設定することも可能なんですが、万能ではないのでここでは設定していません。
※2 IntelliJを利用している人はプロジェクトを新規作成するときにSpring Initializrを選択できるようになっています。
参考 Spring InitializrをIntelliJで使う
手順2 ダウンロードされたプロジェクトを解凍
zipファイルが/ダウンロードに保存されているので任意のディレクトリで解凍します。
手順3 IntelliJ IDEAでプロジェクトをOpen
IntelliJ IDEAを使っていない人はお好みのエディタでOpen
手順4 pom.xmlを修正
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.ibm.db2.jcc</groupId>
<artifactId>db2jcc4</artifactId>
<version>10.1</version>
</dependency>
dependencies
に上記の dependecy
を追加します。
※ db2jcc4はjarをダウンロードする必要があります。
手順5 application.ymlを作成
私は プロジェクト/src/main/resources/config
に application.yml
を配置しました。
内容は以下
spring:
datasource:
primary:
url: jdbc:mysql://localhost/データベース名?useSSL=false
username: root
password:
driverClassName: com.mysql.jdbc.Driver
secondary:
url: jdbc:db2://192.168.99.100:50000/データベース名
username: db2inst1
password: db2inst1
driverClassName: com.ibm.db2.jcc.DB2Driver
jpa:
hibernate:
ddl-auto: none
※ データベース名はご自身の環境に置き換えてください。
手順6 データベースを作成する
MySQLは割愛します。
参考 【MySQL, SQL】データベースを扱う基本SQL一覧
DB2はDockerに環境を構築します!
実装
ディレクトリ構成
Entity
まずはEntityです。
ポイントはデータベースごとにパッケージを分けることです。
パッケージに分けることで、Configurationの設定をシンプルにできます。
package com.example.multipledatabasesdemo.domain.model.primary
import javax.persistence.*
@Entity
class User (
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column
var id: Int = 0,
@Column
var name: String = ""
)
package com.example.multipledatabasesdemo.domain.model.secondary
import javax.persistence.*
@Entity(name = "external_user_info")
class ExternalUserInfo (
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column
var id: Int = 0,
@Column
var status: String = ""
)
Repository
Repositoryのポイントも同じくデータベースごとにパッケージを分けることです。
package com.example.multipledatabasesdemo.domain.repository.primary
import com.example.multipledatabasesdemo.domain.model.primary.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface UserRepository: JpaRepository<User, Int>
package com.example.multipledatabasesdemo.domain.repository.secondary
import com.example.multipledatabasesdemo.domain.model.secondary.ExternalUserInfo
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface ExternalUserInfoRepository: JpaRepository<ExternalUserInfo, Int>
Service
正直今回の処理ではいらないんですが、お作法に従います。
package com.example.multipledatabasesdemo.domain.service.primary
import com.example.multipledatabasesdemo.domain.model.primary.User
import com.example.multipledatabasesdemo.domain.repository.primary.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class UserService @Autowired constructor(private val userRepository: UserRepository) {
fun findById(id: Int): User {
return userRepository.findOne(id)
}
}
package com.example.multipledatabasesdemo.domain.service.secondary
import com.example.multipledatabasesdemo.domain.model.secondary.ExternalUserInfo
import com.example.multipledatabasesdemo.domain.repository.secondary.ExternalUserInfoRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class ExternalUserInfoService @Autowired constructor(private val externalUserInfoRepository: ExternalUserInfoRepository) {
fun findById(id: Int): ExternalUserInfo {
return externalUserInfoRepository.findOne(id)
}
}
Configuration
package com.example.multipledatabasesdemo.domain.configuration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.orm.jpa.JpaTransactionManager
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableJpaRepositories(
basePackages = arrayOf("com.example.multipledatabasesdemo.domain.repository.primary"),
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "primaryTransactionManager")
class PrimaryDataSourceConfiguration {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
fun primaryProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@Primary
@Autowired
fun primaryDataSource(@Qualifier("primaryProperties") properties: DataSourceProperties): DataSource {
return properties.initializeDataSourceBuilder().build()
}
@Bean
@Primary
@Autowired
fun primaryEntityManager(builder: EntityManagerFactoryBuilder, @Qualifier("primaryDataSource") dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
return builder.dataSource(dataSource)
.packages("com.example.multipledatabasesdemo.domain.model.primary")
.persistenceUnit("primary")
.build()
}
@Bean
@Primary
@Autowired
fun primaryTransactionManager(@Qualifier("primaryEntityManager") primaryEntityManager: EntityManagerFactory): JpaTransactionManager {
return JpaTransactionManager(primaryEntityManager)
}
}
package com.example.multipledatabasesdemo.domain.configuration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.orm.jpa.JpaTransactionManager
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableJpaRepositories(
basePackages = arrayOf("com.example.multipledatabasesdemo.domain.repository.secondary"),
entityManagerFactoryRef = "secondaryEntityManager",
transactionManagerRef = "secondaryTransactionManager")
class SecondaryDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
fun secondaryProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean
@Autowired
fun secondaryDataSource(@Qualifier("secondaryProperties") properties: DataSourceProperties): DataSource {
return properties.initializeDataSourceBuilder().build()
}
@Bean
@Autowired
fun secondaryEntityManager(builder: EntityManagerFactoryBuilder, @Qualifier("secondaryDataSource") dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
return builder.dataSource(dataSource)
.packages("com.example.multipledatabasesdemo.domain.model.secondary")
.persistenceUnit("secondary")
.build()
}
@Bean
@Autowired
fun secondaryTransactionManager(@Qualifier("secondaryEntityManager") secondaryEntityManager: EntityManagerFactory): JpaTransactionManager {
return JpaTransactionManager(secondaryEntityManager)
}
}
ちょこっとソース解説
はじめにで参考にした記事を元に記述しています。
DataSourcePropertiesの設定
DataSourcePropertiesはデフォルトでBean登録されており、デフォルトのDataSourcePropertiesを使用しないようにするために、必ずprimaryDB側に @primary
を付与する必要があります。
@primary
がないと、複数のBeanが登録されているためにエラーとなってしまいます。
@ConfigurationProperties
は application.yml
の以下の部分の設定を読み取ります。
spring:
datasource:
*:
以下の設定
DataSourceの設定
それぞれのDBに対するDataSourceを定義します。
同じくprimaryDB側に @primary
をつけないとエラーになります。
@Autowired
をつけて、引数に渡したDataSourcePropertiesをインジェクション(注入)しています。
対象クラスのBeanが複数あるので @Qualifier
で明示しています。
LocalContainerEntityManagerFactoryBeanの設定
EntityManagerを生成するためのクラスになります。
EntityManagerFactoryBuilderはSpring Bootが提供しているBeanをそのままインジェクションします。
.packages(...)で対象となるEntityが格納されたパッケージを指定することで複数データベースに対応しています。この引数はクラスの配列も受け取れるので、パッケージを分けずに、Entityのクラスを直接指定することもできます。
.persistenceUnit(...)は永続性ユニットの名前で、複数データベースを定義するときは永続性ユニットを区別するために必須になります。
JpaTransactionManagerの設定
EntityManagerが2つあるため、それぞれのTransactionManagerを定義します。
JpaTransactionManagerを生成して、EntityManagerFactoryを設定すればOKです。
クラスの設定
対象のJpaRepositoryパッケージ、EntityManagerFactory,TransactionManagerがわかるように、@EnableJpaRepositories
で指定します。
Controller
package com.example.multipledatabasesdemo.controller
import com.example.multipledatabasesdemo.domain.model.primary.User
import com.example.multipledatabasesdemo.domain.model.secondary.ExternalUserInfo
import com.example.multipledatabasesdemo.domain.service.primary.UserService
import com.example.multipledatabasesdemo.domain.service.secondary.ExternalUserInfoService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/")
class MultipleDatabasesDemoController @Autowired constructor(private val userService: UserService, private val externalUserInfoService: ExternalUserInfoService) {
@GetMapping("/user")
fun getUser(@RequestParam(value = "id") id: Int): User = userService.findById(id)
@GetMapping("/external-user-info")
fun getExternalUserInfo(@RequestParam(value = "id") id: Int): ExternalUserInfo = externalUserInfoService.findById(id)
}
ちょこっとソース解説
@RestController
はJsonやXML等を返すWebAPI用のコントローラで使用する。
@RequestMapping
は、クラスとメソッドの両方に使用可能です。
クラスに使用した場合は、親パスに一致させることができます。
@Autowired
を指定することによって、com.example.multipledatabasesdemo.domain.service
に作成したコンポーネントをインジェクション(注入)します。
@RequestMapping
のGETリクエスト用のアノテーションが @GetMapping
です。
検証
プロジェクトのディレクトリに移動して mvn spring-boot:run
を実行します!
サーバーが起動したらREST APIテストツールをなどを利用してテストします!
MySQLにアクセス
DB2にアクセス
ちゃんと接続できています!めでたし!
サンプルコード
GitHubに公開しておりますので是非参考に!
https://github.com/manzen/multiple-databases-demo