Springboot에서 MongoRepository를 상속받아 Multiple Database를 연결하고자하여 해당 부분에 대해 조사하였고, mongoTemplate과 mongoRepository의 의존성을 주입하는 과정에서 생긴 문제와 그 해결방법에 대해 작성함.
일반적으로 Spring data 프로젝트는 단일 데이터베이스 연결을 지원한다.
Spring data 데이터베이스 접근 추상화 영역인 Repository는 단일 database 접근 Client를 사용하도록 구성되어 있다.
Spinrgboot는 application.yaml에 정의된 mongodb 속성들을 기준으로 application이 실행될 때 mongodb와의 커넥션을 만든다.
Spring과 Mongodb는 MongoClient를 기준으로 연결을 맺는다. MongoClient는 mongodb에 대한 접속 정보를 가지고 TCP 연결을 맺는다.
@Configuration
public class AppConfig {
/*
* Use the standard Mongo driver API to create a com.mongodb.client.MongoClient instance.
*/
public @Bean MongoClient mongoClient() {
**return MongoClients.create("mongodb://localhost:27017");**
}
}
mongodb로의 connection은 MongoClient 인스턴스가 들고 있을 것인데, 여러개의 mongodb로 연결이 필요하니 여러개의 MongoClient 인스턴스가 필요하다는 것을 알 수 있다.
여러 개의 MongoClient 인스턴스를 생성하여 Spring IoC 컨테이너에 담아두고 필요할 때마다 사용하면 된다.
Spring data는 DB와의 직접적인 연결을 맺는 MongoClient의 생성을 직접 처리하고, MongoClient를 생성해주는 좀 더 높은 추상화 레벨을 제공한다.
@Configuration
public class AppConfig {
public @Bean MongoClient mongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
public @Bean MongoTemplate mongoTemplate() {
return new MongoTemplate**(mongoClient()**, "mydatabase");
}
}
→ MongoTemplate는 초기화 시점에 mongoClinet 인스턴스를 받는다.
따라서 Multiple mongodb connection을 위해 Spring IoC 컨테이너에 적재할 대상을 MongoTemplate으로 지정하는 것이 적절해보인다.
Spring data는 @Repository
어노테이션이 붙은 인터페이스를 기준으로 자동 생성된 CRUD 기능을 제공한다.
그런데 우리는 multiple database로 접근 하는데, 여러 Repostiory들이 각자 사용할 mongoTemplate 인스턴스를 알려주기위해 mongoTemplateRef
를 통해 bean의 이름을 지정하면 된다.
@EnableMongoRepositoreis 어노테이션 안에 mongoTemplateRef 옵션이 존재하기 때문에 반드시 선언 시점에 옵션을 지정해 주어야 하는데, @EnableMongoRepositories 어노테이션의 타겟(basePackageClasses
)은 ElementType.TYPE이다. 즉, Class, Interface, Enum 선언에만 사용할 수 있다.
따라서 multiple database configuration은 각각의 DB 마다 클래스로 선언이 되어야 한다.
사실 이 이유가 아니라면 하나의 Configuration 클래스 내에 3개의 MongoTemplate Bean을 정의할 수도 있을텐데, 현재로서는 선택권이 없어 보인다.
@Configuration
@EnableMongoRepositories(basePackageClasses = UserRepository.class, mongoTemplateRef = "primaryMongoTemplate")
@EnableConfigurationProperties
public class PrimaryConfig {
/// beans
}
MongoProperties를 사용하여 속성을 검색하고 설정한다. 이 방법으로 모든 속성을 빈에 직접 매핑한다.
@Bean(name = "primaryProperties")
@ConfigurationProperties(prefix = "mongodb.primary")
@Primary
public MongoProperties primaryProperties() {
return new MongoProperties();
}
여러 사용자에게 액세스 권한을 부여하기 위해 MongoCredential
과 함께 MongoDB 인증 매커니즘을 사용한다.
@Bean(name = "primaryMongoClient")
public MongoClient mongoClient(@Qualifier("primaryProperties") MongoProperties mongoProperties) {
MongoCredential credential = MongoCredential
.createCredential(mongoProperties.getUsername(), mongoProperties.getAuthenticationDatabase(), mongoProperties.getPassword());
return MongoClients.create(MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder
.hosts(singletonList(new ServerAddress(mongoProperties.getHost(), mongoProperties.getPort()))))
.credential(credential)
.build());
}
최신 릴리즈에서 제안한 대로 MongoTemplate을 만드는 대신 SimpleMongoClientDatabaseFactory를 사용한다.
@Primary
@Bean(name = "primaryMongoDBFactory")
public MongoDatabaseFactory mongoDatabaseFactory(
@Qualifier("**primaryMongoClient**") MongoClient mongoClient,
@Qualifier("**primaryProperties**") MongoProperties mongoProperties) {
return new SimpleMongoClientDatabaseFactory(mongoClient, mongoProperties.getDatabase());
}
@EnableMongoRepositorires
어노테이션은 basePackageClasses로 어노테이션 타겟을 명시하고, mongoTemplateRef로 어노테이션 타겟과 매핑할 MongoTemplate을 지정한다.
이때 타켓이 되는 basePackageClasses = UserRepository.class는 해당 클래스에 연결하겠다는 것이 아니라, 해당 클래스가 포함된 패키지에 연결하겠다는 것이다.
문제점
기존 파일 구성은 repository 패키지 안에 gmimdb를 참조하는 repo, admindb를 참조하는 repo를 함께 구성하였다. 이렇게 구성할 시 GmimMongoConfig, AdminMongoConfig 모두 repository 패키지를 참조하기때문에 Bean이 오버라이딩된다.
config
└── GmimMongoConfig // gmimdb를 사용하는 MongoTemplate 생성
└── AdminMongoConfig // admindb를 사용하는 MongoTemplate 생성
repository
└── DeviceInfoRepository // gmimdb와 연결해야하는 repo
└── ServiceInfoRepository // gmimdb와 연결해야하는 repo
└── UserRepository // admindb와 연결해야하는 repo
@Configuration
**@EnableMongoRepositories(basePackageClasses = {DeviceInfoRepository.class, ServiceInfoRepository.class}, mongoTemplateRef = "gmimMongoTemplate")**
@EnableConfigurationProperties
public class GmimMongoConfig { ... }
@Configuration
**@EnableMongoRepositories(basePackageClasses = UserRepository.class, mongoTemplateRef = "adminMongoTemplate")**
@EnableConfigurationProperties
public class AdminMongoConfig { ... }
config
└── GmimMongoConfig // gmimdb를 사용하는 MongoTemplate 생성
└── AdminMongoConfig // admindb를 사용하는 MongoTemplate 생성
repository
└── gmim
└── DeviceInfoRepository // gmimdb와 연결해야하는 repo
└── ServiceInfoRepository // gmimdb와 연결해야하는 repo
└── admin
└── UserRepository // admindb와 연결해야하는 repo
최근 multiple database 를 구성하며, datasource 를 두 개 설정하여야 하는 일이 있었다.
이 때 두 개의 데이터베이스 중 하나를 메인 DB, 다른 하나를 서브 DB 라고 정의하여,