Spring boot2 + JPA + Kotlinで複数のデータベースに接続する

# はじめに
実際のプロジェクトでは複数のデータベースに繋ぐことが多いと思うので、JPA使って複数のデータベースに繋ぐのをKotlinでやってみました。

# 環境
Kotlin
Spring-boot 2.0.1.RELEASE

# Properties
プロパティはデフォルトの値ではなく独自の値をマッピングするので、それを繋ぎたいだけ用意します。
今回は面倒というのもあり、ローカルに立てたpostgresに2つDatabaseを立ててそれに繋ぎたいと思います。

構成は
testdb下にtestスキーマを作成してcorpテーブル
postgres下にtestスキーマを作成してuserテーブル
としました。

``` application.properties
spring.datasource.testdb.driver-class-name=org.postgresql.Driver
spring.datasource.testdb.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.testdb.username=postgres
spring.datasource.testdb.password=admin

spring.datasource.postgres.driver-class-name=org.postgresql.Driver
spring.datasource.postgres.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.postgres.username=postgres
spring.datasource.postgres.password=admin
```
# Contoroller
結果を見たいので、今回のDBを作ったテーブルからfindAllした結果を返すControllerを作成しておきます。

``` kotlin
package com.tasogarei.databasetest.contorller;

import com.tasogarei.databasetest.entity.testdb.CorpEntity
import com.tasogarei.databasetest.entity.postgres.UserEntity;
import com.tasogarei.databasetest.repository.testdb.CorpRepository
import com.tasogarei.databasetest.repository.postgres.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class TestController {

    @Autowired
    lateinit var userRepository : UserRepository

    @Autowired
    lateinit var corpRepository: CorpRepository

    @ResponseBody
    @GetMapping("/postgres")
    fun getMapping() : ResponseEntity<List<UserEntity>> {
        return ResponseEntity.ok().body(userRepository.findAll())
    }

    @ResponseBody
    @GetMapping("/test")
    fun postgresMapping() : ResponseEntity<List<CorpEntity>> {
        return ResponseEntity.ok().body(corpRepository.findAll())
    }
}
```

# Entity Repository
JPA接続で使用するEntityとRepositoryを作成します。
設定時にパッケージを指定するので、データベース毎に分けておいた方が無難です。

``` kolin
package com.tasogarei.databasetest.entity.postgres

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table

@Table(name = "user", schema = "test")
@Entity
class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    var id: Int = 0

    @Column
    var name: String = ""
}
```

``` kolin
package com.tasogarei.databasetest.entity.testdb

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table

@Table(name = "corp", schema = "test")
@Entity
class CorpEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column
    var id: Int = 0

    @Column
    var name: String = ""
}
```

``` kotlin
package com.tasogarei.databasetest.repository.postgres

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

import com.tasogarei.databasetest.entity.postgres.UserEntity

@Repository
interface UserRepository : JpaRepository<UserEntity, Int>
```

```
package com.tasogarei.databasetest.repository.testdb

import com.tasogarei.databasetest.entity.testdb.CorpEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface CorpRepository : JpaRepository<CorpEntity, Int>
```

# データベースの接続
実際のデータベースの接続はBeanで行うので、Beanでやってあげます。
やっていることはコンフィグから値読み込んでDataSourceとEntityManagerFactory作成してあげているだけです。
両方とも1つで作成する際のBeanと同じような感じです。
ただし、Beanの名前を分けてあげる必要があるのと片方に`@Primary`を付けてあげるのだけ忘れないであげてください。

あと、TransactionManager作っておかないといけない気がしたので、一緒に作っています。
データベースを読み込むだけなら特に必要はなかったですが、ないとおかしいと感じたので作りました。
動くのは確認済みです。

`@EnableJpaRepositories`をきちんと設定しないと動かないので注意してください。

``` kotlin
package com.tasogarei.databasetest.config

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
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 org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import org.springframework.stereotype.Component
import org.springframework.transaction.PlatformTransactionManager
import java.util.*
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource

@Component
@ConfigurationProperties(prefix = "spring.datasource.postgres")
@EnableJpaRepositories(
        basePackages=["com.tasogarei.databasetest.repository.postgres"],
        entityManagerFactoryRef="postgresEntityManagerFactory",
        transactionManagerRef="postgresTransactionManager"
)
class PostgresDataSourceConfig {
    lateinit var driverClassName: String
    lateinit var url: String
    lateinit var username: String
    lateinit var password: String

    @Bean("postgresDataSource")
    @Primary
    fun dataSource() : DataSource {
        return DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).build()
    }

    @Bean("postgresEntityManagerFactory")
    @Primary
    fun entityManagerFactory(@Qualifier("postgresDataSource") dataSource: DataSource): EntityManagerFactory {
        val properties = Properties()
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect")

        val factory = LocalContainerEntityManagerFactoryBean()
        factory.dataSource = dataSource
        factory.setPackagesToScan("com.tasogarei.databasetest.entity.postgres")
        factory.jpaVendorAdapter = HibernateJpaVendorAdapter()
        factory.setJpaProperties(properties)
        factory.afterPropertiesSet()
        return factory.nativeEntityManagerFactory
    }

    @Bean("postgresTransactionManager")
    @Primary
    fun transactionManager(@Qualifier("postgresDataSource") dataSource: DataSource,
                           @Qualifier("postgresEntityManagerFactory") entityManagerFactory: EntityManagerFactory): PlatformTransactionManager {
        val jpaTransactionManager = JpaTransactionManager()
        jpaTransactionManager.dataSource = dataSource
        jpaTransactionManager.entityManagerFactory = entityManagerFactory
        return jpaTransactionManager
    }
}
```

``` kotlin
package com.tasogarei.databasetest.config

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import org.springframework.stereotype.Component
import java.util.*
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
import org.springframework.orm.jpa.JpaTransactionManager
import org.springframework.transaction.PlatformTransactionManager



@Component
@ConfigurationProperties(prefix = "spring.datasource.testdb")
@EnableJpaRepositories(
        basePackages=["com.tasogarei.databasetest.repository.testdb"],
        entityManagerFactoryRef="testdbEntityManagerFactory",
        transactionManagerRef="testdbTransactionManager"
)
class TestDbDataSourceConfig {
    lateinit var driverClassName: String
    lateinit var url: String
    lateinit var username: String
    lateinit var password: String

    @Bean("testdbDataSource")
    fun dataSource() : DataSource {
        return DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).build()
    }

    @Bean("testdbEntityManagerFactory")
    fun entityManagerFactory(@Qualifier("testdbDataSource") dataSource: DataSource): EntityManagerFactory {
        val properties = Properties()
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect")

        val factory = LocalContainerEntityManagerFactoryBean()
        factory.dataSource = dataSource
        factory.setPackagesToScan("com.tasogarei.databasetest.entity.testdb")
        factory.jpaVendorAdapter = HibernateJpaVendorAdapter()
        factory.setJpaProperties(properties)
        factory.afterPropertiesSet()
        return factory.nativeEntityManagerFactory
    }

    @Bean("testdbTransactionManager")
    fun transactionManager(@Qualifier("testdbDataSource") dataSource: DataSource,
                           @Qualifier("testdbEntityManagerFactory") entityManagerFactory: EntityManagerFactory): PlatformTransactionManager {
        val jpaTransactionManager = JpaTransactionManager()
        jpaTransactionManager.dataSource = dataSource
        jpaTransactionManager.entityManagerFactory = entityManagerFactory
        return jpaTransactionManager
    }
}
```