Connect multiple database using hibernate

I. Giới thiệu

Trong bài viết này mình sẽ hướng dẫn config project Spring boot connect multiple database sử dụng hibernate, cụ thể ở đây mình có 2 database là PostgreSQL và MySQL.

Ở đây mình mình sẽ sử dụng kotlin.

II. Implement:

1. Maven dependencies:

Thực hiện import các dependencies cần thiết vào project:

        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
        </dependency>

Tiếp theo để sử dụng cả Porstgresql và MySQL:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

2. Config Propertiese:

Config trong file application.properties, ở đây mình connect đến 2 database là MySQL và PostgreSQL

spring:
  mysql-datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://${mysql.host}:${mysql.port}/${mysql.name}?useUnicode=yes&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false
    username: ${mysql.user}
    password: ${mysql.password}
    hibernate.dialect: org.hibernate.dialect.MySQLDialect
  postgresql-datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://${postgresql.host}:${postgresql.port}/${postgresql.name}
    username: ${postgresql.user}
    password: ${postgresql.password}
    hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
    initialization-mode: always
  jpa:
    database-platform: default
    hibernate.ddl-auto: none
    properties:
      hibernate:
        format_sql: true
        temp.use_jdbc_metadata_defaults: false
    show-sql: true

3. Config data source

Bây giờ là bước config data source trong Spring, ở đây mình define 2 package repository và 2 package entity tương ứng với MySQL và PostgreSQL để jpa repository trỏ vào

Mysql:

Tạo 1 file config cho MySQL MySQLConfig.kt, ở đây mình sẽ custom lại entityManagerFactoryReftransactionManagerRef:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = ["com.example.multidatabase.repository.mysql"],
        entityManagerFactoryRef = "mysqlEntityManagerFactory",
        transactionManagerRef = "mysqlTransactionManager"
)
class MySQLConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    fun mysqlDataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    fun mysqlDataSource(): DataSource {
        val securityDataSourceProperties = mysqlDataSourceProperties()
        return DataSourceBuilder.create()
                .driverClassName(securityDataSourceProperties.driverClassName)
                .url(securityDataSourceProperties.url)
                .username(securityDataSourceProperties.username)
                .password(securityDataSourceProperties.password)
                .build()
    }

    @Bean
    fun mysqlTransactionManager(): PlatformTransactionManager {
        val factory: EntityManagerFactory? = mysqlEntityManagerFactory().getObject()
        return JpaTransactionManager(factory!!)
    }

    @Bean(name = ["mysqlEntityManagerFactory"])
    @Primary
    fun mysqlEntityManagerFactory(): LocalContainerEntityManagerFactoryBean {
        val em = LocalContainerEntityManagerFactoryBean()
        em.dataSource = mysqlDataSource()
        em.setPackagesToScan("com.example.multidatabase.entity.mysql")

        val vendorAdapter = HibernateJpaVendorAdapter()
        em.jpaVendorAdapter = vendorAdapter
        val properties = HashMap<String, Any?>()
        properties["hibernate.hbm2ddl.auto"] = env.getProperty("hibernate.hbm2ddl.auto")
        properties["hibernate.dialect"] = env.getProperty("spring.datasource.hibernate.dialect")
        em.setJpaPropertyMap(properties)

        return em
    }
}

Postgresql:

Tương tự config cho postgresql, tạo file PostgresqlConfig.kt:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = ["com.example.multidatabase.repository.postgresql"],
        entityManagerFactoryRef = "postgresqlEntityManagerFactory",
        transactionManagerRef = "postgresqlTransactionManager"
)
class PostgresqlConfig {
    @Autowired
    private lateinit var env: Environment

    @Bean
    @ConfigurationProperties(prefix = "spring.postgresql-datasource")
    fun postgresqlDataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    fun postgresqlDataSource(): DataSource {
        val securityDataSourceProperties = postgresqlDataSourceProperties()
        return DataSourceBuilder.create()
                .driverClassName(securityDataSourceProperties.driverClassName)
                .url(securityDataSourceProperties.url)
                .username(securityDataSourceProperties.username)
                .password(securityDataSourceProperties.password)
                .build()
    }

    @Bean
    fun postgresqlTransactionManager(): PlatformTransactionManager {
        val factory: EntityManagerFactory? = postgresqlEntityManagerFactory().getObject()
        return JpaTransactionManager(factory!!)
    }

    @Bean(name = ["postgresqlEntityManagerFactory"])
    @Primary
    fun postgresqlEntityManagerFactory(): LocalContainerEntityManagerFactoryBean {
        val em = LocalContainerEntityManagerFactoryBean()
        em.dataSource = postgresqlDataSource()
        em.setPackagesToScan("com.example.multidatabase.entity.postgresql")

        val vendorAdapter = HibernateJpaVendorAdapter()
        em.jpaVendorAdapter = vendorAdapter
        val properties = HashMap<String, Any?>()
        properties["hibernate.hbm2ddl.auto"] = env.getProperty("hibernate.hbm2ddl.auto")
        properties["hibernate.dialect"] = env.getProperty("spring.second-datasource.hibernate.dialect")
        em.setJpaPropertyMap(properties)

        return em
    }
}

Initializer

Trong trường hợp muốn config khi load entity xong thì hibernate tự động run script sql thì config thêm phần Initializer như bên dưới:

    @Bean
    fun mysqlDataSourceInitializer(): DataSourceInitializer? {
        val dataSourceInitializer = DataSourceInitializer()
        dataSourceInitializer.setDataSource(mysqlDataSource())
        val databasePopulator = ResourceDatabasePopulator()
        databasePopulator.addScript(ClassPathResource("script-mysql.sql"))
        dataSourceInitializer.setDatabasePopulator(databasePopulator)
        return dataSourceInitializer
    }
    @Bean
    fun postgresqlDataSourceInitializer(): DataSourceInitializer? {
        val dataSourceInitializer = DataSourceInitializer()
        dataSourceInitializer.setDataSource(postgresqlDataSource())
        val databasePopulator = ResourceDatabasePopulator()
        databasePopulator.addScript(ClassPathResource("script-postgresql.sql"))
        dataSourceInitializer.setDatabasePopulator(databasePopulator)
        return dataSourceInitializer
    }

4. Create entity package

Ví dụ ở đây mình có 1 table users_mysql trong database mysql, và mình muốn load toàn bộ dữ liệu ở mysql sang table user_postgresql trong database postgresql

Như config ở trên, giờ mình tạo entity cho mysql (package com.example.multidatabase.entity.mysql) và postgresql (package com.example.multidatabase.entity.postgresql) :

-com.example.multidatabase.entity
--mysql
---UsersMySQLEntity.kt
--postgresql
---UsersPostgreSQLEntity.kt

UsersMySQLEntity.kt:

@Entity
@Table(name = "user_email")
class UsersMySQLEntity {
    @Id
    @Column(name = "user_id", unique = true)
    var userId: String? = null

    @Column(name = "email_address", nullable = false, unique = true)
    var emailAddress: String? = null

    @Column(name = "password", nullable = false)
    var userPassword: String? = null

    @Column(name = "reset_password_token", nullable = true)
    var resetPasswordToken: String? = null

    @Column(name = "reset_password_sent_at", nullable = true)
    var resetPasswordSentAt: LocalDateTime? = null

    @Column(name = "last_try_count", nullable = true)
    var lastTryCount: Int? = null

    @Column(name = "last_try_date", nullable = true)
    var lastTryDate: LocalDateTime? = null

    @Column(name = "created_at", nullable = false)
    var createdDate: LocalDateTime? = null

    @Column(name = "deleted_at", nullable = true)
    var deletedAt: LocalDateTime? = null

    @Column(name = "updated_at", nullable = false)
    var updatedDate: LocalDateTime? = null
}

PostgreSQLEntity.kt:

@Entity
@Table(name = "users")
class PostgreSQLEntity {
    @Id
    @Column(name = "id", nullable = false)
    var id: Int? = null

    @Column(name = "provider", nullable = true)
    var provider: String? = null

    @Column(name = "uid", nullable = true)
    var uid: String? = null

    @Column(name = "email", nullable = true)
    var email: String? = null

    @Column(name = "encrypted_password", nullable = true)
    var encryptedPassword: String? = null

    @Column(name = "reset_password_token", nullable = true)
    var resetPasswordToken: String? = null

    @Column(name = "reset_password_sent_at", nullable = true)
    var resetPasswordSentAt: LocalDateTime? = null

    @Column(name = "name", nullable = true)
    var name: String? = null

    @Column(name = "created_at", nullable = false)
    var createdDate: LocalDateTime? = null

    @Column(name = "deleted_at", nullable = true)
    var deletedAt: LocalDateTime? = null

    @Column(name = "updated_at", nullable = false)
    var updatedDate: LocalDateTime? = null
}

5. Create repository package:

OK, đã có entity, giờ mình sẽ tạo repository tương ứng mysql (package com.example.multidatabase.repository.mysql), postgresql (package com.example.multidatabase.repository.postgresql):

-com.example.multidatabase.repository
--mysql
---UsersMySQLRepository.kt
--postgresql
---UsersPostgreSQLRepository.kt

UsersMySQLRepository.kt:

interface UsersMySQLRepository : JpaRepository<UsersMySQLEntity, String> {
}

UsersPostgreSQLRepository.kt:

interface UsersPostgreSQLRepository : JpaRepository<UsersPostgreSQLEntity, String> {
}

6. Execute:

OK, bây giờ define 1 component để cứ 30s 1 lần insert user vào MySQL và PostgreSQL:

@Component
class MigrationComponent(
        val usersMySQLRepository: UsersMySQLRepository,
        val usersPostgreSQLRepository: UsersPostgreSQLRepository) {
    companion object {
        val LOG = LoggerFactory.getLogger(MigrationComponent::class.java)
    }

    @Scheduled(cron = "*/30 * * ? * *")
    fun process() {
        val userMysql = createNewUserMySQL()
        usersMySQLRepository.saveAndFlush(userMysql)
        LOG.info("User MySQL:")
        LOG.info(ObjectMapper().writeValueAsString(userMysql))
        val userPostgreSQL = ModelMapper().map(userMysql, UsersPostgreSQLEntity::class.java)
        usersPostgreSQLRepository.saveAndFlush(userPostgreSQL)
        LOG.info("User PostgreSQL")
        LOG.info(ObjectMapper().writeValueAsString(userPostgreSQL))
    }

    private fun createNewUserMySQL(): UsersMySQLEntity {
        val user = UsersMySQLEntity()
        user.emailAddress = String.format("test+%[email protected]", LocalDateTime.now().toLocalTime().toString())
        return user
    }
}

7. Result:

Log cho phần start spring boot thành công :

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-06-21 10:49:27,699 INFO  [main] o.s.b.StartupInfoLogger:55: Starting MultidatabaseApplicationKt on hoang-MS-7B84 with PID 16023 (/home/hoang/Dev/HOME_WORK/multidatabase/target/classes started by hoang in /home/hoang/Dev/HOME_WORK/multidatabase)
2020-06-21 10:49:27,701 INFO  [main] o.s.b.SpringApplication:655: The following profiles are active: dev
2020-06-21 10:49:28,287 INFO  [main] o.s.d.r.c.RepositoryConfigurationDelegate:127: Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-06-21 10:49:28,330 INFO  [main] o.s.d.r.c.RepositoryConfigurationDelegate:187: Finished Spring Data repository scanning in 34ms. Found 1 JPA repository interfaces.
2020-06-21 10:49:28,331 INFO  [main] o.s.d.r.c.RepositoryConfigurationDelegate:127: Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-06-21 10:49:28,336 INFO  [main] o.s.d.r.c.RepositoryConfigurationDelegate:187: Finished Spring Data repository scanning in 4ms. Found 1 JPA repository interfaces.
2020-06-21 10:49:28,982 INFO  [main] o.s.b.w.e.t.TomcatWebServer:108: Tomcat initialized with port(s): 8080 (http)
2020-06-21 10:49:28,988 INFO  [main] o.a.j.l.DirectJDKLog:173: Initializing ProtocolHandler ["http-nio-8080"]
2020-06-21 10:49:28,989 INFO  [main] o.a.j.l.DirectJDKLog:173: Starting service [Tomcat]
2020-06-21 10:49:28,990 INFO  [main] o.a.j.l.DirectJDKLog:173: Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-06-21 10:49:29,041 INFO  [main] o.a.j.l.DirectJDKLog:173: Initializing Spring embedded WebApplicationContext
2020-06-21 10:49:29,041 INFO  [main] o.s.b.w.s.c.ServletWebServerApplicationContext:285: Root WebApplicationContext: initialization completed in 1294 ms
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
2020-06-21 10:49:29,350 INFO  [main] c.z.h.HikariDataSource:110: HikariPool-1 - Starting...
2020-06-21 10:49:29,351 WARN  [main] c.z.h.u.DriverDataSource:70: Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2020-06-21 10:49:29,431 INFO  [main] c.z.h.HikariDataSource:123: HikariPool-1 - Start completed.
2020-06-21 10:49:29,659 INFO  [main] o.s.o.j.AbstractEntityManagerFactoryBean:416: Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-06-21 10:49:29,693 INFO  [main] c.z.h.HikariDataSource:110: HikariPool-2 - Starting...
2020-06-21 10:49:29,734 INFO  [main] c.z.h.HikariDataSource:123: HikariPool-2 - Start completed.
2020-06-21 10:49:29,780 INFO  [main] o.s.o.j.AbstractEntityManagerFactoryBean:416: Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-06-21 10:49:30,101 INFO  [main] o.s.s.c.ExecutorConfigurationSupport:181: Initializing ExecutorService 'applicationTaskExecutor'
2020-06-21 10:49:30,208 INFO  [main] o.s.s.c.ExecutorConfigurationSupport:181: Initializing ExecutorService 'taskScheduler'
2020-06-21 10:49:30,226 INFO  [main] o.a.j.l.DirectJDKLog:173: Starting ProtocolHandler ["http-nio-8080"]
2020-06-21 10:49:30,236 INFO  [main] o.s.b.w.e.t.TomcatWebServer:220: Tomcat started on port(s): 8080 (http) with context path ''
2020-06-21 10:49:30,246 INFO  [main] o.s.b.StartupInfoLogger:61: Started MultidatabaseApplicationKt in 2.975 seconds (JVM running for 3.382)

Log save user vào database:

User MySQL:
2020-06-21 11:02:00,081 INFO  [scheduling-1] c.e.m.c.MigrationComponent:27: {"userId":"eb77366f-0ef1-43b8-90e1-1a698c3e51da","emailAddress":"test+11:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"lastTryCount":null,"lastTryDate":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
2020-06-21 11:02:00,161 INFO  [scheduling-1] c.e.m.c.MigrationComponent:30: User PostgreSQL
2020-06-21 11:02:00,164 INFO  [scheduling-1] c.e.m.c.MigrationComponent:31: {"userId":"eb77366f-0ef1-43b8-90e1-1a698c3e51da","provider":"email","uid":null,"email":"test+11:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
2020-06-21 11:02:30,005 INFO  [scheduling-1] c.e.m.c.MigrationComponent:26: User MySQL:
2020-06-21 11:02:30,008 INFO  [scheduling-1] c.e.m.c.MigrationComponent:27: {"userId":"9583b283-26e7-4640-8cb8-8f304b0a5494","emailAddress":"test+11:02:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"lastTryCount":null,"lastTryDate":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":30,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":30,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
2020-06-21 11:02:30,017 INFO  [scheduling-1] c.e.m.c.MigrationComponent:30: User PostgreSQL
2020-06-21 11:02:30,020 INFO  [scheduling-1] c.e.m.c.MigrationComponent:31: {"userId":"9583b283-26e7-4640-8cb8-8f304b0a5494","provider":"email","uid":null,"email":"test+11:02:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":30,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":2,"second":30,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
2020-06-21 11:03:00,006 INFO  [scheduling-1] c.e.m.c.MigrationComponent:26: User MySQL:
2020-06-21 11:03:00,009 INFO  [scheduling-1] c.e.m.c.MigrationComponent:27: {"userId":"3df06730-9388-4ea1-a3c5-8e370be01a48","emailAddress":"test+11:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"lastTryCount":null,"lastTryDate":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":3,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":3,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
2020-06-21 11:03:00,017 INFO  [scheduling-1] c.e.m.c.MigrationComponent:30: User PostgreSQL
2020-06-21 11:03:00,020 INFO  [scheduling-1] c.e.m.c.MigrationComponent:31: {"userId":"3df06730-9388-4ea1-a3c5-8e370be01a48","provider":"email","uid":null,"email":"test+11:[email protected]","userPassword":"Bcrypt","resetPasswordToken":null,"resetPasswordSentAt":null,"createdDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":3,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},"deletedAt":null,"updatedDate":{"dayOfWeek":"SUNDAY","dayOfYear":173,"year":2020,"month":"JUNE","nano":0,"monthValue":6,"dayOfMonth":21,"hour":11,"minute":3,"second":0,"chronology":{"calendarType":"iso8601","id":"ISO"}}}

Cùng check kết quả ở database nào:

Table users_postgresql:

|id|provider|uid|email|encrypted_password|created_by|updated_by|reset_password_token|reset_password_sent_at|created_at|updated_at|deleted_at|line_id|
|--|--------|---|-----|------------------|----------|----------|--------------------|----------------------|----------|----------|----------|-------|
|eb77366f-0ef1-43b8-90e1-1a698c3e51da|email||test+11:02@gmail.com|Bcrypt|||||2020-06-21 11:02:00|2020-06-21 11:02:00|||
|9583b283-26e7-4640-8cb8-8f304b0a5494|email||test+11:02:30@gmail.com|Bcrypt|||||2020-06-21 11:02:30|2020-06-21 11:02:30|||
|3df06730-9388-4ea1-a3c5-8e370be01a48|email||test+11:03@gmail.com|Bcrypt|||||2020-06-21 11:03:00|2020-06-21 11:03:00|||
|a042d902-aa46-4117-afea-a7cabd3daacb|email||test+11:03:30@gmail.com|Bcrypt|||||2020-06-21 11:03:30|2020-06-21 11:03:30|||

Table users_mysql:

|user_id|email_address|password|reset_password_token|reset_password_sent_at|last_try_count|last_try_date|created_at|deleted_at|updated_at|
|-------|-------------|--------|--------------------|----------------------|--------------|-------------|----------|----------|----------|
|eb77366f-0ef1-43b8-90e1-1a698c3e51da|test+11:02@gmail.com|Bcrypt|||||2020-06-21 04:02:00.0||2020-06-21 04:02:00.0|
|9583b283-26e7-4640-8cb8-8f304b0a5494|test+11:02:30@gmail.com|Bcrypt|||||2020-06-21 04:02:30.0||2020-06-21 04:02:30.0|
|3df06730-9388-4ea1-a3c5-8e370be01a48|test+11:03@gmail.com|Bcrypt|||||2020-06-21 04:03:00.0||2020-06-21 04:03:00.0|
|a042d902-aa46-4117-afea-a7cabd3daacb|test+11:03:30@gmail.com|Bcrypt|||||2020-06-21 04:03:30.0||2020-06-21 04:03:30.0|

III. Sumary:

Như vậy, việc connect tới nhiều database bằng Spring boot và hibernate có vẻ khá đơn giản phải ko.

Github: https://github.com/hoangntv-1399/multipledatabase