Đối với các ứng dụng Spring Boot chỉ làm việc với một database thì các bạn có thể tham khảo bài viết Cấu hình database trong Spring Boot project sử dụng JPA Starter dependency để biết cách cấu hình database cho ứng dụng. Trong thực tế thì đôi khi chúng ta sẽ làm việc với các ứng dụng cần connect tới 2 hoặc nhiều database khác nhau. Vậy trong những trường hợp này, làm thế nào chúng ta có thể connect tới tất cả các database này để làm việc với chúng? Mình sẽ hướng dẫn các bạn làm điều này trong bài viết này các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Spring Boot project với Spring Data JPA Starter, MySQL Driver, PostgreSQL Driver và Lombok để làm ví dụ:
MySQL Driver và PostgreSQL Driver sẽ giúp ứng dụng ví dụ của mình có thể connect tới MySQL database và PostgreSQL database để làm ví dụ đó các bạn!
Để làm ví dụ và để đơn giản, mình sẽ định nghĩa 1 table trong MySQL database chứa thông tin sinh viên với 2 cột như sau:
1 2 3 4 5 |
CREATE TABLE `student` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; |
Entity của table “student” như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.huongdanjava.springboot.mysql.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import java.io.Serializable; import lombok.Getter; import lombok.Setter; @Table(name = "student") @Entity @Getter @Setter public class Student implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column private String name; } |
Repository class cho entity Student này như sau:
1 2 3 4 5 6 7 8 |
package com.huongdanjava.springboot.mysql.repository; import com.huongdanjava.springboot.mysql.entity.Student; import org.springframework.data.jpa.repository.JpaRepository; public interface StudentRepository extends JpaRepository<Student, Long> { } |
Tương tự, mình cũng định nghĩa 1 table trong PostgreSQL database chứa thông tin lớp học với 2 cột như sau:
1 2 3 4 |
CREATE TABLE clazz ( id serial PRIMARY KEY, name varchar(50) NOT NULL ) |
Entity của table “clazz” sẽ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.huongdanjava.springboot.postgresql.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import java.io.Serializable; import lombok.Getter; import lombok.Setter; @Table(name = "clazz") @Entity @Getter @Setter public class Clazz implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column private String name; } |
Repository class cho entity Clazz như sau:
1 2 3 4 5 6 7 8 |
package com.huongdanjava.springboot.postgresql.repository; import com.huongdanjava.springboot.postgresql.entity.Clazz; import org.springframework.data.jpa.repository.JpaRepository; public interface ClazzRepository extends JpaRepository<Clazz, Long> { } |
Bây giờ, mình sẽ cấu hình thông tin của 2 database này nha các bạn.
Mình sẽ tạo mới class PostgreSQLDatabaseConfiguration để cấu hình cho PostgreSQL database với annotation @EnableJpaRepositories như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.huongdanjava.springboot.postgresql; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Configuration @EnableJpaRepositories( basePackages = {"com.huongdanjava.springboot.postgresql.repository"}, entityManagerFactoryRef = "postgresqlEntityManagerFactory", transactionManagerRef = "postgresqlTransactionManager" ) public class PostgreSQLDatabaseConfiguration { } |
Thuộc tính basePackages sẽ có giá trị là package chứa tất cả các repository liên quan đến PostgreSQL database, của mình ở đây là “com.huongdanjava.springboot.postgresql.repository” đó các bạn! Giá trị của 2 thuộc tính entityManagerFactoryRef và transactionManagerRef sẽ trỏ đến 2 bean mà mình sẽ định nghĩa cho PostgreSQL database như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Bean @Primary @ConfigurationProperties(prefix = "postgresql.datasource") public DataSource postgresqlDataSource() { return DataSourceBuilder.create().build(); } @Bean public LocalContainerEntityManagerFactoryBean postgresqlEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(postgresqlDataSource()) .packages("com.huongdanjava.springboot.postgresql.entity") .build(); } @Bean public PlatformTransactionManager postgresqlTransactionManager( EntityManagerFactory postgresqlEntityManagerFactory) { return new JpaTransactionManager(postgresqlEntityManagerFactory); } |
Do một số nguyên nhân liên quan đến việc auto-configuration, class EntityManagerFactoryBuilder trong class JpaBaseConfiguration cần xác định bean của class Datasource để khởi tạo bản thân nó trong Spring container, các bạn cần chỉ định một trong 2 bean Datasource cho 2 database của chúng ta là primary bằng cách sử dụng annotation @Primary của Spring, như mình đã làm ở trên.
Thông tin liên quan đến PostgreSQL database mình sẽ cấu hình trong tập tin application.properties như sau:
1 2 3 4 |
postgresql.datasource.jdbc-url=jdbc:postgresql://localhost:5432/example postgresql.datasource.driver-class-name=org.postgresql.Driver postgresql.datasource.username=postgres postgresql.datasource.password=123456 |
Các bạn lưu ý là chúng ta cần khai báo postgresql.datasource.jdbc-url thay vì postgresql.datasource.url bởi vì mặc định Spring Boot sẽ khởi tạo HirakiDataSource. Class HirakiDataSource này sử dụng class HikariConfig để lưu thông tin cấu hình database. Class HikariConfig này sử dụng thuộc tính jdbcUrl để lưu thông tin về connection string đó các bạn!
Tương tự như cho PostgreSQL, cho MySQL database, mình cũng sẽ tạo mới class MySQLDatabaseConfiguration để cấu hình cho nó như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.huongdanjava.springboot.mysql; import jakarta.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; 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; @Configuration @EnableJpaRepositories( basePackages = {"com.huongdanjava.springboot.mysql.repository"}, entityManagerFactoryRef = "mysqlEntityManagerFactory", transactionManagerRef = "mysqlTransactionManager" ) public class MySQLDatabaseConfiguration { @Bean @ConfigurationProperties(prefix = "mysql.datasource") public DataSource mysqlDataSource() { return DataSourceBuilder.create().build(); } @Bean public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(mysqlDataSource()) .packages("com.huongdanjava.springboot.mysql.entity") .build(); } @Bean public PlatformTransactionManager mysqlTransactionManager( EntityManagerFactory mysqlEntityManagerFactory) { return new JpaTransactionManager(mysqlEntityManagerFactory); } } |
Thông tin liên quan đến MySQL database, mình cũng sẽ cấu hình trong tập tin application.properties như sau:
1 2 3 4 |
mysql.datasource.jdbc-url=jdbc:mysql://localhost:3306/example mysql.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mysql.datasource.username=root mysql.datasource.password=123456 |
Bây giờ, mình sẽ implement interface CommandLineRunner cho class SpringBootMultipleDatasourcesApplication để lấy thông tin sinh viên và lớp học từ 2 database trên như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.huongdanjava.springboot; import com.huongdanjava.springboot.mysql.repository.StudentRepository; import com.huongdanjava.springboot.postgresql.repository.ClazzRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootMultipleDatasourcesApplication implements CommandLineRunner { @Autowired StudentRepository studentRepository; @Autowired ClazzRepository clazzRepository; public static void main(String[] args) { SpringApplication.run(SpringBootMultipleDatasourcesApplication.class, args); } @Override public void run(String... args) throws Exception { System.out.println("Getting all students from MySQL database ..."); studentRepository.findAll().forEach(s -> System.out.println(s.getName())); System.out.println("Getting all classes from PostgreSQL database ..."); clazzRepository.findAll().forEach(c -> System.out.println(c.getName())); } } |
Nếu trong MySQL và PostgreSQL database của mình có những thông tin về lớp học và sinh viên như sau:
MySQL:
PostgreSQL:
thì khi chạy ví dụ, các bạn sẽ thấy kết quả như sau:
Như vậy là chúng ta đã cấu hình thành công cho ứng dụng Spring Boot có thể kết nối và làm việc với nhiều database cùng lúc rồi đó các bạn!