728x90
💡 회사에서 새롭게 서버를 리팩터링 하는 도중에 하나의 프로젝트에 2개 이상의 데이터베이스를 연결해야 했다. 구글링을 통해 검색해 본 결과 생각보다 간단하게 할 수 있었다. 집에 와서 프로젝트를 간단하게 요약하여 재구성한 후 테스트를 진행해보려고 한다.
Spring Initializr
- Spring Initializr 설정이다.
- 원래 H2로도 테스트를 해보고 싶었는데 못해서 의미가 없게 됐다.
프로젝트 패키지 구조
- 이 테스트의 핵심은 데이터베이스 2개를 하나의 프로젝트에 연결하고, 패키지 별로 사용 db를 나눈 후에 이를 기준으로 정상적으로 데이터베이스와 해당 패키지의 클래스가 연결되는지 보는 것이다.
- 따라서 db1(main), db2(second) 서버와 연결할 각각의 Entity와 Repository 만들었다.
- 또, Entity&Repository와 각 데이터베이스의 연결을 담당할 configuration 클래스도 각각 만들었다.
application.yml
#mysql - db1
spring:
datasource:
hikari:
db1:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db1?&characterEncoding=UTF-8
username: mysql username 입력
password: 비밀번호 입력
#mysql - db2
db2:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db2?&characterEncoding=UTF-8
username: mysql username 입력
password: 비밀번호 입력
jpa:
hibernate:
ddl-auto: create # 애플리케이션이 정상 작동하면 기존 테이블을 지우고 엔티티를 읽어 새로 만듬
properties:
hibernate:
show_sql: true # sql문이 db로 날라가는 것을 system.out으로 확인 가능
format_sql: true # 한줄이 아닌 여러 줄로 나뉘어 sql문의 가독성을 높여서 볼 수 있음
- 두 개의 DB를 연결해 주었다.
- 이제 confiuration 클래스를 만들어 Repository와 Entity 별로 원하는 DB와 연결하기 위한 작업을 진행해야 한다.
db1Config
package Test.MultiDb.config;
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.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 javax.sql.DataSource;
@EnableJpaRepositories(
basePackages = "Test.MultiDb.db1Repository", // 적용할 repository 경로
entityManagerFactoryRef = "db1EntityManager", // (1) 아래 메서드 명과 일치해야 함
transactionManagerRef = "db1TransactionManager" // (2) 아래 메서드 명과 일치해야 함
)
@Configuration
public class db1Config {
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean db1EntityManager() { // (1)
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(db1DataSource());
em.setPackagesToScan(new String[] {"Test.MultiDb.db1Entity"});
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.db1") // 적용할 Entity 경로
protected DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean
public PlatformTransactionManager db1TransactionManager() { // (2)
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(db1EntityManager().getObject());
return transactionManager;
}
}
- db1 데이터베이스와 db1의 Entity & Repository와 연결을 설정하는 클래스이다.
@EnableJpaRepositories
- JPA Repository Bean을 활성화한다.
- basePackages를 통해 적용할 레파지토리의 패키지를 여러 곳 적을 수 있다. db별로 Repository를 나누어 관리해야 함을 의미한다.
@Primary
- 동일한 인터페이스를 구현하거나 동일한 클래스를 확장하는 빈 객체가 여러 개일 경우 Primary 애너테이션이 붙은 빈이 우선순위를 갖는다.
- 여기서는 예시로 db1을 메인 db로 둔다는 가정을 했기 때문에 명시했다.
LocalContainerEntityManagerFactoryBean
- Datasource, Hibernate Property, Entity가 위치한 Package를 지정한다.
- Hibernate 기반으로 동작을 지정하는 JpaVendor를 설정한다.
- Hibernate vendor와 JPA 간 Adapter를 설정한다.
- Hibernate vendor는 Hibernate 관련 속성 및 동작을 담당한다.
@ConfigurationProperties
- *.properties, *.yml 파일에 있는 설정을 자바클래스에 가져와 사용할 수 있게 한다.
Datasource
- 사용자 지정 데이터 소스를 사용하기 위해 정의한다.
PlatformTransactionManager
- DB 연동에 따라 각각의 구현 클래스가 제공된다.
- @Transactional이 포함된 메서드가 호출될 경우에 PlatformTransactionManager를 사용하여 트랜잭션을 시작하고 정상 작동 여부에 따라 Commit 또는 Rollback을 한다.
db2Config
package Test.MultiDb.config;
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.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 javax.sql.DataSource;
@EnableJpaRepositories(
basePackages = "Test.MultiDb.db2Repository",
entityManagerFactoryRef = "db2EntityManager",
transactionManagerRef = "db2TransactionManager"
)
@Configuration
public class db2Config {
@Bean
public LocalContainerEntityManagerFactoryBean db2EntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(db2DataSource());
em.setPackagesToScan(new String[] {"Test.MultiDb.db2Entity"});
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.db2")
protected DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager db2TransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(db2EntityManager().getObject());
return transactionManager;
}
}
- db1 데이터베이스와 db1의 Entity & Repository와 연결을 설정하는 클래스이다.
- 자세한 설명은 dbConfig2로 대신한다.
DB1 & DB2 Entity
package Test.MultiDb.db1Entity;
import lombok.Getter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
public class DB1 {
@Id
@GeneratedValue
private Long id;
private String db1Col1;
private String db1Col2;
private String db1Col3;
private String db1Col4;
}
//-------------------------------------------
package Test.MultiDb.db2Entity;
import lombok.Getter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
public class DB2 {
@Id
@GeneratedValue
private Long id;
private String db2Col1;
private String db2Col2;
private String db2Col3;
private String db2Col4;
}
- db1, db2 테이블과 매핑할 엔티티이다.
package Test.MultiDb.db1Repository;
import Test.MultiDb.db1Entity.DB1;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface Db1Repository extends JpaRepository<DB1, Long> {
}
//----------------------------------------------------------------
package Test.MultiDb.db2Repository;
import Test.MultiDb.db2Entity.DB2;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface Db2Repository extends JpaRepository<DB2, Long> {
}
- JPaRepository를 상속받은 DB1Repository, DB2Repository이다.
MySQL DB 설정
- MySQL 내 테이블에 데이터가 들어있는 상황을 가정했다.
- 데이터베이스와 테이블을 만들고 스텁 데이터를 넣는 작업을 했다.
create database db1;
create database db2;
- create 명령문으로 데이터베이스 생성
use db1;
show tables;
- use db1 명령문 이용하여 db1 데이터베이스 접속
- show tables 명령문을 이용하여 db1 데이터베이스 내 테이블 확인(비어있음)
- create table 명령문으로 db1 데이터베이스에 db1 테이블을 생성
- insert into db1 명령문을 이용해 db1 테이블에 스텁데이터 삽입
- db2 데이터베이스에도 동일하게 db2 테이블 생성 후 스텁데이터 삽입
테스트코드 작성
package Test.MultiDb;
import Test.MultiDb.db1Entity.DB1;
import Test.MultiDb.db1Repository.Db1Repository;
import Test.MultiDb.db2Entity.DB2;
import Test.MultiDb.db2Repository.Db2Repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class MultiDbApplicationTests {
@Autowired
private Db1Repository db1Repository;
@Autowired
private Db2Repository db2Repository;
@Test
void init() {
testDB1();
System.out.println("----------------------------------");
testDB2();
}
void testDB1() {
List<DB1> all = db1Repository.findAll();
for (DB1 db1 : all) {
System.out.println(db1.getId());
System.out.println(db1.getDb1Col1());
}
}
void testDB2() {
List<DB2> all = db2Repository.findAll();
for (DB2 db2 : all) {
System.out.println(db2.getId());
System.out.println(db2.getDb2Col1());
}
}
}
- 준비가 끝났다. 테스트를 진행해 보자.
- init() 테스트를 실행하면 testDB1()와 textDB2()가 차례로 실행된다.
- 각각의 레파지토리에서 스텁데이터를 가져와 id와 첫 번째 칼럼의 데이터를 출력하는 테스트이다.
- 정상적으로 작동한다면 서로 다른 db인 db1과 db2에서 데이터를 가져와 '-----' 선을 기준으로 id와 칼럼에 들어있는 스텁데이터가 출력될 것이다.
- 양쪽 db에 동일한 데이터를 같은 개수로 넣었으니 정상적으로 테스트가 작동한다면 동일한 출력문이 2번 반복될 것이다.
출력 화면
- 하나의 프로젝트로 양쪽 db에 접근하여 데이터를 다룰 수 있음이 확인되었다.
자세한 코드는 깃허브에 올려두었다.
https://github.com/wonyongg/test/tree/main/MultiDb
참고
728x90
'[JAVA] > JAVA 기본' 카테고리의 다른 글
내부 클래스를 static으로 선언해야 하는 이유 (2) | 2023.06.09 |
---|---|
try - catch 문을 여러 경우의 수로 한 번에 이해해보자. (0) | 2023.05.24 |
DTO 클래스 ↔︎ Entity 클래스 Mapping에 대한 정리(ModelMapper, Mapstruct, 수동매핑) (0) | 2023.05.08 |
JVM과 자바 메모리 구조 간단 요약 정리 (0) | 2023.05.05 |
자바에서의 문자열 비교 ==, equals의 차이 (0) | 2023.03.24 |