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

아현·2024년 5월 8일
1

Spring

목록 보기
4/7

One (JPA)

JPA VS JDBC Template https://sundries-in-myidea.tistory.com/133

참고, 참고2

참고참고1, 참고2

참고, 참고2, 참고3, 참고4

먼저, 하나의 DB를 사용할 때는 application.properties 또는 application.yml에 DB에 대한 몇가지 설정만 해주면, 알아서 Entity와 Repository들을 스캔해서 바로 개발을 할 수 있게 세팅을 해준다.

하지만, 다중 DB를 설정하기 위해서는 JPA에서 자동으로 설정해주었던 것들을 직접 설정해주어야한다.

크게 보면 아래의 2가지 설정을 해주면 된다.

  1. DataSource 설정
  2. EntityManager와 QueryFactory를 Bean에 등록

QueryFactory Bean을 등록하는 작업은 QueryDsl을 사용하는 사람만 해주면 된다.

1. application.yml


  • application.properties
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    # 첫번째 DB에 대한 설정
    spring.datasource.url=jdbc:mysql://localhost:3306/{DB명}
    spring.datasource.username={DB 유저}
    spring.datasource.password={DB 패스워드}
    
    # 두번째 DB에 대한 설정
    spring.second-datasource.url=jdbc:mysql://localhost:3306/{또 다른 DB명}
    spring.second-datasource.username={DB 유저}
    spring.second-datasource.password={DB 패스워드}
    



server:
  port: 8082

spring:
  #포항
  pohang:
    url:-
    username: -
    password: -
    driver-class-name: org.postgresql.Driver
  #광양
  kwangyang:
    url: -
    username: -
    password: -
    driver-class-name: org.postgresql.Driver

  jpa:
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        show_sql: false
        format_sql: false

...



2. DataSource 설정


1) Main DataSource 설정

다중 DB를 설정할 때는, @Primary 어노테이션을 이용하여 Master가되는 DataSource를 지정해야합니다.



PohangDataSourceConfig.java



package com.poscodx.energykeeper.util;

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.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.poscodx.energykeeper.domain.pohang.repo", // 첫번째 DB가 있는 패키지(폴더) 경로
        entityManagerFactoryRef = "pohangEntityManagerFactory", // EntityManager의 이름
        transactionManagerRef = "pohangTransactionManager" // 트랜잭션 매니저의 이름
)
public class PohangDataSourceConfig {
    @Bean
    @Primary
    @ConfigurationProperties("spring.pohang") // application.properties에 작성된 DB와 관련된 설정 값들의 접두사
    public DataSourceProperties pohangDatasourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.pohang.configuration") // DB와 관련된 설정값들의 접두사에 .configuration을 붙여준다.
    public DataSource pohangDatasource() {
        return pohangDatasourceProperties()
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean(name = "pohangEntityManagerFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean pohangEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        DataSource dataSource = pohangDatasource();
        return builder
                .dataSource(dataSource)
                .packages("com.poscodx.energykeeper.domain.pohang.entity") // 첫번째 DB와 관련된 엔티티들이 있는 패키지(폴더) 경로
                .persistenceUnit("pohangEntityManager")
                .build();
    }

    @Bean(name = "pohangTransactionManager")
    @Primary
    public PlatformTransactionManager pohangTransactionManager(
            final @Qualifier("pohangEntityManagerFactory") LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean
    ) {
        return new JpaTransactionManager(localContainerEntityManagerFactoryBean.getObject());
    }

}



@) Second DataSource 설정


KwangyangDataSourceConfig.java


package com.poscodx.energykeeper.util;

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.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.poscodx.energykeeper.domain.kwangyang.repo", // 첫번째 DB가 있는 패키지(폴더) 경로
        entityManagerFactoryRef = "kwangyangEntityManagerFactory", // EntityManager의 이름
        transactionManagerRef = "kwangyangTransactionManager" // 트랜잭션 매니저의 이름
)
public class KwangyangDataSourceConfig {
    @Bean
    @Primary
    @ConfigurationProperties("spring.kwangyang") // application.properties에 작성된 DB와 관련된 설정 값들의 접두사
    public DataSourceProperties kwangyangDatasourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.kwangyang.configuration") // DB와 관련된 설정값들의 접두사에 .configuration을 붙여준다.
    public DataSource kwangyangDatasource() {
        return kwangyangDatasourceProperties()
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean(name = "kwangyangEntityManagerFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean kwangyangEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        DataSource dataSource = kwangyangDatasource();
        return builder
                .dataSource(dataSource)
                .packages("com.poscodx.energykeeper.domain.kwangyang.entity") // 첫번째 DB와 관련된 엔티티들이 있는 패키지(폴더) 경로
                .persistenceUnit("kwangyangEntityManager")
                .build();
    }

    @Bean(name = "kwangyangTransactionManager")
    @Primary
    public PlatformTransactionManager kwangyangTransactionManager(
            final @Qualifier("kwangyangEntityManagerFactory") LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean
    ) {
        return new JpaTransactionManager(localContainerEntityManagerFactoryBean.getObject());
    }

}

3. Bean 등록



package com.poscodx.energykeeper.util;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Configuration
public class JPAConfig {
    //@PersistenceContext
    //private EntityManager entityManager;

    @PersistenceContext(unitName = "pohangEntityManager")
    private EntityManager pohangEntityManager;

    @PersistenceContext(unitName = "kwangyangEntityManager")
    private EntityManager kwangyangEntityManager;

    //@Bean
    //public JPAQueryFactory queryFactory() {
    //    return new JPAQueryFactory(entityManager);
    //}

    /* QueryDsl 관련 설정 */
    @Primary // ⭐
    @Bean
    public JPAQueryFactory pohangQueryFactory() {
        return new JPAQueryFactory(pohangEntityManager);
    }

    @Bean(name="kwangyangQueryFactory")
    public JPAQueryFactory kwangyangQueryFactory() {
        return new JPAQueryFactory(kwangyangEntityManager);
    }
}



Another (JDBC Template)

참고


단일 datasource 설정과 다르게 다중 datasource 는 spring.datasource 뒤에 접두사 primary 와 secondary 로 구분하고 datasource 를 초기화 할때 사용하게 된다.

  • Springboot 2.x 은 spring.datasource.secondary.jdbc-url 를 사용하고, Springboot 1.x 은 spring.datasource.secondary.url 를 사용한다.
    • 실행시 java.lang.IllegalArgumentException:jdbcUrl is required with driverClassName 발생하면 해당 버전별 설정이 제대로 되었는지 확인해보면 된다.



1. application.yml


 server:
  port: 8082

spring:
  datasource:
	#포항
    pohang:
      jdbc-url: -
      username: -
      password: -
      driver-class-name: org.postgresql.Driver
    #광양
	kwangyang:
      jdbc-url: -
      username: -
      password: -
      driver-class-name: org.postgresql.Driver

...



2. DataSource 설정


  • DataSource 초기화 및 JdbcTemplate 설정

DataSourceConfig.java



package com.poscodx.energykeeper.util;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    //포항
    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.pohang")
    public DataSource pohangDataSource() {
        return DataSourceBuilder.create().build();
    }

    //광양
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.kwangyang")
    public DataSource kwangyangDataSource() {
        return DataSourceBuilder.create().build();
    }

    //포항
    @Bean
    public JdbcTemplate pohangJdbcTemplate(@Qualifier("pohangDataSource") DataSource pohangDataSource) {
        return new JdbcTemplate(pohangDataSource);
    }

    //광양
    @Bean
    public JdbcTemplate kwangyangJdbcTemplate(@Qualifier("kwangyangDataSource") DataSource kwangyangDataSource) {
        return new JdbcTemplate(kwangyangDataSource);
    }
}



3. Test (example)



@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter37ApplicationTests {

    @Autowired
    protected JdbcTemplate primaryJdbcTemplate;

    @Autowired
    protected JdbcTemplate secondaryJdbcTemplate;

    @Before
    public void setUp() {
        primaryJdbcTemplate.update("DELETE  FROM  USER ");
        secondaryJdbcTemplate.update("DELETE  FROM  USER ");
    }

    @Test
    public void test() throws Exception {

        primaryJdbcTemplate.update("insert into user(name,age) values(?, ?)", "aaa", 20);
        primaryJdbcTemplate.update("insert into user(name,age) values(?, ?)", "bbb", 30);

        secondaryJdbcTemplate.update("insert into user(name,age) values(?, ?)", "ccc", 20);

        Assert.assertEquals("2", primaryJdbcTemplate.queryForObject("select count(1) from user", String.class));

        Assert.assertEquals("1", secondaryJdbcTemplate.queryForObject("select count(1) from user", String.class));
    }

}



참고

<손프로님 아이디어>

Primary/Secondary 나누지 말고 하나의 클래스에서 Map을 bean으로 생성해서 접근하면 2개를 관리할 필요가 없음

profile
For the sake of someone who studies computer science

4개의 댓글

comment-user-thumbnail
2024년 12월 2일

안녕하세요. 잘 봤습니다.

저는 지금 Repository 클래스에서
@PersistenceContext
private EntityManager entityManager;
이렇게 구현하여 사용하는 단계를 보고 있는데,

올리신 글처럼 이중 DB 컨피그를 구현할 경우 Repository에서
마치 Autowired 하듯 고대로
@PersistenceContext(unitName = "pohangEntityManager")
private EntityManager entityManager;
이렇게 구현해서 사용하면 되는걸까요?

2개의 답글