본문 바로가기
Programming/Spring Boot

[Spring Boot] multiple DataSource 다중 데이터베이스 연결 구성

by 주리니e 2023. 4. 26.
728x90

[Spring Boot] multiple DataSource 다중 데이터베이스 연결 구성

 

 

  • Eclipse IDE 2022-06
  • Open JDK 17
  • Spring Boot 3.0.6
  • Gradle
  • Lombok
  • PostgreSQL

 

하나의 프로젝트에서 여러 데이터베이스에 연결을 하기 위한 다중 데이터소스(Multiple Datasource)를 구성하여 사용해보자. 하나의 데이터베이스에 연결할 경우 아래와 같이 application.properties에 간단히 설정만으로도 구동이 가능하다. 번외로 설명을 추가하자면 Spring Boot 2.0부터는 기본 연결 풀이 tomcat-jdbc에  HikariCP로 변경되었다. 그래서 아래와 같이 설정하여도 HikariCP를 이용하여 데이터소스를 제공받을 수 있다.

  • application.properties
# datasource
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://xxx.xxx.xxx.xxx:5432/db_playground
spring.datasource.username=jiurinie
spring.datasource.password=jiurinie

 

  • 서버 구동 로그
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)
...
2023-04-26T16:44:35.164+09:00  INFO 13452 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-04-26T16:44:35.263+09:00  INFO 13452 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@1c57f6b2
2023-04-26T16:44:35.264+09:00  INFO 13452 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
...

 

 

다중 데이터소스를 사용하여 데이터베이스에 연결을 하려면 자바 소스코드를 이용하여 Configuration을 작성해야 한다. 편의를 위해 DB는 서로 다르지만 PostgreSQL에 접속하는 두개의 데이터소스를 만들어보자.

  • package explorer

 

  • build.gradle
plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.0.6'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.postgresql:postgresql'
	
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

  • DataSourceConfiguration.java
package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
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.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 com.zaxxer.hikari.HikariDataSource;

import jakarta.persistence.EntityManagerFactory;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "com.example.demo.persistence.dao.primary" })
public class DataSourceConfiguration {

	@Primary
	@Bean(name = "dataSource")
	@ConfigurationProperties(prefix = "spring.datasource")
	public DataSource dataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

	@Primary
	@Bean(name = "entityManagerFactory")
	public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {

		Map<String, String> properties = new HashMap<String, String>();
		properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
		properties.put("hibernate.hbm2ddl.auto", "create");

		return builder.dataSource(dataSource).packages("com.example.demo.persistence.model.primary").persistenceUnit("primary").properties(properties).build();
	}

    @Primary
    @Bean(name = "transactionManager")
    PlatformTransactionManager transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

 

  • DataSourceSecondConfiguration.java
package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
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;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariDataSource;

import jakarta.persistence.EntityManagerFactory;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager", basePackages = { "com.example.demo.persistence.dao.second" })
public class DataSourceSecondConfiguration {

	@Bean(name = "secondDataSource")
	@ConfigurationProperties(prefix = "spring.second-datasource")
	public DataSource secondDataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

	@Bean(name = "secondEntityManagerFactory")
	public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("secondDataSource") DataSource secondDataSource) {

		Map<String, String> properties = new HashMap<String, String>();
		properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
		properties.put("hibernate.hbm2ddl.auto", "create");

		return builder.dataSource(secondDataSource).packages("com.example.demo.persistence.model.second").persistenceUnit("second").properties(properties).build();
	}

	@Bean(name = "secondTransactionManager")
	public PlatformTransactionManager secondTransactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory secondEntityManagerFactory) {
		return new JpaTransactionManager(secondEntityManagerFactory);
	}
}

데이터베이스 연결 테스트를 위해 서버 구동 시 테이블이 자동생성될 수 있도록 hibernate.hbm2ddl.auto 값을 create로 설정하였다. 실제 운영 서버 적용 시 값을 validate로 변경하여 사용하자.

 

  • User.java
package com.example.demo.persistence.model.primary;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "tb_primary_user")
public class User  {

	@Id
	@Column(name = "id")
	private String id;

	@Column(name = "password")
	private String password;
}

 

  • Account.java
package com.example.demo.persistence.model.second;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "tb_second_account")
public class Account {

	@Id
	@Column(name = "id")
	private String id;

	@Column(name = "password")
	private String password;
}

 

  • UserRepository.java
package com.example.demo.persistence.dao.primary;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.persistence.model.primary.User;


public interface UserRepository extends JpaRepository<User, String> {

}

 

  • AccountRepository.java
package com.example.demo.persistence.dao.second;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.persistence.model.second.Account;

public interface AccountRepository extends JpaRepository<Account, String> {

}

 

  • application.properties
# primary datasource
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.jdbc-url=jdbc:postgresql://xxx.xxx.xxx.xxx:5432/db_playground
spring.datasource.username=jiurinie
spring.datasource.password=jiurinie


# second datasource
spring.second-datasource.driver-class-name=org.postgresql.Driver
spring.second-datasource.jdbc-url=jdbc:postgresql://xxx.xxx.xxx.xxx:5432/db_playground2
spring.second-datasource.username=jiurinie
spring.second-datasource.password=jiurinie


#jpa
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

applicaiton.properties에서 주의할 점이 있다. Java로 Configuration을 구성하여 데이터소스를 제공하는 경우에는 spring.datasource.url 이 아닌 spring datasource.jdbc-url 속성으로 변경해주어야 한다. 변경하지 않고 사용할 경우에는 다음과 같은 오류가 발생한다. 

java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.

둘 다 HikariCP를 사용하지만 Java로 Configuration을 구성하느냐 안하느냐에 따라 속성 이름을 변경해주어야 한다.

 

  • 서버 구동 로그
2023-04-26T17:29:18.180+09:00  INFO 19304 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: primary]
2023-04-26T17:29:18.212+09:00  INFO 19304 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.1.7.Final
2023-04-26T17:29:18.405+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-04-26T17:29:18.508+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@1b1252c8
2023-04-26T17:29:18.510+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-04-26T17:29:18.530+09:00  INFO 19304 --- [           main] SQL dialect                              : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Hibernate: 
    
    drop table if exists tb_primary_user cascade
Hibernate: 
    
    create table tb_primary_user (
       id varchar(255) not null,
        password varchar(255),
        primary key (id)
    )
2023-04-26T17:29:18.990+09:00  INFO 19304 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-04-26T17:29:18.996+09:00  INFO 19304 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'primary'
2023-04-26T17:29:19.006+09:00  INFO 19304 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: second]
2023-04-26T17:29:19.015+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2023-04-26T17:29:19.063+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-2 - Added connection org.postgresql.jdbc.PgConnection@539dd2d0
2023-04-26T17:29:19.064+09:00  INFO 19304 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2023-04-26T17:29:19.070+09:00  INFO 19304 --- [           main] SQL dialect                              : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Hibernate: 
    
    drop table if exists tb_second_account cascade
Hibernate: 
    
    create table tb_second_account (
       id varchar(255) not null,
        password varchar(255),
        primary key (id)
    )
2023-04-26T17:29:19.102+09:00  INFO 19304 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-04-26T17:29:19.102+09:00  INFO 19304 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'second'

 

728x90

댓글