글을 작성하게 된 계기
회사에서 하나의 서버에 두 개의 데이터베이스를 운영하고 있습니다. 이를 어떻게 JPA로 매핑하는지, 어떻게 스키마를 관리했는지 정리하기 위해 글을 작성하게 되었습니다.
1. 문제 상황
현재 회사에서 하나의 데이터베이스 서버 에 여러개의 데이터베이스를 사용 하고 있습니다. 예를 들어, 다음과 같이요. hello라는 로컬 서버의 데이터베이스에 hello, world라는 두 개의 데이터베이스가 존재하는 것이죠.
물리적으로는 하나의 서버 지만, 논리적으로 다른 데이터베이스를 사용 하는 것이죠. 현재 프로젝트에서는 JPA 를 사용하고 있고, 논리적으로 다른 두 데이터베이스를 조인 하기 때문에 두 데이터베이스의 JPA 엔티티를 한 프로젝트에 매핑 해야 했습니다.
한 데이터베이스의 칼럼 값이 다른 데이터베이스에 존재하며, 연관 관계를 맺고 있었기 때문에 연관 관계 매핑, 데이터베이스 설정, 권한 등 고려할 점이 굉장히 많았습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# hello 데이터베이스에서 world의 partner_id를 가진 경우
CREATE TABLE IF NOT EXISTS hello.ms_merchants
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
biz_no VARCHAR(255),
partner_id BIGINT
);
CREATE TABLE IF NOT EXISTS world.partners
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
뿐만 아니라 통합 테스트 를 할 때, 스키마 생성 에 대해서도 이슈가 많았는데요, 두 데이터베이스의 테이블을 모두 생성 해야 했기 때문입니다. 문제를 정리하면 아래와 같은데, 이를 어떻게 해결했는지 하나씩 살펴보겠습니다.
- 서로 다른 두 데이터베이스의 JPA 엔티티를 한 프로젝트에 매핑
- 통합 테스트 실행 전, 두 데이터베이스의 스키마 생성
2. Entity 매핑
엔티티를 매핑하기 전, 먼저 스키마를 살펴보겠습니다. hello 데이터베이스에는 merchant_size_data와 ms_merchants 라는 두 개의 테이블이 존재하며, world 데이터베이스에는 partners라는 테이블이 존재합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# hello 데이터베이스
CREATE TABLE IF NOT EXISTS hello.merchant_size_data
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
merchant_size_type VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS hello.ms_merchants
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255),
biz_no VARCHAR(255),
partner_id BIGINT
);
# world 데이터베이스
CREATE TABLE IF NOT EXISTS world.partners
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
이를 다이어그램으로 보면 다음과 같습니다. 각 네모는 한 데이터베이스 단위를 나타내며, 즉, 하나의 서버에 두 개의 데이터베이스가 존재하는 것이죠. 테이블 간 연관관계가 존재하면서요.
해결책은 정말 간단한데요, 다음과 같이 엔티티에 schema 옵션을 추가해 어떤 데이터베이스의 테이블인지 명시해주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Getter
@Entity(name = "ms_merchants")
@Table(name = "ms_merchants", schema = "hello")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MsMerchantJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "biz_no")
private String bizNo;
@Column(name = "partner_id")
private Long partnerId;
}
1
2
3
4
5
6
7
8
9
10
11
12
@Getter
@Entity(name = "partners")
@Table(name = "partners", schema = "world")
public class PartnerJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
}
이는 Hibernate가 어노테이션을 기반 으로 엔티티를 빈으로 등록하며, 메타데이터를 관리하기 때문입니다. 이를 통해 같은 서버의 서로 다른 두 데이터베이스를 저장하고 관리할 수 있게 됩니다. 메타데이터를 어떻게 관리하는지에 대한 상세한 내용은 해당 포스팅을 참조해주세요.
3. 스키마 생성
통합 테스트는 데이터베이스를 포함하기 때문에 테스트 실행 전, 스키마가 생성 돼 있어야 합니다. 그런데 데이터베이스를 두 개 사용하고 있기 때문에, 조금 더 추가적인 설정이 필요한데요, 다음과 같이 어떤 데이터베이스를 사용하는지 명시 해 줘야 합니다.
1
2
CREATE TABLE IF NOT EXISTS hello.ms_merchants(......);
CREATE TABLE IF NOT EXISTS world.partners(......);
이후 테스트를 실행하기 전, @Sql 어노테이션 이나 Flyway, Liquibase 와 같은 툴을 이용해 이를 실행만 시켜주면 됩니다.
1
2
3
4
5
6
7
8
9
10
@ActiveProfiles("test")
@Sql(
scripts = "classpath:init.sql",
config = @SqlConfig(
dataSource = "lazyDataSource",
transactionManager = "transactionManager"
)
)
@SpringBootTest(classes = App.class)
public abstract class IntegrationTestBase { ...... }
처음에는 테스트 컨테이너 를 이용해 해당 스키마를 실행하려고 했는데, 권한 이슈 가 있었습니다. 망 분리 환경이다보니 테스트 컨테이너를 사용하기도 꽤 힘들었는데요, 어쩔 수 없이 도커 컨테이너를 띄워 스키마를 생성했습니다.
- Getting started with Testcontainers in a Java Spring Boot Project
- Access denied when creating a new Database (#479)
- Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation (#4545)
4. 정리
하나의 서버에 두 개의 서로 다른 데이터베이스를 사용하는 경우 발생할 수 있는 문제점과 해결책을 살펴보았습니다. 솔직히 좋은 방법이라고는 생각되지 않는데요, 어쩌겠습니까. 이미 회사에서 수 십년간 사용한 방식인데요.