프로젝트를 진행하던 중 여러개의 DB에서 데이터를 가져와야하거나 같은 DB지만 다른 스키마인 경우가 있었다.
이럴 때는 다중 Datasource를 설정해주면 된다.
코드로 구현해봤다.
mysql의 'testdb'의 student 테이블이 있다.
또 'sample'의 student_log 테이블이 있다.
이 데이터들을 하나의 프로젝트에서 쓰고 싶다.
1. 스프링 부트 프로젝트 셋업
일단 일반적인 스프링부트 프로젝트를 만들어줬다.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.12'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
mysql, lombok, jpa 만 추가해줬다.
application.yaml
spring:
jpa:
hibernate:
show-sql: true
ddl-auto: update
properties:
hibernate:
format_sql: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/testdb
username: test
password: test
일단은 student 테이블이 있는 testdb의 정보만 설정해줬다.
2. Student 정보 가져오기
미리 testdb에 'student'테이블을 만들어 값도 넣어줬다.
CREATE TABLE `student` (
`id` bigint NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3
INSERT INTO testdb.student(email,name) VALUES('hong@sample.com','홍길동');
INSERT INTO testdb.student(email,name) VALUES('gogogo@sample.com','고길동');
INSERT INTO testdb.student(email,name) VALUES('hahuhohi@sample.com','허균');
Studnet
테이블과 맞게 student 도메인을 만들어줬다.
package com.study.advanced_jpa.service.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Getter
@Setter
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
StudentRepository
jparepository를 상속받는 repository도 만들어줬다.
package com.study.advanced_jpa.service.repository;
import com.study.advanced_jpa.service.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
프로젝트가 실행되면 바로 student 정보를 가져오도록 CommandLineRunner를 implements 했다.
AdvancedJpaApplication
package com.study.advanced_jpa;
import com.study.advanced_jpa.service.domain.Student;
import com.study.advanced_jpa.service.repository.StudentRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.List;
@SpringBootApplication
public class AdvancedJpaApplication implements CommandLineRunner {
private final StudentRepository studentRepository;
public AdvancedJpaApplication(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public static void main(String[] args) {
SpringApplication.run(AdvancedJpaApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
List<Student> students = studentRepository.findAll();
System.out.println("--------[ Student ]--------");
students.forEach(s -> {
System.out.println(s.toString());
});
System.out.println("---------------------------");
}
}
이대로 프로젝트를 실행시키면 학생 정보를 findAll() 해 와 뿌려준다.
3. Student_log 정보 가져오기
이제 다중 datasource를 구성해 sample 스키마의 student_log 테이블의 값들을 가져와보자.
일단 먼저 student_log 테이블을 만들어주고 값도 넣어줬다.
CREATE TABLE `sample`.`student_log` (
`log_id` bigint NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`ip` varchar(15) DEFAULT NULL,
`log_date` datetime,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3
INSERT INTO sample.student_log(email,ip,log_date)
VALUES
('hong@sample.com', '127.0.0.1', '2023-06-17 11:27:09'),
('hong@sample.com', '127.0.0.1', '2023-06-17 11:39:19'),
('hong@sample.com', '127.0.0.1', '2023-06-17 11:47:37'),
('hong@sample.com', '127.0.0.1', '2023-06-17 11:50:11'),
('gogogo@sample.com', '127.0.0.1', '2023-06-17 11:50:13'),
('hahuhohi@sample.com', '127.0.0.1', '2023-06-17 11:52:19'),
('hahuhohi@sample.com', '127.0.0.1', '2023-06-17 12:11:11'),
('gogogo@sample.com', '127.0.0.1', '2023-06-17 13:10:13')
🌟 application.yaml 에 datasource 추가
spring:
jpa:
hibernate:
show-sql: true
ddl-auto: update
properties:
hibernate:
format_sql: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/testdb
username: test
password: test
log-datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/sample
username: test
password: test
원래 datasource와 같은 라인에 다른 이름으로 하나 더 추가해줬다.
이름은 마음대로 상관은 없지만 두개의 datasource가 중복되는 이름을 가질 수는 없다.
또 url 을 jdbc-url로 바꿔줘야 한다.
🌟 Configuration 추가
설정파일을 만들어줘야 한다.
datasource가 두개니까 두개 만들어준다.
DatasourceConfig
package com.study.advanced_jpa.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( // JPA Repository Bean 활성화
basePackages = "com.study.advanced_jpa.service",
entityManagerFactoryRef = "firstEntityManager",
transactionManagerRef = "firstTransactionManager"
)
@Configuration
public class DatasourceConfig {
@Primary // Bean 우선순위 설정
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(firstDataSource());
em.setPackagesToScan(new String[] {"com.study.advanced_jpa.service.domain"});
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource firstDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean
public PlatformTransactionManager firstTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(firstEntityManager().getObject());
return transactionManager;
}
}
- EntityManagerFactory 설정
- Datasource 설정
- Transaction 설정
우리는 Spring Data JPA를 사용하고 JPA를 통해 실제 DB와 소통할 것이기 때문에 @EnableJpaRepositories 로 JPA Repository를 활성화해줘야 한다.
basePackage를 설정해줘 스캔할 패키지를 지정해준다.
entityManagerFactoryRef 와 transactionManagerRef 를 지정해줘 이후 코드에서 사용할 수 있다.
예를 들어 어떤 메서드에 트랜잭션처리를 해주고 싶은데
@Transactional 이대로 쓰면 안되고
@Transactional(value = "firstTransactionManager") 이런식으로 불러와 사용할 수 있다.
@Primary 를 통해 bean의 우선순위를 해당 datasource에 둔다고 선언할 수 있다.
LogDatasourceConfig
package com.study.advanced_jpa.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 = "com.study.advanced_jpa.log",
entityManagerFactoryRef = "secondEntityManager",
transactionManagerRef = "secondTransactionManager"
)
@Configuration
public class LogDatasourceConfig {
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(secondDataSource());
em.setPackagesToScan(new String[] {"com.study.advanced_jpa.log.domain"});
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
@Bean
@ConfigurationProperties(prefix = "spring.log-datasource")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager secondTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(secondEntityManager().getObject());
return transactionManager;
}
}
거의 비슷하지만 @Primary가 빠졌고 스캔되는 패키지의 경로, 설정된 이름 등이 다르다.
StudentLog
package com.study.advanced_jpa.log.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Getter
@Setter
@Table(name = "student_log")
public class StudentLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "log_id")
private Long logId;
private String email;
private String ip;
@Column(name = "log_date")
private Timestamp logDate;
@Override
public String toString() {
return "StudentLog{" +
"logId=" + logId +
", email='" + email + '\'' +
", ip='" + ip + '\'' +
", logDate=" + logDate +
'}';
}
}
StudentLog 역시 테이블과 맞게 만들어준다.
LogRepository
package com.study.advanced_jpa.log.repository;
import com.study.advanced_jpa.log.domain.StudentLog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface LogRepository extends JpaRepository<StudentLog, Long> {
List<StudentLog> findAllByEmail(String email);
}
jparepository를 상속받는 repository도 만들어주고 email을 통해 findAll하는 메서드도 하나 만들어줬다.
AdvancedJpaApplication
package com.study.advanced_jpa;
import com.study.advanced_jpa.log.domain.StudentLog;
import com.study.advanced_jpa.log.repository.LogRepository;
import com.study.advanced_jpa.service.domain.Student;
import com.study.advanced_jpa.service.repository.StudentRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.List;
@SpringBootApplication
public class AdvancedJpaApplication implements CommandLineRunner {
private final StudentRepository studentRepository;
private final LogRepository logRepository;
public AdvancedJpaApplication(StudentRepository studentRepository, LogRepository logRepository) {
this.studentRepository = studentRepository;
this.logRepository = logRepository;
}
public static void main(String[] args) {
SpringApplication.run(AdvancedJpaApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
List<Student> students = studentRepository.findAll();
students.forEach(s -> {
System.out.println();
System.out.println("--------[ Student ]--------");
System.out.println(s.toString());
System.out.println("--------[ Log ]--------");
List<StudentLog> logs = logRepository.findAllByEmail(s.getEmail());
logs.forEach(l -> {
System.out.println(l.toString());
});
System.out.println("---------------------------");
});
System.out.println();
}
}
그리고 student에서 가져온 email로 log를 출력하도록 간단히 코드를 작성해주고 실행해봤다.
결과를 확인해보면...
성공적이다.
이렇게 하나의 프로젝트에서 여러개의 DB에 접근해 데이터를 가져와 사용할 수 있게 되었다.
코드
https://github.com/recordbuffer/TIL/tree/main/Spring_Boot/advanced_jpa
참고
https://www.baeldung.com/spring-data-jpa-multiple-databases
https://zzang9ha.tistory.com/439
'Backend > JPA' 카테고리의 다른 글
[JPA] 상속 관계 매핑 전략 (조인 전략 / 단일 테이블 전략 / 구현 클래스마다 테이블 전략 / MappedSuperClass) (0) | 2022.12.01 |
---|---|
[JPA] ManyToMany 연관관계 (mappedby / fetchType) (0) | 2022.10.26 |
[JPA] ManyToOne, OneToMany 연관관계 (mappedby / fetchType) (0) | 2022.10.18 |
[JPA] OneToOne 연관관계 (mappedby / fetchType) (0) | 2022.10.14 |
[JPA] Entity 생성일시 수정일시 자동 입력 방법 2가지 (@CreatedDate vs @CreationTimestamp) (0) | 2022.10.04 |
댓글