스프링부트

spring data MongoDB - Repository 사용하기

알쓸개잡 2024. 2. 20.

 

Spring Data MongoDB는 Spring 애플리케이션 내에서 데이터 액세스를 단순화하는 것을 목표로 하는 Spring Data 프로젝트의 일부이다. MongoDB와의 원활한 통합을 제공하여 CRUD 작업을 추상화하여 데이터베이스와 상호작용하는 생산적인 방법을 제공한다.

Repository는 Spring Data 프레임워크의 핵심이며 데이터 액세스 계층에 대한 높은 수준의 추상화를 제공한다. 이번 포스팅에서는 Spring Data MongoDB에서 Repository를 이용하여 간단한 CRUD 작업을 수행하는 방법에 대해서 샘플 기반으로 소개하고자 한다.

 

Core concepts

Spring Data Repository 추상화의 중심 인터페이스는 Repository다. Repository 인터페이스는 관리할 도메인 클래스와 도메인 클래스의 식별자(Primary Key) 타입을 Generic 인수로 받는다. Repository 인터페이스는 주로 작업할 유형을 캡처하고 이 인터페이스를 확장하는 인터페이스를 발견하는데 도움이 되는 마커 인터페이스 역할을 한다. CrudRepository 및 ListCrudRepository 인터페이스는 관리 중인 도큐먼트 클래스에 대한 조금 더 상세한 CRUD 기능을 제공한다.

ex) CrudRepository<T, ID>

public interface CrudRepository<T, ID> extends Repository<T, ID> {

  // 전달된 entity 를 저장한다.
  <S extends T> S save(S entity);

  // 전달된 primaryKey 값으로 데이터를 조회한다.
  Optional<T> findById(ID primaryKey);

  // 모든 entity 클래스를 리턴한다.
  Iterable<T> findAll();

  // 엔티티의 수(도큐먼트 수)를 반환한다.
  long count();

  // 전달된 entity 를 삭제한다.
  void delete(T entity);

  // 전달된 primaryKey를 가진 entity 존재여부를 체크한다.
  boolean existsById(ID primaryKey);

  // … more functionality omitted.
}

CrudRepository를 확장한 ListCrudRepository나 PagingAndSortingRepository를 제공하여 조금 더 상세한 컨트롤을 할 수 있도록 지원한다.

 

엔티티 상태 감지 전략

아래 표는 Spring Data가 새로운 엔티티인지 여부를 감지하기 위해 제공하는 전략에 대한 설명이다.

@Id 프로퍼티 검사(default) 기본적으로 Spring Data는 주어진 엔티티의 식별자 속성을 검사한다. 식별자 속성이 null 이거나 기본 타입의 경우 0 이면 엔티티가 새로 생성된 것으로 간주한다. 그렇지 않은 경우 새 엔티티가 아닌 것으로 간주한다.
@Version 프로퍼티 검사 @Version 어노테이션이 붙은 속성이 존재하고 해당 속성이 null 이거나 해당 속성이 기본 타입이고 값이 0 인 경우 엔티티는 새로 생성된 것으로 간주한다. 버전 속성이 존재하지만 값이 다른 경우 엔티티는 새 엔티티가 아닌 것으로 간주한다. 버전 속성이 없는 경우 Spring 데이터는 식별자 속성 검사 (@Id 프로퍼티 검사) 로 돌아간다.
Persistable 구현 엔티티가 Persistable을 implement 하는 경우 엔티티 클래스의 isNew(..) 메서드에 새 탐지를 위임한다. 
isNew(..) 메서드의 return 이 true 이면 엔티티가 새로 생성된 것으로 간주하고 그렇지 않으면 새 엔티티가 아닌 것으로 간주한다.
사용자 지정 EntityInformation 구현 Repository Factory 의 subclass 를 생성하고 getEntityInformation(...) 메서드를 재정의하여 EntityInformation 추상화를 사용자 지정할 수 있다. Repository Factory 의 사용자 정의 구현을 Spring 빈으로 등록한다. 이 작업은 거의 사용되지 않음.

Persistable 구현의 예

@Document
@Getter
@Setter
public abstract class Auditable implements Persistable<UUID> {

    @CreatedBy
    private String createdBy;

    @CreatedDate
    private Instant createdDate;

    @LastModifiedBy
    private String lastModifiedBy;

    @LastModifiedDate
    private Instant lastModifiedDate;

    @Override
    public boolean isNew() {
        return createdDate == null;
    }
}

Persistable 인터페이스를 구현하는 Auditable 은 감사로그 역할을 하면서 isNew(...) 메서드를 재정의 하여 엔티티가 새로 생성되었는지 여부를 isNew(...) 메서드에서 결정하도록 한다.

위 예에서는 createdDate 필드에 값이 지정되지 않은 경우에는 새로 생성된 엔티티로 간주하도록 한다.

엔티티 상태 감지 전략은 감사로그와도 관련이 있다.

엔티티 상태 감지 전략에 의해서 새로운 엔티티로 감지되면 createdBy, lastModifiedBy 필드가 업데이트 되고 아닌 경우 lastModifiedBy 필드만 업데이트된다.

 

Repository Interface 정의

일반적으로 CRUD 기능을 위한 메서드를 제공하는 CrudRepository를 확장하여 Repository Interface를 정의한다. 버전 3.0에서는 CrudRepository와 유사하지만 여러 엔티티를 반환하는 메서드의 경우 사용하기 더 쉬운 Iterable 대신 List를 반환하는 ListCrudRepository가 도입되었다.

MongoDB의 경우 MongoRepository 인터페이스가 제공되는데 ListCrudRepository, ListPagingAndSortingRepository, QueryByExampleExecutor 인터페이스를 상속한다.

이를 통해 MongoRepository를 상속하여 Repository를 정의하면 페이징 처리 및 정렬, 여러 엔티티를 리턴하는 메서드를 사용하는 경우 Iterable 타입 보다 사용성이 쉬운 List 타입을 사용할 수 있게 된다.

다음은 MongoRepository, ListCrudRepository, CrudRepository 클래스 정의다.

@NoRepositoryBean
public interface MongoRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    <S extends T> S insert(S entity);

    <S extends T> List<S> insert(Iterable<S> entities);

    <S extends T> List<S> findAll(Example<S> example);

    <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

----------------------------------------------

@NoRepositoryBean
public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> {
    <S extends T> List<S> saveAll(Iterable<S> entities);

    List<T> findAll();

    List<T> findAllById(Iterable<ID> ids);
}

---------------------------------------------

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);

    <S extends T> Iterable<S> saveAll(Iterable<S> entities);

    Optional<T> findById(ID id);

    boolean existsById(ID id);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> ids);

    long count();

    void deleteById(ID id);

    void delete(T entity);

    void deleteAllById(Iterable<? extends ID> ids);

    void deleteAll(Iterable<? extends T> entities);

    void deleteAll();
}

CrudRepository의 findAll(...), findAllById(...) 메서드는 Iterable 타입을 반환하는 반면 ListCrudRepository의 findAll(...), findAllById(...) 메서드는 List 타입을 반환하도록 하여 Iterable 타입 대신에 List 타입을 사용할 수 있도록 하였다.

 

Repository를 정의할 때 보통 Spring Data에서 제공하는 Repository를 상속하면서 구체적인 엔티티 타입과 ID 타입을 지정해 주어야 한다.

@Repository
public interface MailSenderRepository extends MongoRepository<MailSenderDocument, UUID> {

    Optional<MailSenderDocument> findByAddress(String address);
    ...
}

MailSenderRepository는 MailSenderDocument 엔티티를 관리하는 Repository 역할을 한다. ID 타입은 UUID 타입임을 의미한다.

 

@NoRepositoryBean 어노테이션

@NoRepositoryBean 어노테이션은 런타임에 Repository 인터페이스의 인스턴스를 생성하는 것을 방지하는 데 사용된다.

여러 Repository에서 공통된 메서드 집합을 사용하고자 하는 경우 공통된 메서드 집합을 정의한 Repository에 보통 사용한다. 여러 Repository에서 공통으로 사용하는 메서드 집합을 정의해야 하므로 Generic 클래스로 정의한다.

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

---------------------------------------------

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

MyBaseRepository는 여러 Repository에서 공통으로 사용할 findById(...), save(...) 메서드를 정의한다. 여러 Repository에서 공통으로 사용할 메서드를 정의하였기 때문에 엔티티 타입, ID 타입 위치에는 구체화된 타입이 아닌 Generic 타입이 정의된다.

MyBaseRepository 인터페이스에 @NoRepositoryBean 어노테이션을 정의하여 Spring Data에 이 인터페이스에 대한 Repository 빈을 생성하지 않게 된다.

UserRepository는 MyBaseRepository<User, Long> 를 상속하기 때문에 다음과 같이 정의된 공통 메서드를 사용할 수 있다.

  • Optional<User> findById(Long id);
  • <S extends User> S save(S entity);

 

@RepositoryDefinition 어노테이션

@RepositoryDefinition 어노테이션은 CrudRepository와 같은 Spring Data Repository 인터페이스를 확장하지 않고자 하는 경우에 사용한다. 

@RepositoryDefinition 어노테이션은 다음과 같은 경우에 유용하다.

  • 최소한의 Repository 정의
    • 최소한의 작업 집합으로 Repository를 정의하고 표준 Spring Data Repository 인터페이스에서 제공하는 메서드를 상속하지 않으려는 경우
  • 사용자 정의 Repository 동작
    • Spring Data Repository 인터페이스 확장을 통해 제공되는 기본 동작이 요구 사항에 맞지 않고 노출된 메서드에 대해 더 많은 제어가 필요한 경우
  • 선택적 메서드 노출
    • 노출되는 Repository 대해서 선택적으로 메서드를 사용하고자 하는 경우
@Document(collection = "animal")
public class AnimalDocument {
	...
}

------------------------------------------------------

@RepositoryDefinition( domainClass = AnimalDocument.class, idClass = UUID.class )
public interface AnimalRepository {
    Optional<AnimalDocument> findById(UUID id);
    <S extends AnimalDocument> S save(S animalDocument);
}

AnimalRepository에 정의된 @RepositoryDefinition 어노테이션은 AnimalDocument에 대한 저장소로 AnimalRepository를 명시적으로 선언하는 데 사용된다. domainClass 속성은 Repository가 관리하는 엔티티(Document) 타입을 지정하고 idClass 속성은 엔티티의 식별자 타입을 지정한다.

AnimalRepository는 Spring Data에서 제공하는 Repository를 상속하지 않았기 때문에 findById(...)와 save(...) 메서드만 사용할 수 있다.

@RepositoryDefinition 어노테이션을 통해서 특정 작업만을 허용하는 Repository를 정의할 수 있다.

 

MongoDB Repository 빈 생성

Repository 사용을 시작하려면 @EnableMongoRepositories 어노테이션을 사용한다.

@EnableMongoRepositories 어노테이션을 통해서 빈으로 생성할 Repository를 스캔하도록 설정하는 몇 가지 속성이 있다.

basePackages 속성

EnableMongoRepositories 어노테이션에 basePackages 속성으로 지정된 package 네임스페이스 경로에서 빈으로 생성할 Repository를 스캔한다.

@Configuration
@RequiredArgsConstructor
@Slf4j
@EnableMongoRepositories(basePackages = "com.example.springmongodb.repository")
public class MongoDBConfigurationByDirect {
	...
}

위 코드는 com.example.springmongodb.repository 패키지에서 빈으로 생성할 Repository를 스캔한다.

basePackages 속성이 지정되지 않은 경우 EnableMongoRepositories 어노테이션이 지정된 Configuration 클래스의 패키지를 검색한다.

basePackages 속성은 여러 패키지 경로를 설정할 수 있다.

@EnableMongoRepositories(
	basePackages = {
		"com.example.springmongodb.repository",
		"com.example.springmongodb.otherrepository"
    }
)

또한 지정된 패키지 경로에 와일드카드를 지정할 수도 있다.

@EnableMongoRepositories(basePackages = "com.example.*.repository")

와일드카드 패턴 사용은 개별적으로 패키지 경로를 나열하지 않고 여러 하위 패키지를 검색하는데 유용할 수 있지만 원치 않는 패키지가 의도치 않게 포함될 수 있으므로 사용에 주의가 필요하다.

와일드카드 패턴에 대한 실제 지원은 Spring 버전과 사용되는 콘텍스트에 따라 달라질 수 있다. 일반적으로 명확성을 위해서 패키지 이름이나 클래스 참조를 사용하고 검사 대상을 정확하게 지정하는 것이 좋다.

basePackageClasses 속성

basePackageClasses 속성은 클래스 참조를 통해서 검색할 패키지를 지정할 수 있다.

@EnableMongoRepositories(
	basePackageClasses = {MyRepository.class, AnotherRepository.class}
)

MyRepository.class, AnotherRepository.class의 패키지를 Repository 스캔을 위한 기본 패키지로 사용한다.

 

샘플코드

Dependency

gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}

 

maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

 

 

문서 정의 (Document)

@Document(collection = "person")
@Getter
@ToString
public class PersonDocument {
    @Id
    private final UUID personId;
    private final String personName;
    private final Integer personAge;
    @Setter private Gender personGender;
    @Setter private String personNation;
    private final String comment;


    @Builder
    public PersonDocument( UUID personId,
                           String personName,
                           Integer personAge,
                           Gender personGender,
                           String personNation,
                           String comment) {
        this.personId = personId;
        this.personName = personName;
        this.personAge = personAge;
        this.personGender = personGender;
        this.personNation = personNation;
        this.comment = comment;
    }
}

 

Repository 인터페이스 생성

@Repository
public interface PersonRepository extends MongoRepository<PersonDocument, UUID> {
    /*
    성별에 따른 Document 리스트를 추출 한다.
     */
    List<PersonDocument> findByPersonGender( Gender gender );

    /*
    personAge 필드의 값이 파라미터 age 보다 큰 Document 리스트를 추출 한다.
     */
    @Query("{" +
            "'personAge': { $gt: ?0 }" +
            "}")
    List<PersonDocument> findByPersonAgeGreaterThan(Integer age);
    
    /*
    성별에 따른 Document 페이지 목록을 추출 한다.
     */
    Page<PersonDocument> findByPersonGender( Gender gender, Pageable pageable );
}

MongoRepository의 Generic Type으로 처리하고자 하는 Document 클래스와 ID 타입을 전달한다.

기본 Repository에서 지원하는 키워드는 아래 링크를 참고하기 바란다.

https://docs.spring.io/spring-data/mongodb/reference/repositories/query-keywords-reference.html

 

Repository query keywords :: Spring Data MongoDB

The following table lists the predicate keywords generally supported by the Spring Data repository query derivation mechanism. However, consult the store-specific documentation for the exact list of supported keywords, because some keywords listed here mig

docs.spring.io

 

Repository 인터페이스의 주요 기능

  • 추가 코드 없이 MongoRepository를 상속하면 CRUD 기능이 제공된다.
  • @Query 어노테이션을 이용하여 사용자 정의 쿼리 사용이 가능하다. @Query 어노테이션을 통해서 네이티브 형태의 쿼리 조회가 가능하다.
  • @Update 어노테이션을 이용하여 조회된 도큐먼트에 대해서 데이터 업데이트가 가능하다.
  • 페이징과 정렬 기능 사용이 가능하다.

 

Repository를 이용한 데이터 업데이트

샘플코드에는 Repository에 @Update 어노테이션을 사용하는 예시가 없어서 Spring 공식 문서에 있는 내용을 참고하였다.

public interface PersonRepository extends CrudRepository<Person, String> {

    @Update("{ '$inc' : { 'visits' : 1 } }")
    long findAndIncrementVisitsByLastname(String lastname); //---> 1

    @Update("{ '$inc' : { 'visits' : ?1 } }")
    void findAndIncrementVisitsByLastname(String lastname, int increment); //---> 2

    @Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
    long findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment); //---> 3

    @Update(pipeline = {"{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }"})
    void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment); //---> 4

    @Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
    long findAndPushShippingAddressByEmail(String email, Address address); //---> 5

    @Query("{ 'lastname' : ?0 }")
    @Update("{ '$inc' : { 'visits' : ?1 } }")
    void updateAllByLastname(String lastname, int increment); //--->6
}

--->1 : 업데이트에 대한 필터 쿼리는 메서드 이름에서 파생된다. 즉, find~ByLastname(String lastname)과 같이 lastname 필드에 대한 필터 쿼리를 수행한 결과에 visit 필드의 값을 1 증가시키는 동작을 한다.

--->2 : visits 필드에 추가되는 증분 값은 1번째 파라미터인 increment의 값으로 '?1'이 대체된다. (placeholder는 ?0 부터 시작)

--->3 : 매개변수 바인딩에 SpEL 표현식을 사용할 수 있다.

--->4 : 파이프라인 속성을 사용하여 aggregation 업데이트를 수행한다.

--->5 : email 필드에 대한 필터 쿼리를 수행하여 나온 Document에서 array field인 shippingAddress에 Address 타입의 element를 추가한다. $push 연산자는 array field에서 사용가능한 연산자다.

--->6 : @Query 어노테이션과 결합하여 사용할 수 있다. @Query 어노테이션을 통해 lastname 필드가 일치하는 문서를 필터링하여 visits 필드의 값을 1 증가시키는 작업을 수행한다.

 

사용 코드

Service 구현

100개의 PersonDocument를 생성한다.

@Service
@RequiredArgsConstructor
@Slf4j
public class MongoDBService {
    private final PersonRepository personRepository;

    public void savePersonByRepository100() {
        Stream.iterate( 0, seed -> seed + 1 ).limit( 100 )
                .forEach( index -> {
                    PersonDocument personDocument = PersonDocument.builder()
                            .personId( UUID.randomUUID() )
                            .personName( STR."test-name\{index}" )
                            .personAge( index )
                            .personGender( index % 2 == 0 ? Gender.MALE : Gender.FEMALE )
                            .comment( STR."test-name 코멘트\{index}"  )
                            .build();
                    personRepository.save( personDocument );
                } );
    }

    public List<PersonDocument> findByGender(Gender gender) {
        return personRepository.findByPersonGender( gender );
    }
    
    public List<PersonDocument> findByAgeGreaterThan(Integer age) {
        return personRepository.findByPersonAgeGreaterThan( age );
    }

    public Page<PersonDocument> listByGenderPageable( Gender gender, Pageable pageable ) {
        return personRepository.findByPersonGender( gender, pageable );
    }
}

savePersonByRepository100() 메서드를 호출하여 100개의 PersonDocument를 저장한다.

Document를 저장하는 메서드는 save()와 insert() 메서드가 제공되는데 이 둘의 차이점은 다음과 같다.

Repository를 통하여 document를 저장하는 메서드는 save()와 insert()가 있는데 둘의 차이는 키의 중복 체크 여부이다.
save()의 경우 동일한 키를 가진 document가 이미 존재한다면 해당 document를 업데이트하지만 insert()의 경우에는 동일한 키를 가진 document가 존재하는 경우 duplicated key exception 이 발생한다.

 

성별에 따른 document 조회

personGender 필드가 'MALE' 값을 가지는 Document 리스트 조회

@Autowired
private MongoDBService mongoDBService;

...

mongoDBService.savePersonByRepository100();
List<PersonDocument> malePersonList = mongoDBService.findByGender(Gender.MALE);

 

지정된 Age 이상의 값을 가지는 document 조회

personAge 필드의 값이 90보다 큰 값을 가지는 Document 리스트 조회

@Autowired
private MongoDBService mongoDBService;

...

mongoDBService.savePersonByRepository100();
List<PersonDocument> byAgeGreaterThan = mongoDBService.findByAgeGreaterThan( 90 );

 

페이지 출력을 위한 document 조회

Repository는 Pageable 객체를 통해서 출력할 페이지 번호, 출력 Document 개수, 정렬을 관리한다.

 

직접 Pageable 객체 생성

직접 Pageable 객체를 지정된 개수만큼 Document를 조회할 수 있다.

샘플 코드는 다음과 같다.

@Autowired
private MongoDBService mongoDBService;

....

mongoDBService.savePersonByRepository100();

Pageable pageable =
	PageRequest.of( 0, 10, Sort.by( Sort.Direction.ASC, "personAge") );
                
Page<PersonDocument> personDocuments;
do {
    log.info( "pageable: {}", pageable );
    personDocuments = mongoDBService.listByGenderPageable( Gender.FEMALE, pageable );
    if ( !personDocuments.isEmpty() ) {
        personDocuments.forEach( femalePerson -> log.info( "person: {}", femalePerson ) );
    }
    pageable = pageable.next();
} while ( !personDocuments.isEmpty() );

Pageable 객체는 PageRequest.of() 메서드를 통해서 직접 생성할 수 있다. 페이지 번호는 0부터 시작한다.

샘플 코드에서는 0번째 페이지부터 시작하여 10개씩 Document를 조회하며 personAge 필드를 기준으로 오름차순으로 정렬하여 데이터를 가져오도록 한다.

pageable.next()를 호출하여 다음 페이지에 해당하는 Document를 가져오는데 pageable.next() 메서드 코드는 다음과 같다.

public PageRequest next() {
    return new PageRequest(this.getPageNumber() + 1, this.getPageSize(), this.getSort());
}

현재 page number에서 1을 더하여 다음 페이지를 나타내며 page size와 sort 값은 그대로 세팅하는 것을 알 수 있다.

위 샘플 코드의 실행 결과는 다음과 같다.

person: PersonDocument(personId=a9401b16-b107-4459-bc6a-cc203bddc096, personName=test-name1, personAge=1, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트1)
person: PersonDocument(personId=9ef81259-b2d6-44b7-89ab-fd6c3156f298, personName=test-name3, personAge=3, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트3)
person: PersonDocument(personId=0983d70f-b3bf-4188-9fda-4f0b093caa24, personName=test-name5, personAge=5, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트5)
person: PersonDocument(personId=ceacfb11-dd2e-40af-8071-36ef1c33a9ab, personName=test-name7, personAge=7, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트7)
person: PersonDocument(personId=2c99d15f-986c-4eb4-8740-265c7a59159c, personName=test-name9, personAge=9, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트9)
person: PersonDocument(personId=180e4d4d-037d-469e-8eca-24488d717d86, personName=test-name11, personAge=11, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트11)
person: PersonDocument(personId=f5803c3d-bc1d-40d2-be7d-948f0512fc6f, personName=test-name13, personAge=13, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트13)
person: PersonDocument(personId=26be5421-8882-453c-8a61-dc4280cec7e8, personName=test-name15, personAge=15, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트15)
person: PersonDocument(personId=bf99a67c-6df4-40e8-96e2-47c7d15dda25, personName=test-name17, personAge=17, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트17)
person: PersonDocument(personId=09e926f4-b2f5-4a64-82af-dea0216ce732, personName=test-name19, personAge=19, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트19)
...
...
...
person: PersonDocument(personId=9cc87ec2-832c-4073-b285-85e811675c55, personName=test-name81, personAge=81, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트81)
person: PersonDocument(personId=777056c4-d39c-4b92-ae27-c7ba639c8287, personName=test-name83, personAge=83, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트83)
person: PersonDocument(personId=a2e25bb0-4296-4cd4-83c6-5fecd17c2404, personName=test-name85, personAge=85, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트85)
person: PersonDocument(personId=35754787-6a63-4f51-8d45-df78196aedc3, personName=test-name87, personAge=87, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트87)
person: PersonDocument(personId=988b3156-060b-4182-b513-36839f381af3, personName=test-name89, personAge=89, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트89)
person: PersonDocument(personId=ba74b13e-2fcc-47d3-803c-5031de73d02d, personName=test-name91, personAge=91, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트91)
person: PersonDocument(personId=ec76d94c-fccc-4064-969c-f91116a87991, personName=test-name93, personAge=93, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트93)
person: PersonDocument(personId=52ff4b27-cda9-45ab-af8b-f32f27693068, personName=test-name95, personAge=95, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트95)
person: PersonDocument(personId=64ef7367-1f3f-4a2b-bcb0-09afbc771457, personName=test-name97, personAge=97, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트97)
person: PersonDocument(personId=c7178a5f-79aa-44a4-85a2-3e0bb343e5bd, personName=test-name99, personAge=99, personGender=FEMALE, personNation=Korea, comment=test-name 코멘트99)

 

Controller에서 pageable 사용

Controller를 통해서 데이터를 조회하는 경우 요청에 제공된 쿼리 파라미터를 통해서 Pageable 객체는 자동으로 세팅된다.

Controller 클래스

@RestController
@RequestMapping("/mongodb/persons")
@RequiredArgsConstructor
public class PersonController {
    private final MongoDBService mongoDBService;

    @GetMapping(value = "/gender", produces = MediaType.APPLICATION_JSON_VALUE )
    public Page<PersonDocument> listPersonByGender( @RequestParam Gender gender,
                                                    Pageable pageable ) {
        return mongoDBService.listByGenderPageable(gender, pageable);
    }
}

listPersonGender()는 성별에 해당하는 PersonDocument 리스트를 조회하는 API다.

호출 URL이 다음과 같을 때

http://127.0.0.1:8080/mongodb/persons/gender?gender=FEMALE&page=0&size=10&sort=personAge,asc

요청에 제공된 page, size, sort 쿼리 파라미터 값을 기반으로 Pageable 객체가 생성된다.

호출 URL에 제공된 쿼리 파라미터는 0번 페이지의 10개 데이터를 personAge 필드의 값을 기준으로 오름차순으로 정렬하여 요청한다는 의미다.

위 호출 URL의 응답은 다음과 같다.

{
    "content": [
        {
            "personId": "a9401b16-b107-4459-bc6a-cc203bddc096",
            "personName": "test-name1",
            "personAge": 1,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트1"
        },
        {
            "personId": "9ef81259-b2d6-44b7-89ab-fd6c3156f298",
            "personName": "test-name3",
            "personAge": 3,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트3"
        },
        {
            "personId": "0983d70f-b3bf-4188-9fda-4f0b093caa24",
            "personName": "test-name5",
            "personAge": 5,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트5"
        },
        {
            "personId": "ceacfb11-dd2e-40af-8071-36ef1c33a9ab",
            "personName": "test-name7",
            "personAge": 7,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트7"
        },
        {
            "personId": "2c99d15f-986c-4eb4-8740-265c7a59159c",
            "personName": "test-name9",
            "personAge": 9,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트9"
        },
        {
            "personId": "180e4d4d-037d-469e-8eca-24488d717d86",
            "personName": "test-name11",
            "personAge": 11,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트11"
        },
        {
            "personId": "f5803c3d-bc1d-40d2-be7d-948f0512fc6f",
            "personName": "test-name13",
            "personAge": 13,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트13"
        },
        {
            "personId": "26be5421-8882-453c-8a61-dc4280cec7e8",
            "personName": "test-name15",
            "personAge": 15,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트15"
        },
        {
            "personId": "bf99a67c-6df4-40e8-96e2-47c7d15dda25",
            "personName": "test-name17",
            "personAge": 17,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트17"
        },
        {
            "personId": "09e926f4-b2f5-4a64-82af-dea0216ce732",
            "personName": "test-name19",
            "personAge": 19,
            "personGender": "FEMALE",
            "personNation": "Korea",
            "comment": "test-name 코멘트19"
        }
    ],
    "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
            "empty": false,
            "unsorted": false,
            "sorted": true
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 5,
    "totalElements": 50,
    "number": 0,
    "size": 10,
    "sort": {
        "empty": false,
        "unsorted": false,
        "sorted": true
    },
    "first": true,
    "numberOfElements": 10,
    "empty": false
}

 

지금까지 Spring Data MongoDB의 Repository에 대한 기초적인 사용법을 알아보았다.


참고 문서

https://docs.spring.io/spring-data/mongodb/reference/repositories/core-concepts.html

https://docs.spring.io/spring-data/mongodb/reference/repositories/definition.html

https://docs.spring.io/spring-data/mongodb/reference/mongodb/repositories/repositories.html

https://docs.spring.io/spring-data/mongodb/reference/repositories/create-instances.html

 

전체 샘플 코드

https://gitlab.com/blog4031530/spring-mongodb

댓글

💲 추천 글