2024年3月11日 星期一

SpringBoot 使用多個資料庫連線

紀錄一下我自己在處理這問題的筆記

整理 package

假設我有兩個資料庫連線:

  • (MySQL) 192.168.1.100:3306/db
  • (MS SQL Server) 192.168.2.200:1433;databaseName=hr;
  • (MySQL) 192.168.1.110:3306/sys_setting
  • ……等等

那首先,先把 repository、Hibernate Entity classes都整理好,不同連線的要放在不同 package

例如

資料庫 package
jdbc:mysql://192.168.1.100:3306/db org.example.database.db.models
org.example.database.db.repositories
jdbc:sqlserver://192.168.2.200:1433;databaseName=hr org.example.database.hr.models
org.example.database.hr.repositories

如果資料庫連線全都是使用 repository,Service(DAO)沒有直接使用到 DataSource、EntityManager、TransactionManager的話,可以不用特別移動。

像是下面這個 Service,去連資料庫是 HRUserRepository 做的,而不是 HRUserService,那 HRUserService 就沒有必要移動

import org.example.database.hr.repositories.HRUserRepository;
// ... 其他 import

@Service
public class HRUserService(){
	private HRUserRepository repository;

	public HRUser getUserName(String id){
		return repository.findNameById(id);
	}
}

設置 Bean

再來就是設定好連線的 config:

import java.util.HashMap;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableJpaRepositories(
	/*
	 * basePackages 填上會使用這邊的連線的 package。
	 * 如果只有少數幾個 class 會使用到,也可以使用 basePackageClasses ,
	 * 例如 
	 * 	@EnableJpaRepositories(
	 * 		basePackageClasses = XXXRepository.class
	 * 	)
	 */
	basePackages = {
			"org.example.database.db.models",
			"org.example.database.db.repositories"
	},
	entityManagerFactoryRef = "MySQLDBEntityManagerFactory", 
	transactionManagerRef = "MySQLDBTransactionManager"
)
@EnableTransactionManagement
public class MySQLDBConnectionConfig {
	/*
	 * 建立 DataSource。
	 * 這邊我使用 Spring 從 properties 注入。
	 * 我在網路上查到可以用以下方式來寫
	 *
	 * 	@Bean("MySQLDBDataSource")
	 * 	@ConfigurationProperties("spring.datasource.mysqldb")
	 * 	public DataSource DataSource(){
	 * 		return DataSourceBuilder.create().build();	
	 * 	}
	 * 但我沒法成功,所以改用注入參數處理
	 */
	@Bean("MySQLDBDataSource")
	public DataSource DataSource(
			@Value("${spring.datasource.mysqldb.url}") String jdbcURL,
			@Value("${spring.datasource.mysqldb.username}") String user,
			@Value("${spring.datasource.mysqldb.password}") String password,
			@Value("${spring.datasource.mysqldb.driverClass}") String driverClassName
		) {
		return DataSourceBuilder.create()
				.driverClassName(driverClassName)
				.url(jdbcURL)
				.username(user)
				.password(password)
				.build();
	}

	@Bean("MySQLDBEntityManagerFactory")
	public LocalContainerEntityManagerFactoryBean EntityManagerFactory(
			@Qualifier("MySQLDBDataSource") DataSource ds) {
		EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
		return builder.dataSource(ds)
				.packages("org.example.database.db")
				.persistenceUnit("mysqldb") 
				.build();
	}

	@Bean("MySQLDBTransactionManager")
	public PlatformTransactionManager TransactionManager(
	        @Qualifier("MySQLDBEntityManagerFactory") LocalContainerEntityManagerFactoryBean emf) {
	    return new JpaTransactionManager(emf.getObject());
	}
}

每個連線都依次處理

@Configuration
@EnableJpaRepositories(
	// 中略
)
@EnableTransactionManagement
public class HRDBConnectionConfig{
	@Bean("HRDataSource")
	public DataSource DataSource(/* 注入的參數…… */) {
		// 中略
	}
	@Bean("HREntityManagerFactory")
	public LocalContainerEntityManagerFactoryBean EntityManagerFactory(
			@Qualifier("HRDataSource") DataSource cDataSource,
			EntityManagerFactoryBuilder builder) {
		// 中略
	}

	@Bean("HRTransactionManager")
	public PlatformTransactionManager cTransactionManager(
		@Qualifier("HREntityManagerFactory") LocalContainerEntityManagerFactoryBean emf
	) {
		return new JpaTransactionManager(emf.getObject());
	}

}

參考資料

黃紹溥. (2021, December 5). Spring Boot + Spring Data JPA 配置多個 DataSource. 昕力大學. https://www.tpisoftware.com/tpu/articleDetails/2637
P-c lin. (2021, September 12). 具有多個 DataSource 的 Spring Data JPA. HackMD. https://hackmd.io/@pclin/rJB8Mfu-t
Bingdoal. (2022, September 12). Spring Boot JPA 下套用多個資料庫來源. Bingdoal. https://bingdoal.github.io/backend/2022/09/spring-boot-jpa-multiple-datasource/

沒有留言:

張貼留言