하나의 Server에서 하나의 DB만 사용하면 너무 편하겠지만 예외적인 상황들이 있을 수 있다
중앙에서 관리하는 Server(Route역할)가 앞에서 filter를 거쳐, 뒤에 이어진 다양한 Server들을 이어주는 경우가 아니라면 여러개의 DataSource를 설정하는 고민을 해 보았을 것 이다
만들어 볼 프로젝트
Home IoT를 관리하는 서버를 만들자
도메인은 IoTLog, Device, Users
서로 매핑되면 좋은 프로젝트가 되겠지만, 엔티티의 매핑보다 DataSource를 다양하게 설정하는데에 주목하자
개발언어 및 사용스택
Kotlin
SpringBoot
MariaDB
PostgreSQL
Mongodb
프로젝트 초기 설정
spring-boot-starter-web
spring-boot-starter-data-jpa
spring-boot-starter-data-mongodb
postgresql
mariadb
lombok
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id ( "org.springframework.boot" ) version "2.7.3"
id ( "io.spring.dependency-management" ) version "1.0.13.RELEASE"
kotlin ( "jvm" ) version "1.6.21"
kotlin ( "plugin.spring" ) version "1.6.21"
kotlin ( "plugin.jpa" ) version "1.6.21"
}
group = "wool"
version = "0.0.1-SNAPSHOT"
java . sourceCompatibility = JavaVersion . VERSION_17
configurations {
compileOnly {
extendsFrom ( configurations . annotationProcessor . get ())
}
}
repositories {
mavenCentral ()
}
dependencies {
implementation ( "org.springframework.boot:spring-boot-starter-data-jpa" )
implementation ( "org.springframework.boot:spring-boot-starter-web" )
implementation ( "com.fasterxml.jackson.module:jackson-module-kotlin" )
implementation ( "org.jetbrains.kotlin:kotlin-reflect" )
implementation ( "org.jetbrains.kotlin:kotlin-stdlib-jdk8" )
implementation ( "org.springframework.boot:spring-boot-starter-data-mongodb" )
compileOnly ( "org.projectlombok:lombok" )
runtimeOnly ( "org.mariadb.jdbc:mariadb-java-client" )
runtimeOnly ( "org.postgresql:postgresql" )
annotationProcessor ( "org.projectlombok:lombok" )
testImplementation ( "org.springframework.boot:spring-boot-starter-test" )
}
tasks . withType < KotlinCompile > {
kotlinOptions {
freeCompilerArgs = listOf ( "-Xjsr305=strict" )
jvmTarget = "17"
}
}
tasks . withType < Test > {
useJUnitPlatform ()
}
dependency를 설정한 gradle파일이다
Configuration 조작하기 - Bean으로 직접 설정하기
application.properties는 설정을 자동으로 해 주기 때문에 기본적인 설정을 입력하면 자동으로 세팅을 완료 해준다
우리가 만들려고 하는 서버에서는 기본적인 설정 외에 추가적인 설정을 필요로 하기때문에 Bean으로 직접 생성 해 주려고 한다
application.properties
# Maria DB - "db1"
users-data.datasource.url=jdbc:mariadb://localhost:3306/paul?characterEncoding=UTF-8&serverTimezone=UTC
users-data.datasource.username=paul
users-data.datasource.password=qwerqwer123
users-data.datasource.driver-class-name=org.mariadb.jdbc.Driver
users-data.datasource.hikari.connection-test-query=SELECT 1
# PostgreSQL DB - "db2"
device-data.datasource.url=jdbc:postgresql://localhost:5432/paul
device-data.datasource.username=paul
device-data.datasource.password=qwerqwer123
device-data.datasource.driver-class-name=org.postgresql.Driver
device-data.datasource.hikari.connection-test-query=SELECT 1
# MongoDB
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.username=paul
spring.data.mongodb.password=qwerqwer123
spring.data.mongodb.database=paul
spring.data.mongodb.authentication-database=admin
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
# logging
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
logging.level.org.hibernate.SQL=debug
server.port=8000
Maria DB, PostgreSQL DB, Mongo DB 를 따로 설정 해 주기 위해 따로 설정값들을 나열했다
Postgresql은 사용자 데이터(Users), Maria DB은 Device 관리 테이블(Device), Mongo DB는 Device가 사용된 기록을 raw로 남긴다
사실 우리가 만들 코드에서는 MongoDB 설정값이 크게 필요 없다.
이부분은 차후에 properties 파일에서 설정값을 읽어오는 방향으로 코드를 수정하면 된다
Configuration Bean 생성하기 1 - Postgresql DB 세팅
config 패키기를 생성하고, 하위에 PostgresqlConfing 파일을 생성한다
package wool.multidb.config
import com.zaxxer.hikari.HikariDataSource
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 org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
entityManagerFactoryRef = "deviceEntityManagerFactory" ,
basePackages = [ "wool.multidb.device.repository" ]
)
class PostgresqlConfig {
@Primary
@Bean ( name = [ "deviceDataSourceProperties" ])
@ConfigurationProperties ( "device-data.datasource" )
fun deviceDataSourceProperties (): DataSourceProperties {
return DataSourceProperties ()
}
@Primary
@Bean ( name = [ "deviceDataSource" ])
@ConfigurationProperties ( "device-data.datasource.configuration" )
fun dataSource ( @Qualifier ( "deviceDataSourceProperties" ) deviceDataSourceProperties : DataSourceProperties ): DataSource {
return deviceDataSourceProperties . initializeDataSourceBuilder (). type ( HikariDataSource :: class . java ). build ()
}
@Primary
@Bean ( name = [ "deviceEntityManagerFactory" ])
fun entityManagerFactory (
builder : EntityManagerFactoryBuilder ,
@Qualifier ( "deviceDataSource" ) deviceDataSource : DataSource
): LocalContainerEntityManagerFactoryBean {
return builder
. dataSource ( deviceDataSource )
. packages ( "wool.multidb.device.domain" )
. persistenceUnit ( "device" )
. build ()
}
@Primary
@Bean ( name = [ "deviceTransactionManager" ])
fun transactionManager (
@Qualifier ( "deviceEntityManagerFactory" ) deviceEntityManagerFactory : EntityManagerFactory
): PlatformTransactionManager {
return JpaTransactionManager ( deviceEntityManagerFactory )
}
}
Configuration Bean 생성하기 2 - Maria DB 세팅
config 패키기를 생성하고, 하위에 MariaConfig 파일을 생성한다
package wool.multidb.config
import com.zaxxer.hikari.HikariDataSource
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 org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
entityManagerFactoryRef = "entityManagerFactory" ,
basePackages = [ "wool.multidb.users.repository" ]
)
class MariaConfig {
@Bean ( name = [ "dataSourceProperties" ])
@ConfigurationProperties ( "users-data.datasource" )
fun usersDataSourceProperties (): DataSourceProperties {
return DataSourceProperties ()
}
@Bean ( name = [ "dataSource" ])
@ConfigurationProperties ( "users-data.datasource.configuration" )
fun dataSource ( @Qualifier ( "dataSourceProperties" ) usersDataSourceProperties : DataSourceProperties ): DataSource {
return usersDataSourceProperties . initializeDataSourceBuilder (). type ( HikariDataSource :: class . java )
. build ()
}
@Bean ( name = [ "entityManagerFactory" ])
fun entityManagerFactory (
builder : EntityManagerFactoryBuilder , @Qualifier ( "dataSource" ) dataSource : DataSource
): LocalContainerEntityManagerFactoryBean {
return builder
. dataSource ( dataSource )
. packages ( "wool.multidb.users.domain" )
. persistenceUnit ( "users" )
. build ()
}
@Bean ( name = [ "transactionManager" ])
fun transactionManager (
@Qualifier ( "entityManagerFactory" ) usersEntityManagerFactory : EntityManagerFactory
): PlatformTransactionManager {
return JpaTransactionManager ( usersEntityManagerFactory )
}
}
Configuration Bean 생성하기 3 - Mongo DB 세팅
config 패키기를 생성하고, 하위에 MongoConfig 파일을 생성한다
package wool.multidb.config
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
@Configuration
@EnableMongoRepositories ( basePackages = [ "wool.multidb.iotlog.repository" ])
class MongoConfig : AbstractMongoClientConfiguration () {
override fun getDatabaseName (): String {
return "iotlog"
}
override fun mongoClient (): MongoClient {
val connectionString = ConnectionString ( "mongodb://paul:qwerqwer123@localhost:27017/paul?authSource=admin" )
val mongoClientSettings = MongoClientSettings
. builder ()
. applyConnectionString ( connectionString )
. build ()
return MongoClients . create ( mongoClientSettings )
}
}
여기에서 사용한 ConnectionString에서는 user,password, admin DB 정보가 모두 들어가있다.
차후에 고도화과정에서 해당 내용들을 properties로 빼거나, kube의 secret를 넣으면 된다
Users 패키지 개발 - Controller, Service, Repository, Domain
Domain - UsersEntity
package wool.multidb.users.domain
import lombok.Data
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table
@Entity
@Data
@Table ( name = "users" )
data class UsersEntity (
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
val id : Int ? = null ,
val userName : String ,
val email : String ,
)
repository - UsersEntityRepository
package wool.multidb.users.repository
import org.springframework.data.jpa.repository.JpaRepository
import wool.multidb.users.domain.UsersEntity
interface UsersEntityRepository : JpaRepository < UsersEntity , Int > {
}
package wool.multidb.users.service
import org.springframework.stereotype.Service
import wool.multidb.users.domain.UsersEntity
import wool.multidb.users.repository.UsersEntityRepository
@Service
class UsersService (
private val usersEntityRepository : UsersEntityRepository
) {
fun getUsers (): List < UsersEntity > = usersEntityRepository . findAll ()
}
controller - UsersController
package wool.multidb.users.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import wool.multidb.users.domain.UsersEntity
import wool.multidb.users.service.UsersService
@RestController
class UsersController (
private val usersService : UsersService
) {
@GetMapping ( "/users" )
fun getAllUsers (): List < UsersEntity > {
return usersService . getUsers ()
}
}
Device 패키지 개발 - Controller, Service, Repository, Domain
Domain - DeviceEntity
package wool.multidb.device.domain
import lombok.Data
import javax.persistence.*
@Entity
@Data
@Table ( name = "device" )
data class DeviceEntity (
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
var id : Int ? = null ,
var deviceName : String ? = null ,
var deviceType : String ? = null
)
repository - DeviceEntityRepository
package wool.multidb.device.repository
import org.springframework.data.jpa.repository.JpaRepository
import wool.multidb.device.domain.DeviceEntity
interface DeviceEntityRepository : JpaRepository < DeviceEntity , Int > {
}
service - DeviceService
package wool.multidb.device.service
import org.springframework.stereotype.Service
import wool.multidb.device.domain.DeviceEntity
import wool.multidb.device.repository.DeviceEntityRepository
@Service
class DeviceService (
private val deviceEntityRepository : DeviceEntityRepository
) {
fun getAllDevices (): MutableList < DeviceEntity > {
return deviceEntityRepository . findAll ()
}
}
controller - DeviceController
package wool.multidb.device.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import wool.multidb.device.domain.DeviceEntity
import wool.multidb.device.service.DeviceService
@RestController
class DeviceController (
val deviceService : DeviceService
) {
@GetMapping ( "/devices" )
fun getAllDevices (): MutableList < DeviceEntity > {
return deviceService . getAllDevices ()
}
}
IoTLog 패키지 개발 - Controller, Service, Repository, Domain
MongoDB 세팅이기떄문에 기존의 Repository의 JPARepository를 상속받기보다 MongoTemplate을 상속받을 것
IoT Device 중, 전구(Light) 데이터를 저장 해 보려고 한다
Domain - Light
package wool.multidb.iotlog.domain
import org.springframework.data.mongodb.core.mapping.Document
import javax.persistence.Entity
import javax.persistence.Id
@Document ( collection = "iotLog" )
data class Light (
@Id
var id : String ? = null ,
var status : Boolean = false
)
repository - IoTRepository
package wool.multidb.iotlog.repository
import org.springframework.data.mongodb.repository.MongoRepository
import wool.multidb.iotlog.domain.Light
interface IoTRepository : MongoRepository < Light , String >{}
service - IoTService
package wool.multidb.iotlog.service
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.stereotype.Service
import wool.multidb.iotlog.domain.Light
import wool.multidb.iotlog.repository.IoTRepository
@Service
class IoTService (
val iotRepository : IoTRepository ,
val mongoTemplate : MongoTemplate
) {
fun getAllLight (): List < Light > {
return iotRepository . findAll ()
}
fun putLightLog ( light : Light ) {
mongoTemplate . save ( light )
}
}
controller - IoTController
package wool.multidb.iotlog.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import wool.multidb.iotlog.domain.Light
import wool.multidb.iotlog.service.IoTService
@RestController
class IoTController (
private val ioTService : IoTService
) {
@GetMapping ( "/light" )
fun getAllLight (): List < Light > {
return ioTService . getAllLight ()
}
@PostMapping ( "/light" )
fun addLightLog ( @RequestBody light : Light ) {
return ioTService . putLightLog ( light )
}
}
몽고디비 or Document DB config 보안관련
각 회사나 일부 프로젝트에서는 CA 파일을 설정하여 연결을 요구할 수 있다 그때에는 아래와 같은 코드로 SSLConfig 를 추가하면 된다
override fun getDatabaseName (): String {
return "DB이름"
}
override fun mongoClient (): MongoClient {
val trustStorePath = "./auth"
val keyStore : KeyStore = KeyStore . getInstance ( KeyStore . getDefaultType ())
keyStore . load ( null )
val certF : CertificateFactory = CertificateFactory . getInstance ( "X.509" )
val cert : Certificate = certF . generateCertificate ( URL ( "https://s3.amazonaws.com/rds-downloads/rds-ca-2019-root.pem" ). openStream ())
keyStore . setCertificateEntry ( "mongo-cert" , cert )
FileOutputStream ( trustStorePath ). use { out -> keyStore . store ( out , "pass" . toCharArray ()) }
val trustManagerFactory : TrustManagerFactory = TrustManagerFactory . getInstance ( TrustManagerFactory . getDefaultAlgorithm ())
trustManagerFactory . init ( keyStore )
val sslContext = SSLContext . getInstance ( "TLS" )
sslContext . init ( null , trustManagerFactory . getTrustManagers (), SecureRandom ())
val builder = MongoClientSettings . builder ()
. applyConnectionString ( ConnectionString ( "mongodb-url" ))
. applyToSslSettings { b : SslSettings . Builder ->
b . enabled ( true )
b . context ( sslContext )
}
. build ()
return MongoClients . create ( builder )
}
확인하기
스프링부트 서버를 실행시키고, 잘 동작하는지 확인한다
application properties에 작성했었던 Configuration 값들을 config 패키지를 만들어 내부로 이동시켰다
controller에서 정했던 url로 각각 호출 해 보면 잘 되는 것을 볼 수 있다
It would be very convenient if a single server only needed one database, but there can be exceptional situations
Unless you have a centrally managed server (acting as a router) that filters requests and connects various backend servers, you’ve probably considered configuring multiple DataSources
Project We’ll Build
Let’s create a server that manages Home IoT
The domains are IoTLog, Device, and Users
While mapping them together would make a great project, let’s focus on configuring multiple DataSources rather than entity mapping
Development Language and Tech Stack
Kotlin
SpringBoot
MariaDB
PostgreSQL
Mongodb
Initial Project Setup
spring-boot-starter-web
spring-boot-starter-data-jpa
spring-boot-starter-data-mongodb
postgresql
mariadb
lombok
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id ( "org.springframework.boot" ) version "2.7.3"
id ( "io.spring.dependency-management" ) version "1.0.13.RELEASE"
kotlin ( "jvm" ) version "1.6.21"
kotlin ( "plugin.spring" ) version "1.6.21"
kotlin ( "plugin.jpa" ) version "1.6.21"
}
group = "wool"
version = "0.0.1-SNAPSHOT"
java . sourceCompatibility = JavaVersion . VERSION_17
configurations {
compileOnly {
extendsFrom ( configurations . annotationProcessor . get ())
}
}
repositories {
mavenCentral ()
}
dependencies {
implementation ( "org.springframework.boot:spring-boot-starter-data-jpa" )
implementation ( "org.springframework.boot:spring-boot-starter-web" )
implementation ( "com.fasterxml.jackson.module:jackson-module-kotlin" )
implementation ( "org.jetbrains.kotlin:kotlin-reflect" )
implementation ( "org.jetbrains.kotlin:kotlin-stdlib-jdk8" )
implementation ( "org.springframework.boot:spring-boot-starter-data-mongodb" )
compileOnly ( "org.projectlombok:lombok" )
runtimeOnly ( "org.mariadb.jdbc:mariadb-java-client" )
runtimeOnly ( "org.postgresql:postgresql" )
annotationProcessor ( "org.projectlombok:lombok" )
testImplementation ( "org.springframework.boot:spring-boot-starter-test" )
}
tasks . withType < KotlinCompile > {
kotlinOptions {
freeCompilerArgs = listOf ( "-Xjsr305=strict" )
jvmTarget = "17"
}
}
tasks . withType < Test > {
useJUnitPlatform ()
}
This is the gradle file with dependencies configured
Manipulating Configuration - Direct Bean Configuration
application.properties handles configuration automatically, so entering basic settings completes the setup automatically
Since the server we’re building requires additional settings beyond the basics, we’ll create Beans directly
application.properties
# Maria DB - "db1"
users-data.datasource.url=jdbc:mariadb://localhost:3306/paul?characterEncoding=UTF-8&serverTimezone=UTC
users-data.datasource.username=paul
users-data.datasource.password=qwerqwer123
users-data.datasource.driver-class-name=org.mariadb.jdbc.Driver
users-data.datasource.hikari.connection-test-query=SELECT 1
# PostgreSQL DB - "db2"
device-data.datasource.url=jdbc:postgresql://localhost:5432/paul
device-data.datasource.username=paul
device-data.datasource.password=qwerqwer123
device-data.datasource.driver-class-name=org.postgresql.Driver
device-data.datasource.hikari.connection-test-query=SELECT 1
# MongoDB
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.username=paul
spring.data.mongodb.password=qwerqwer123
spring.data.mongodb.database=paul
spring.data.mongodb.authentication-database=admin
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
# logging
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
logging.level.org.hibernate.SQL=debug
server.port=8000
I’ve listed separate configuration values for Maria DB, PostgreSQL DB, and Mongo DB
PostgreSQL stores user data (Users), Maria DB manages the Device table (Device), and Mongo DB stores raw Device usage logs
Actually, our code doesn’t need much of the MongoDB configuration values
This can be modified later to read configuration values from the properties file
Creating Configuration Bean 1 - PostgreSQL DB Setup
Create a config package and create a PostgresqlConfig file under it
package wool.multidb.config
import com.zaxxer.hikari.HikariDataSource
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 org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
entityManagerFactoryRef = "deviceEntityManagerFactory" ,
basePackages = [ "wool.multidb.device.repository" ]
)
class PostgresqlConfig {
@Primary
@Bean ( name = [ "deviceDataSourceProperties" ])
@ConfigurationProperties ( "device-data.datasource" )
fun deviceDataSourceProperties (): DataSourceProperties {
return DataSourceProperties ()
}
@Primary
@Bean ( name = [ "deviceDataSource" ])
@ConfigurationProperties ( "device-data.datasource.configuration" )
fun dataSource ( @Qualifier ( "deviceDataSourceProperties" ) deviceDataSourceProperties : DataSourceProperties ): DataSource {
return deviceDataSourceProperties . initializeDataSourceBuilder (). type ( HikariDataSource :: class . java ). build ()
}
@Primary
@Bean ( name = [ "deviceEntityManagerFactory" ])
fun entityManagerFactory (
builder : EntityManagerFactoryBuilder ,
@Qualifier ( "deviceDataSource" ) deviceDataSource : DataSource
): LocalContainerEntityManagerFactoryBean {
return builder
. dataSource ( deviceDataSource )
. packages ( "wool.multidb.device.domain" )
. persistenceUnit ( "device" )
. build ()
}
@Primary
@Bean ( name = [ "deviceTransactionManager" ])
fun transactionManager (
@Qualifier ( "deviceEntityManagerFactory" ) deviceEntityManagerFactory : EntityManagerFactory
): PlatformTransactionManager {
return JpaTransactionManager ( deviceEntityManagerFactory )
}
}
Creating Configuration Bean 2 - Maria DB Setup
Create a config package and create a MariaConfig file under it
package wool.multidb.config
import com.zaxxer.hikari.HikariDataSource
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 org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
import javax.persistence.EntityManagerFactory
import javax.sql.DataSource
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
entityManagerFactoryRef = "entityManagerFactory" ,
basePackages = [ "wool.multidb.users.repository" ]
)
class MariaConfig {
@Bean ( name = [ "dataSourceProperties" ])
@ConfigurationProperties ( "users-data.datasource" )
fun usersDataSourceProperties (): DataSourceProperties {
return DataSourceProperties ()
}
@Bean ( name = [ "dataSource" ])
@ConfigurationProperties ( "users-data.datasource.configuration" )
fun dataSource ( @Qualifier ( "dataSourceProperties" ) usersDataSourceProperties : DataSourceProperties ): DataSource {
return usersDataSourceProperties . initializeDataSourceBuilder (). type ( HikariDataSource :: class . java )
. build ()
}
@Bean ( name = [ "entityManagerFactory" ])
fun entityManagerFactory (
builder : EntityManagerFactoryBuilder , @Qualifier ( "dataSource" ) dataSource : DataSource
): LocalContainerEntityManagerFactoryBean {
return builder
. dataSource ( dataSource )
. packages ( "wool.multidb.users.domain" )
. persistenceUnit ( "users" )
. build ()
}
@Bean ( name = [ "transactionManager" ])
fun transactionManager (
@Qualifier ( "entityManagerFactory" ) usersEntityManagerFactory : EntityManagerFactory
): PlatformTransactionManager {
return JpaTransactionManager ( usersEntityManagerFactory )
}
}
Creating Configuration Bean 3 - Mongo DB Setup
Create a config package and create a MongoConfig file under it
package wool.multidb.config
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
@Configuration
@EnableMongoRepositories ( basePackages = [ "wool.multidb.iotlog.repository" ])
class MongoConfig : AbstractMongoClientConfiguration () {
override fun getDatabaseName (): String {
return "iotlog"
}
override fun mongoClient (): MongoClient {
val connectionString = ConnectionString ( "mongodb://paul:qwerqwer123@localhost:27017/paul?authSource=admin" )
val mongoClientSettings = MongoClientSettings
. builder ()
. applyConnectionString ( connectionString )
. build ()
return MongoClients . create ( mongoClientSettings )
}
}
The ConnectionString used here contains user, password, and admin DB information
Later, during enhancement, you can extract these values to properties or use Kubernetes secrets
Developing Users Package - Controller, Service, Repository, Domain
Domain - UsersEntity
package wool.multidb.users.domain
import lombok.Data
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table
@Entity
@Data
@Table ( name = "users" )
data class UsersEntity (
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
val id : Int ? = null ,
val userName : String ,
val email : String ,
)
repository - UsersEntityRepository
package wool.multidb.users.repository
import org.springframework.data.jpa.repository.JpaRepository
import wool.multidb.users.domain.UsersEntity
interface UsersEntityRepository : JpaRepository < UsersEntity , Int > {
}
package wool.multidb.users.service
import org.springframework.stereotype.Service
import wool.multidb.users.domain.UsersEntity
import wool.multidb.users.repository.UsersEntityRepository
@Service
class UsersService (
private val usersEntityRepository : UsersEntityRepository
) {
fun getUsers (): List < UsersEntity > = usersEntityRepository . findAll ()
}
controller - UsersController
package wool.multidb.users.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import wool.multidb.users.domain.UsersEntity
import wool.multidb.users.service.UsersService
@RestController
class UsersController (
private val usersService : UsersService
) {
@GetMapping ( "/users" )
fun getAllUsers (): List < UsersEntity > {
return usersService . getUsers ()
}
}
Developing Device Package - Controller, Service, Repository, Domain
Domain - DeviceEntity
package wool.multidb.device.domain
import lombok.Data
import javax.persistence.*
@Entity
@Data
@Table ( name = "device" )
data class DeviceEntity (
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
var id : Int ? = null ,
var deviceName : String ? = null ,
var deviceType : String ? = null
)
repository - DeviceEntityRepository
package wool.multidb.device.repository
import org.springframework.data.jpa.repository.JpaRepository
import wool.multidb.device.domain.DeviceEntity
interface DeviceEntityRepository : JpaRepository < DeviceEntity , Int > {
}
service - DeviceService
package wool.multidb.device.service
import org.springframework.stereotype.Service
import wool.multidb.device.domain.DeviceEntity
import wool.multidb.device.repository.DeviceEntityRepository
@Service
class DeviceService (
private val deviceEntityRepository : DeviceEntityRepository
) {
fun getAllDevices (): MutableList < DeviceEntity > {
return deviceEntityRepository . findAll ()
}
}
controller - DeviceController
package wool.multidb.device.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import wool.multidb.device.domain.DeviceEntity
import wool.multidb.device.service.DeviceService
@RestController
class DeviceController (
val deviceService : DeviceService
) {
@GetMapping ( "/devices" )
fun getAllDevices (): MutableList < DeviceEntity > {
return deviceService . getAllDevices ()
}
}
Developing IoTLog Package - Controller, Service, Repository, Domain
Since this is MongoDB setup, we’ll inherit from MongoTemplate rather than JPARepository
We’ll try storing Light (bulb) data from IoT Devices
Domain - Light
package wool.multidb.iotlog.domain
import org.springframework.data.mongodb.core.mapping.Document
import javax.persistence.Entity
import javax.persistence.Id
@Document ( collection = "iotLog" )
data class Light (
@Id
var id : String ? = null ,
var status : Boolean = false
)
repository - IoTRepository
package wool.multidb.iotlog.repository
import org.springframework.data.mongodb.repository.MongoRepository
import wool.multidb.iotlog.domain.Light
interface IoTRepository : MongoRepository < Light , String >{}
service - IoTService
package wool.multidb.iotlog.service
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.stereotype.Service
import wool.multidb.iotlog.domain.Light
import wool.multidb.iotlog.repository.IoTRepository
@Service
class IoTService (
val iotRepository : IoTRepository ,
val mongoTemplate : MongoTemplate
) {
fun getAllLight (): List < Light > {
return iotRepository . findAll ()
}
fun putLightLog ( light : Light ) {
mongoTemplate . save ( light )
}
}
controller - IoTController
package wool.multidb.iotlog.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import wool.multidb.iotlog.domain.Light
import wool.multidb.iotlog.service.IoTService
@RestController
class IoTController (
private val ioTService : IoTService
) {
@GetMapping ( "/light" )
fun getAllLight (): List < Light > {
return ioTService . getAllLight ()
}
@PostMapping ( "/light" )
fun addLightLog ( @RequestBody light : Light ) {
return ioTService . putLightLog ( light )
}
}
MongoDB or Document DB Config Security
Some companies or projects may require CA file configuration for connections. In such cases, you can add SSLConfig with code like below:
override fun getDatabaseName (): String {
return "DB name"
}
override fun mongoClient (): MongoClient {
val trustStorePath = "./auth"
val keyStore : KeyStore = KeyStore . getInstance ( KeyStore . getDefaultType ())
keyStore . load ( null )
val certF : CertificateFactory = CertificateFactory . getInstance ( "X.509" )
val cert : Certificate = certF . generateCertificate ( URL ( "https://s3.amazonaws.com/rds-downloads/rds-ca-2019-root.pem" ). openStream ())
keyStore . setCertificateEntry ( "mongo-cert" , cert )
FileOutputStream ( trustStorePath ). use { out -> keyStore . store ( out , "pass" . toCharArray ()) }
val trustManagerFactory : TrustManagerFactory = TrustManagerFactory . getInstance ( TrustManagerFactory . getDefaultAlgorithm ())
trustManagerFactory . init ( keyStore )
val sslContext = SSLContext . getInstance ( "TLS" )
sslContext . init ( null , trustManagerFactory . getTrustManagers (), SecureRandom ())
val builder = MongoClientSettings . builder ()
. applyConnectionString ( ConnectionString ( "mongodb-url" ))
. applyToSslSettings { b : SslSettings . Builder ->
b . enabled ( true )
b . context ( sslContext )
}
. build ()
return MongoClients . create ( builder )
}
Verification
Run the SpringBoot server and verify that it works properly
The configuration values written in application properties have been moved inside by creating a config package
When you call each URL defined in the controller, you can see that everything works well
관련 글
태그:
Database ,
Database Configuration ,
Database Connection ,
Java ,
Kotlin ,
MongoDB ,
MySQL ,
PostgreSQL ,
Spring ,
Spring-Kotlin ,
SpringBoot
카테고리:
Backend Engineering ,
Spring
업데이트: July 2, 2022
댓글남기기