조금 평범한 개발 이야기

JPA 다중 데이타소스 관리 (1) 본문

개발/JPA 다중 데이타소스 관리

JPA 다중 데이타소스 관리 (1)

jogeum 2018. 9. 4. 01:38

사용 기술

  1. Spring boot 2.0.4.RELEASE
  2. maven
  3. JPA
  4. H2 DBMS
  5. Hikari CP
  6. java 1.8

 

개요

Spring Boot 을 사용해 개발을 진행하면 많은 부분의 설정을 자동화해줘서 개발 설정으로 인한 고민을 하지 않아도 되기 때문에 보다 빠르게 개발에 집중할 수 있게 해줍니다.

하지만 정해진 기능이 아닌 조금 다른 방식으로 Spring Boot 을 사용하고자 하면 Spring Boot 이 아닌 Spring에 관련된 설정을 하나씩 일일이 해줘야 하는 불편함이 있는 건 어쩔 수 없는 부분입니다.

이 글에서는 Spring Boot에서 다중 데이타소스를 관리하고 다중 트랜잭션을 설정해 JPA 상에서 동작이 되게 Spring 설정을 추가 하는 내용을 다룹니다.

 

maven 설정

프로젝트를 시작하기에 앞서 Spring 설정은 https://start.spring.io/ 에서 설정된 내용을 기준으로 필요한 내용을 추가 하겠습니다.

Spring Boot 은 2.0.4.RELEASE, java 는 1.8 을 사용합니다.

트랜잭션 테스트를 위해서 h2 dbms 를 추가 하였고 개발을 위해 lombok, guava lib 를 추가 하였습니다.

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>26.0-jre</version>
		</dependency>
	</dependencies>

 

application.yaml

Spring Boot 에 모든 기능을 설정할 수 있는 application.yaml 설정파일에서는 Spring Boot 데이타소스 설정을 사용하지 않고 별도로 데이타소스 설정을 구성하기 때문에 단순히 다중 디비 접속에 관한 정보만 관리 합니다. (Spring Boot 설정은 아무것도 하지 않습니다.)

Connnection Pool 은 Common CP 를 사용하는 것이 아니라 Hikari CP 를 사용하고 이에 따른 설정을 구성하였습니다.

datasource:
  db1:
    jdbc-url: jdbc:h2:file:./db1;AUTO_SERVER=TRUE;Mode=MYSQL
    username: sa
    password:
    driver-class-name: org.h2.Driver
  db2:
    jdbc-url: jdbc:h2:file:./db2;AUTO_SERVER=TRUE;Mode=MYSQL
    username: sa
    password:
    driver-class-name: org.h2.Driver

 

Java Config

이제 실질적인 멀티 데이타소스 설정을 진행해 보겠습니다. 디비 설정은 db1, db2 라는 이름을 가지고 각각의 데이타소스로 관리되며 전혀 다른 디비 스키마를 (전혀 다른 물리적인 공간을) 가집니다. 설정 내용은 데이타소스 1번과, 데이타소스 2번이 동일하기 때문에 데이타소스 1번에 대해서만 설명을 진행하며 데이타소스 2번의 설정은 설정 내용에 있는 1번의 숫자만 교체해서 사용하시면 됩니다.

설정 내용은 아래와 같으며 하나씩 확인해 보겠습니다.

@Configuration
@ConfigurationProperties(prefix = "datasource.db1")
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactory1",
        transactionManagerRef = "transactionManager1",
        basePackages = {"net.jogeum.multitxtest.db1.repository"})
public class DbConfig1 extends HikariConfig {

    @Bean
    public DataSource dataSource1() {
        return new LazyConnectionDataSourceProxy(new HikariDataSource(this));
    }

    @Bean
    public EntityManagerFactory entityManagerFactory1() {
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(this.dataSource1());
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setJpaPropertyMap(ImmutableMap.of(
                "hibernate.hbm2ddl.auto", "update",
                "hibernate.dialect", "org.hibernate.dialect.H2Dialect",
                "hibernate.show_sql", "true"
        ));

        factory.setPackagesToScan("net.jogeum.multitxtest.db1.domain");
        factory.setPersistenceUnitName("db1");
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager1() {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(entityManagerFactory1());
        return tm;
    }
}

 

먼저 해당 자바 파일이 Spring 설정을 위한 파일임을 Spring Framework 에게 알립니다.

@Configuration

 

그런 다음 application.yaml 파일에 설정해 두었던 디비 접속 정보를 @ConfigurationProperties 를 이용해 HikariConfig Bean 을 상속 받은 DbConfig1 Bean 의 Setter 에 설정 합니다. 상속 받은 HikariConfig 는 디비 접속 정보를 담을 수 있게 Hikari CP 설정을 위한 Setter/Getter 를 제공해 줍니다.

Hikari CP 는 Spring Boot 2.0 부터는 기본으로 포함되는 라이브러리 이며 Spring Boot 1.5.X 버전에서는 별도로 라이브러리를 추가해서 사용해야 합니다.

@ConfigurationProperties(prefix = "datasource.db1")
...
public class DbConfig1 extends HikariConfig {
...

 

datasource

가지고 온 설정 정보를 가지고 실제 데이타소스를 생성해 봅시다. 먼저 Datasource 의 구현체로 HikariDataSource 를 이용해 데이타소스를 생성하는데 HikariDataSource 를 생성할때 DbConfig1 자기 자신을 주입합니다. 이는 상속받은 HikariConfig 를 기준으로 HikariDataSource 를 생성 하겠다는 의미입니다.

보면 생성된 HikariDataSource 가 바로 데이타소스의 @Bean 으로 생성되는 것이 아니라 LazyConnectionDataSourceProxy 로 한번 감싸져서 최종적으로 DataSourceProxy@Bean 으로 생성이 되는데 LazyConnectionDataSourceProxy 로 깜사는 이유는 Spring 이 시작될때 바로 Connection Pool 에 지정된 갯수 만큼의 Connnection 을 확보 하는 것이 아니라 실제 요청이 들어와 필요한 시점에 Connection 을 확보하기 위해서 입니다.

@Bean
public DataSource dataSource1() {
    return new LazyConnectionDataSourceProxy(new HikariDataSource(this));
}

 

EntityManager

EntityManager 객체는 JPA 의 모든 정보를 관리하는 객체인데 멀티 데이타소스를 사용하기 위해 Spring Boot 에서 제공하는 자동 설정을 사용하지 않고 각각의 데이타소스에 맞춰 EntityManager 역시 별도로 설정하고 생성 합니다. 하나씩 소스를 파악해 봅시다.

@Bean
public EntityManagerFactory entityManagerFactory1() {
    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setDataSource(this.dataSource1());
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setJpaPropertyMap(ImmutableMap.of(
            "hibernate.hbm2ddl.auto", "update",
            "hibernate.dialect", "org.hibernate.dialect.H2Dialect",
            "hibernate.show_sql", "true"
    ));

    factory.setPackagesToScan("net.jogeum.multitxtest.db1.domain");
    factory.setPersistenceUnitName("db1");
    factory.afterPropertiesSet();

    return factory.getObject();
}

 

먼저 데이타소스 에서 사용하기 위한 EntityManagerFactoryBean 을 생성합니다. 그리고 EntityManagerFactoryBean 에서 사용할 데이타소스를 앞서 생성한 DataSource 1번을 사용하겠다고 지정합니다.

LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(this.dataSource1());

 

그 다음 EntityManager 에서 사용할 JPA 설정을 추가 하는데 이것은 Spring Boot JPA 설정을 application.yaml 파일에 하는 내용과 동일한 내용을 가집니다. 만약 프로젝트 구성시 JPA 설정에 추가로 필요한 내용이 있다면 아래 설정에 설정할 내용을 추가해 주시면 됩니다.

  • hibernate.hbm2ddl.auto 는 Spring 이 시작할때 JPA 에 설정된 내용을 기준으로 db table 을 어떤 방식으로 초기화 시킬 것인지를 설정하는 값입니다. 각각 create, create-drop, update, validate, none 이 있습니다.
설정설명
create시작될때 모든 table 을 생성 합니다.
create-drop시작될때 모든 table 을 생성 하나 종료될때 생성된 table 을 지웁니다.
update시작될때 JPA Entity 와 실제 table 을 비교하여 변경된 내용을 반영 합니다. 만약 table 이 없다면 새롭게 생성 합니다.
validate시작될때 JPA Entity 와 실제 table 을 비교하여 결과가 상이하면 시작되지 않고 종료 합니다.
none아무런검사를 하지 않습니다.
  • hibernate.dialect 사용할 데이타베이스의 종류를 지정 합니다. 이 프로젝트는 테스트 용으로 h2 dbms를 사용하고 있음으로 H2Dialect 을 지정합니다.
  • hibernate.show_sql JPA 가 동작될때 sql 구문을 로그 파일에 노출 시킬지 여부를 결정 합니다.
factory.setJpaPropertyMap(ImmutableMap.of(
        "hibernate.hbm2ddl.auto", "update",
        "hibernate.dialect", "org.hibernate.dialect.H2Dialect",
        "hibernate.show_sql", "true"
));

 

그리고 데이타소스 1번에서 사용되는 EntityManager 1번 객체가 관리하게 될 Entity Bean 의 패키지 위치를 지정합니다. 해당 패키지에 @Entity 어노테이션이 붙어 있는 Bean 이 대상이 됩니다.

factory.setPackagesToScan("net.jogeum.multitxtest.db1.domain");

 

TransactionManager

TransactionManager 객체는 데이타소스를 이용한 데이타의 영속성과 일관성을 보장해 주는 트랜잭션을 관리하는 객체 입니다. 데이타가 문제가 없다면 Commit 을 통해 데이타를 영속화 시키고 문제가 발생된다면 Rollback 을 통해 트랜잭션의 원자성을 보장해 주는 역할을 담당합니다.

TransactionManager 객체 역시 멀티 데이타소스 설정을 위해 별도로 생성해야 하는데 특이한 점은 Spring 에서 사용하는 기본 TransactionManagerDataSourceTransactionManager 구현체를 사용하지 않고 JpaTransactionManager 구현체를 사용한다는 점입니다.

생성된 JpaTransactionManager 에 앞서 생성한 EntityManager 를 주입해 트랜잭션이 처리될때 JPA EntityManager 객체의 데이타 관리를 포함 시켜 데이타를 관리 하겠다고 설정합니다.

@Bean
public PlatformTransactionManager transactionManager1() {
    JpaTransactionManager tm = new JpaTransactionManager();
    tm.setEntityManagerFactory(entityManagerFactory1());
    return tm;
}

 

마지막으로 이제껏 앞서 생성한 모든 Bean을 기준으로 @EnableJpaRepositories 설정을 마무리 합니다.

추가로 데이타소스 1번에서 사용되는 EntityManager 1번 객체가 관리하게 될 Repository Bean 의 패키지 위치를 지정합니다. 해당 패키지에 @Repository 어노테이션이 붙어 있는 Bean 이 대상이 됩니다.

@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactory1",
        transactionManagerRef = "transactionManager1",
        basePackages = {"net.jogeum.multitxtest.db1.repository"})

 

다음글에서는 설정한 다중데이타소스를 기준으로 트랜잭션관리를 어떻게 할지를 알아보겠습니다.


Comments