몽고DB( MongoDB )는 데이터를 도큐먼트( document )로 저장하는 NoSQL 데이터베이스다.
도큐먼트는 다음 예제와 같이 JSON( 자바스크립트 객체 표기법 ) 문자열과 비슷하다.
{
_id : 5747d49f16e329249803bf47
, balance : 1000
, lastTransctionTimestamp : 2016-05-27 10:31:19
}
※ MongoDB의 특징
① 몽고DB 도큐먼트는 필드-값 쌍으로 이뤄진다.
② 도큐먼트는 관계형 데이터베이스 테이블에 저장된 레코드에 비유된다.
③ 각 도큐먼트에는 그 도큐먼트의 기본키인 _id 피르닥 있다.
④ 몽고DB는 '_id' 필드값을 자동으로 생성한다.
⑤ 컬렉션( Collection )을 만들고, 비슷한 도큐먼트를 컬렉션에 저장한다.
STEP#01. 도메인 엔티티 모델링하기
ch09-springdata-mongo 프로젝트에
몽고DB에 저장할 BankAccountDetails와 FixedDepositeDetails 도메인 엔티티 정의가 들어있다.
다음 예제는 BankAccountDetails 엔티니를 보여준다.
# 프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/domain
package sample.spring.chapter09.bankapp.domain;
import org.springframework.data.annotation.Id;
import org.springframework.mongodb.core.mapping.Document;
@Document( collection = "bankaccounts" )
public class BankAccountDetails {
@Id
private String accountId;
private int balance;
private Date lastTransactionTimestamp;
private List<FixedDepositDetails> fixedDeposits;
......
}
@Document는 BankAccountDetails 객체를 몽고DB에 영속화시킨다는 뜻이다.
스프링 데이터 몽고DB가 도메인 객체를 몽고DB 도큐먼트로 변환하거나 반대로 변환하는 과정을 알아서 처리한다.
collection 속성은 도큐먼트를 저장할 몽고DB 컬렉션 이름을 지정한다.
이는 BankAccountDetails 객체를 bankaccounts 컬렉션에 저장한다는 뜻이다.
@Id는 기본키로 사용할 필드를 지정한다.
몽고DB는 @Id를 설정할 필드값을 자동으로 생성하며, 도큐먼트에서는 _id라는 이름을 사용한다.
BankAccountDetails 객체가 0 또는 하나 이상의 FixeDepositDetails 객체와 연관될 수 있기 때문에
BankAccountDetails 객체 내부에 List<FixedDepositDetails> 타입의 속성으로 이를 정의한다.
BankAccountDetails와 FixedDepositDetails 엔티티 사이에는 부모-자식 관계가 존재한다.
FixedDepositDetails 객체는 BankAccountDetails 도큐먼트 안에 내장 도큐먼트 embedded document로 저장된다.
다음 예제는 FixedDepositDetails 엔티티를 보여준다.
# 프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/domain
package sample.spring.chapter09.bankapp.domain;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
......
public class FixedDepositDetails {
@Id
private ObjectId fixedDepositId;
private int fdAmount;
......
public FixedDepositDetails() {
this.fixedDepositId = ObjectId.get();
}
......
}
BankAccountDetails 도큐먼트 내부에 내장 도큐먼트로 저장할 것이기 때문에
FixedDepositDetails 도큐먼트에는 @Documet를 설정하지 않는다.
fixedDepositId에 @Id를 설정하지만, 내장 도큐먼트인 엔티티에는 _id 필드가 설정되지 않는다.
_id값을 바탕으로 정기 예금을 유일하게 식별하고 싶기 때문에 ObjectId의 get 메서드를 호출함으로써
fixedDepositId 필드를 명시적으로 설정한다.
ObjectId는 도큐먼트를 위해 전역 고유 식별자( GUID )를 제공한다.
다음 예제는 내장 FixedDepositDetails 도큐먼트를 포함한 BankAccountDetails 도큐먼트 하나를 보여준다.
# FixedDepositDetails를 내장한 BankAccountDetails 도큐먼트
{
_id : 5747d5a316e32925ec26372c
, _class : sample.spring.chapter09.bnakapp.domain.BankAccountDetails
, balance : 1000
, lastTransactionTimestamp : 2016-05-27 05:05:39
, fixedDeposits : [
{
_id : 5747d5a316e32925ec26372b
, fdCreationDate : 2016-05-27 05:05:39
, fdAmount : 500
, tenure : 6
, acitve : Y
}, {
_id : 5737d5a316e32925ec26372d
, fdCreationDate : 2016-05-27 05:05:39
, fdAmount : 210000
, tenure : 7
, active : Y
}
]
}
위 BSON 코드에서 최상위 도큐먼트는 BankAccountDetails 엔티티에 해당한다.
이 사실을 도메인 엔티티에 대해 전체 이름을 저장한 _class 필드가 나타낸다.
fixeDeposits 필드에는 내장 FixedDepositDetails 도큐먼트 2개가 포함되어 있다.
이제 몽고DB 데이터베이스와 상호 작용하기 위해 스프링 데이터 몽고DB를 어떻게 설정하는지 알아보자.
STEP#02. 스프링 데이터 몽고DB 설정하기 - JAVA 기반 설정
다음 예제는 @Configuration을 설정한 DatabaseConfig 클래스다.
이 클래스는 ch09-springdata-mong 프로젝트에서 스프링 데이터 몽고DB 지원을 활성화한다.
# DatabaseConfig 클래스
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp
package sample.spring.chapter09.bankapp;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import com.mongodb.MongoClient;
@Configuration
@EnableMongoRepositories( basePackages = "sample.spring" )
@EnableAsync
public class DatabaseConfig {
@Bean
public MongoClient mongoClient() {
return new MongoClient("localhost");
}
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoDbFactory(mongoClient(), "test");
}
@Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoDbFactory());
}
}
@EnableMongoRepositories 애너테이션은 애플리케이션에서 스프링 데이터 몽고DB를 활성화 한다.
basePackages 속성은 스프링 데이터 리포지터리를 찾기 위해 스캔할 패키지를 지정하고,
스프링 데이터는 속성에 지정한 패키지에서 찾은 리포지터리에 해당하는 프록시를 생성한다.
@EnableAsync 애너테이션은 스프링의 @Async 애너테이션 지원을 활성화한다.
@Bean을 설정한 mongoClient 메서드는 애플리케이션이 몽고DB 데이터베이스에 연결할 때 사용할 MongoClient 인스턴스를 만든다.
MongoClient 생성자는 몽고DB에 인스턴스가 실행중인 서버 이름을 인수로 받는다.
여기서는 몽고DB 인스턴스를 로컬에서 실행하고 있으므로 localhost를 MongoClient 생성자 인수로 넘겼다.
기본적으로 몽고DB 인스턴스가 27017번 포트에서 리슨하는 중이라고 가정한다.
몽고DB가 다른 포트를 사용한다면 mognoClient 생성자 인수로 포트 번호를 다음과 같이 추가한다.
new MongoClinet( "loclhost", 27018 );
@Bean을 설정한 mongoDbFactory 메서드는 SimpeMongoDbFactory 팩토리 인스턴스를 만든다.
SimpleMongoDbFactory는 몽고 DB에 저장된 데이터베이스에 대한 클라이언트 쪽 표현을 만들어준다.
SimpleMongoDbFactory 생성자는 MongoClient 인스턴스와
클라이언트 쪽 표현을 만들려는 몽고DB 데이터베이스 이름( 여기서는 test )을 인수로 받는다.
@Bean을 설정한 mongoDbFactory 메서드는 몽고DB에 있는 데이터베이스와 상호 작용하기 위한
연산을 제공하는 MongoTemplate 인스턴스를 반환한다.
예를 들어 MongoTemplate을 사용해 컬렉션에 저장된 도큐먼트에 대한 CRUD 연산을 수행할 수 있다.
MongoTemplate 생성자는 MongoTemplate이 상호 작용할 데이터베이스를 식별하는
MongoDbFactory 인스턴스를 인수로 받는다.
MongoTemplate 클래스는 MongoOperations 인스턴스를 구현하며,
MongoConverter 객체를 사용해서 도메인 객체를 몽고DB 도큐먼트로 변환하거나 역방향으로 변환한다.
STEP#03. Spring 데이터 MongoDB 설정하기 - XML 기반 설정
ch09-springdata-mongo 프로젝트에는 DatabaseConfig 클래스 대신
어플리케이션 설정에 사용하는 XML 파일도 들어 있다.
다음 XML 파일을 살펴보자.
# 스프링 데이터 몽고DB 설정
#프로젝트 - ch09-springdata-mongo
#META-INF/spring/applicationContext.xml
<beans ......
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="......http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
<mongo:repositories base-pakcage="sample.spring"/>
<mongo:mongo-client host="localhost" port="27017"/>
<mongo:db-factory dbname="test" mongo-ref="mongoDbFactory"/>
......
</beans>
이 예제에서 <repositories> 엘리먼트 스프링 데이터 몽고DB 리포지터리에 대한 지원을 활성화한다.
<mongo-client> 엘리먼트는 MongoClient 인스턴스를 생성하고, 인스턴스를 mongoClient 빈으로 등록한다.
<db-factory> 엘리먼트는 주어진 MongoDbFactory 인스턴스에 대한 MongoTemplate 인스턴스를 만든다.
이제 몽고DB 데이터베이스와 상호 작용하는 커스텀 스프링 데이터 리포지터리를 만드는 방법을 살펴보자.
STEP
#04. 커스텀 리포지터리 만들기
커스텀 리포지터리를 만들려면 데이터베이스에 중립적인
리포지터리 인터페이스( Repository, CrudRepository, PagingAndSortingRepository )를 사용하거나,
몽고DB용 MongoRepository 인터페이스( 스프링 데이터 몽고DB가 제공 )를 사용할 수 있다.
JpaRepository 인터페이스와 마찬가지로 MongoRepository 인터페이스도
PaggingAndSortingRepository와 QueryByExampleExecutor 인터페이스를 확장한다.
다음 예제는 스프링 데이터의 MongoRepository와 QueryslPredicateExecutor 인터페이스,
커스텀 BankAccountRepositoryCustom 인터페이스를 확장한 BankAccountRepository 인터페이스를 보여준다.
# BankAccountRepository 클래스
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/repository
package sample.spring.chapter09.bankapp.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.reposiotry.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.scheduling.annotation.Async;
......
public interface BankAccountRepository extends MongoRepository<BankAccountDetails, String>
, QuerydslPredicateExecutor<BankAccountDetails>, BankAccountRepositoryCustom {
......
List<BankAccountDetails> findByFixedDepositsTenureAndFixedDepositsFdAmount(int tenure, int fdAmoung);
@Async
CompletableFuture<List<BankAccountDetails>> findAllByBalanceGreaterThan(int balance);
@Query("{'balance' : { '$lte' : ?0 } }")
List<BankAccountDetails> findByCustomQuery(int balance);
}
BankAccountRepository는 BankAccountDetails 엔티티를 반환하는 검색 메서드를 선언한다.
스프링 데이터 JPA와 스프링 데이터 몽고DB로 개발한 리포지터리 사이에는 공통점이 아주 많다.
- 질의 메서드에 @Async를 설정해 질의를 비동기적으로 실행할 수 있다.
- 질의 메서드에 @Query를 사용해서 커스텀 질의를 지정할 수 있다.
- 질의 결과를 스트림화하기 위해 질의 메서드가 Stream<T>를 반환하게 선언할 수 있다.
- 질의 메서드가 도큐먼트에 접근할 때 페이지 단위로 접근할 수 있도록 질의 메서드에 Pageable 인수를 넘길 수 있다.
- 질의 메서드가 질의에 정렬을 추가하게 하기 위해 Sort 인수를 넘길 수 있다.
BankAccountDetails에서 FixedDepositDetails 리스트가 들어 있는 fixedDeposits 필드를 정의하기 때문에
findByFixedDepositsTenureAndFixedDepositsFdAmount를 사용하면 BankAccountDetails 객체에 들어있는
FixedDepositDetails의 tenure와 fdAmount 피드를 기준으로 BankAccountDetails를 찾을 수 있다.
이 메서드는 엔티티를 찾기 위해 내포된 프로퍼티를 활용하는 검색 메서드를 선언하는 방법을 보여준다.
이제 BankAccountRepository에 subtractFromAccount라는 커스텀 메서드를 추가하는 방법을 살펴보자.
STEP#05. Repository에 커스텀 메서드 추가하기
몽고DB Repository에도 JPA 리포지터리에 커스텀 메서드를 더할 때와 같은 과정을 거쳐서 커스텀 메서드를 추가할 수 있다.
BankAccountRepository에 subtractFromAccount 메서드를 추가할 때 사용한 절차는 다음과 같다.
- subtractFromAccount 커스텀 메서드를 선언하는 BankAccountRepositoryCustom 인터페이스를 정의한다.
- BankAccountRepositoryCustom에 대한 구현을 제공한다.
- BankAccountRepository가 BankAccountRepositoryCustom 인터페이스를 확장하게 만든다.
다음 예제는 BankAccountRepositoryCustom 인터페이스를 구현하는 BankAccountRepositoryImpl 클래스다.
#BankAccountRepositoryImpl 클래스
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/repository
package sample.spring.chapter09.bankapp.repository;
improt org.springframework.data.mongodb.core.MongoOperaions;
......
public class BankAccountRepositoryImpl implements BankAccountRepositoryCustom {
@Autowired
private MongoOperations mongoOperations;
@Override
public void subtractFromAccount(String, bankAccountId, int amount) {
BankAccountDetails bankAccountDetails =
mongoOperations.findById(bankAccountId, BankAccountDetails.class);
if(bankAccountDetails.getBalance() < amount) {
throw new RuntimException("Insufficient balance amount in bank account");
}
bankAccountDetails.setBalance(bankAccountDetails.getBalance() - amount);
mongoOperations.save(bankAccountDetails);
}
}
스프링 데이터는 자동으로 BankAccountRepositoryImpl을 선택하며
BankAccountRepositoryImpl을 다른 스프링 빈과 마찬가지로 취급한다.
자동 연결된 MongoOperations는 이전에 설정한 내용이며,
이를 사용해 BankAccountDetails의 balance 필드에서 정기 예금액을 차감한다.
이 예제는 스프링 데이터 몽고DB를 사용하는 경우에도 직접 MongoOperions를 사용해
몽고DB와 상호 작용할 수 있다는 유연성을 보여준다.
이제 Querydsl을 사용해 질의를 만드는 방법을 살펴보자.
STEP#06. Querydsl을 사용해 질의 만들기
스프링 데이터 JPA와 마찬가지로 Querydsl을 사용해 몽고DB에서 도큐먼트를 가져오는 질의를 만들 수 있다.
spring-data-mongodb JAR에는 어플리케이션의 @Document를 설정한 엔티티에 대한 메타 모델 클래스를 생성하는
MongoAnnotationProcessor 클래스( 애너테이션 프로세서 )가 들어있다.
메이븐 컴파일러 플러그인이 컴파일 시점에 MongoAnnotationProcessor를 실행해서 메타 모델 클래스를 생성한다.
ch09-springdata-mongo 프로젝트의 pom.xml을 보면 메이븐 컴파일러 플러그인을 설정한는 방법에 대해 알 수 있다.
다음 예제는 Querydsl을 사용해 현재 유효하며 예금액이 1000 이상이고 만기가 6개월에서 12개월 사이인
정기예금을 읽어오는 BankAccountServiceImpl의 getHighValueFds 메서드를 보여준다.
#BankAccountServiceImpl의 getHighValueFds 메서드
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/repository
public Iterable<BankAccountDetails> getHighValueFds() {
Predicate whereClause =
QBankAccountDetails.bankAccountDetails.fixedDeposits.any().active.eq("Y")
.and(QBankAccountDetails.bankAccountDetails.fixedDeposits.any().fdAmount.gt(1000))
.and(QBankAccountDetails.bankAccountDetails.fixedDeposits.any().tenure.between(6, 12));
retrun bankAccountRepository.findAll(whereClause);
}
QBankAccountDetails 클래스는 BankAccountDetails에 해당하는 메타 모델 클래스다.
BankAccountDetails의 fixedDeposits 필드는 FixedDepositDetails 객체의 리스트를 가리킨다.
이 질의는 BankAccountDetails에 내포된 FixedDepositDetails 컬렉션에서
active, fdAmount, tenure 필드 중 하나라도 일치하는 BankAccountDetails를 찾는다.
이때 any( ) 메서드가 적용된 필드 중 하나 이상을 BankAccountDetails가 만족하면 된다.
STEP#07. 예제를 통한 질의를 사용해 질의 만들기
MongoRepository가 QueryByExampleExecutor 인터페이스를 확장하므로 몽고DB 도큐먼트에 대해 질의하기 위해서 예제를 통한 질의를 사용할 수도 있다.
여기서는 예제를 통한 질의를 어떻게 어떤 FixedDepositDetails와도 관련이 없는 BankAccountDetails를 가져올 수 있는지 살펴본다.
다음 예제는 BankAccountDetails 클래스다.
#BankAccountDetails 클래스
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/domain
package sample.spring.chapter09.bankapp.domain;
......
@Document( collection = "bankaccounts" )
public class BankAccountDetails {
......
private List<FixedDepositDetails> fixedDeposits;
public BankAccountDetails() {
fixedDeposits = new ArrayList<>();
}
......
}
BankAccountDetails에는 FixedDepositDetails 객체의 리스트가 들어 있는 fixedDeposits 필드 정의가 있다.
BankAccountDetails 생서자는 fixedDeposits 필드를 빈 ArrayList로 초기화한다.
다음 예제는 포함된 정기 예금이 하나도 없는 BankAccountDetails를 읽는 BankAccountServiceImpl의
getAllBankAccountsWithoutFds 메서드다.
#프로젝트 - ch09-springdata-mongo
#src/main/java/sample/spring/chapter09/bankapp/service
public Iterable<BankAccountDetails> getAllBankAccountsWithoutFds() {
BankAccountDetails bankAccountDetails = new BankAccountDetails();
ExampleMatcher matcher =
ExampleMatcher.matching().withIgnorePaths("accountId", "balance", "lastTransactionTimestamp");
Example<BankAccountDetails> example = Example.of(bankAccountDetails, matcher);
return bankAccountRepository.findAll(example);
}
정기 예금이 하나도 없는 BankAccountDetails를 얻기 위해, 빈 fixedDeposits 리스트가 있는 BankAccountDetails 인스터를 만든다.
BankAccountDetails에 대해 질의할 때 accountId, balance, lastTransctionTimestamp 필드를 고려하지 않을 것이므로
ExampleMatcher를 사용해서 세 필드를 무시하도록 지정한다.
출처 : 배워서 바로쓰는 스프링 프레임워크
'Spring' 카테고리의 다른 글
[Spring] 로그 출력하기 (0) | 2020.12.16 |
---|---|
[Spring] 자동화 빌드 도구 메이븐의 특징과 개념 (0) | 2020.12.14 |
[Spring] 메이븐 웹 어플리케이션 생성하기 (0) | 2020.12.12 |
[Spring] Spring Tool Suit 다운 및 설정 (0) | 2020.10.23 |