ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 인 액션] Chapter 12 - 리액티브 데이터 퍼시스턴스 :: 리액티브 몽고DB 리퍼지터리 작성하기
    개발서적읽기/Spring in Action 제 5판 2020. 10. 10. 02:18


    NoSql 중 하나인 몽고DB는 문서형 DB다. 몽고DB는 BSON(Binary JSON) 형식의 문서로 


    데이터를 저장하며, 다른 DB에서 데이터를 쿼리하는 것과 거의 유사한 방법으로 문서를 


    쿼리하거나 검색할 수 있다. 몽고DB를 스프링 데이터로 사용하는 방법은 JPA나 카산드라를


    스프링 데이터로 사용하는 방법과 크게 다르지 않다. 즉, 도메인 타입을 문서 구조로 매핑하는


    애노테이션을 도메인 클래스에 지정한다. 그리고 JPA나 카산드라에서 알아보았던 것과 동일한


    프로그래밍 모델을 따르는 Repository Interface를 작성하면 된다.




    ■스프링 데이터 몽고DB 활성화하기


    리액티브 스프링 데이터 몽고DB 스타터 의존성을 추가하자.

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

    이렇게 빌드에 의존성을 추가하면 스프링 데이터 리액티브 몽고DB 지원을 활성화하는 


    자동-구성이 수행된다. (Repository Interface 자동 구현) 스프링 데이터 몽고DB는 기본적으로


    27017 포트를 리스닝한다. 그러나 테스트와 개발에 편리하도록 in-memory 내장 몽고DB를 


    사용할 수도 있다. 아래와 같이 Flapdoodle 의존성을 빌드에 추가한다.

    <dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    </dependency>




    ■도메인 타입을 문서로 매핑하기


    스프링 데이터 몽고DB는 몽고DB에 저장되는 문서 구조로 도메인 타입을 매핑하는 데 유용한


    애노테이션들을 제공한다. 그 중 아래 3개가 가장 많이 사용된다.


    • @Id : 지정된 속성을 문서 ID로 지정한다. Serializable 타입인 어떤 속성에도 지정할 수
      있다.

    • @Document : 지정된 도메인 타입을 몽고DB에 저장되는 문서로 선언한다.

    • @Field : 몽고DB의 문서에 속성을 저장하기 위해 필드 이름(과 선택적으로 순서)을
      지정한다. @Field가 지정되지 않은 도메인 타입의 속성들은 필드 이름과 속성 이름을
      같은 것으로 간주된다.

     
    이 애노테이션들을 이용하여 Ingredient 클래스를 작성한다.
    package tacos;

    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;

    import lombok.AccessLevel;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.RequiredArgsConstructor;

    @Data
    @RequiredArgsConstructor
    @NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
    @Document
    public class Ingredient {

    @Id
    private final String id;
    private final String name;
    private final Type type;

    public static enum Type {
    WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }

    }

    다음은 Taco의 몽고DB 매핑을 알아보자.

    package tacos;

    import java.util.Date;
    import java.util.List;

    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;

    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;
    import org.springframework.data.rest.core.annotation.RestResource;

    import lombok.Data;

    @Data
    @RestResource(rel = "tacos", path = "tacos")
    @Document
    public class Taco {

    @Id
    private String id;

    @NotNull
    @Size(min = 5, message = "Name must be at least 5 characters long")
    private String name;

    private Date createdAt = new Date();

    @Size(min=1, message="You must choose at least 1 ingredient")
    private List<Ingredient> ingredients;

    }

    ID로 String 타입의 속성을 사용하면 이 속성값이 DB에 저장될 때 몽고DB가 자동으로 ID 값을 


    지정해준다. (null일 경우에 한함)


    몽고DB 애노테이션이 지정된 다음의 Order 클래스를 살펴보자.

    package tacos;

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;

    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;

    import lombok.Data;
    import org.springframework.data.mongodb.core.mapping.Field;

    @Data
    @Document
    public class Order implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    private String id;
    private Date placedAt = new Date();

    @Field("customer")
    private User user;

    private String deliveryName;

    private String deliveryStreet;

    private String deliveryCity;

    private String deliveryState;

    private String deliveryZip;

    private String ccNumber;

    private String ccExpiration;

    private String ccCVV;


    private List<Taco> tacos = new ArrayList<>();

    public void addDesign(Taco design) {
    this.tacos.add(design);
    }

    }

    customer 열을 문서에 저장한다는 것을 나타내기 위해서 user 속성에 @FIeld를 지정하였다.


    다음으로 User 도메인 클래스를 작성하자.

    package tacos;
    import java.util.Arrays;
    import java.util.Collection;

    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.
    SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;

    import lombok.AccessLevel;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.RequiredArgsConstructor;

    @Data
    @NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
    @RequiredArgsConstructor
    @Document
    public class User implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    private String id;

    private final String username;

    private final String password;
    private final String fullname;
    private final String street;
    private final String city;
    private final String state;
    private final String zip;
    private final String phoneNumber;
    private final String email;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public boolean isAccountNonExpired() {
    return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    return true;
    }

    @Override
    public boolean isEnabled() {
    return true;
    }

    }

    이제는 Repository Interface를 작성하자.




    ■리액티브 몽고DB 리퍼지터리 인터페이스 작성하기


    스프링 데이터 몽고DB는 스프링 데이터 JPA 및 스프링 데이터 카산드라가 제공하는 것과 


    유사한 자동 Repository 지원을 제공한다. 몽고DB의 Reactvie Repository를 작성할 때는 


    ReactiveCrudRepository나 ReactiveMongoRepository를 선택할 수 있다. 


    ReactiveCrudRepository는 새로운 문서나 기존 문서의 save() 메서드에 의존하는 반면,


    ReactiveMongoRepository는 새로운 문서의 저장에 최적화된 소수의 특별한 insert() 메서드를


    제공한다. 


    우선, Ingredient 객체를 문서로 저장하는 Repository를 정의하자. 식재료를 저장한 문서는 


    초기에 식재료 데이터를 DB에 추가할 때 생성되며, 이외에는 거의 추가되지 않는다. 따라서 


    새로운 문서의 저장에 최적화된 ReactiveMongoRepository 보다는 ReactiveCrudRepository를


    확장해야 한다.

    package tacos.data;

    import org.springframework.data.repository.reactive.ReactiveCrudRepository;
    import org.springframework.web.bind.annotation.CrossOrigin;

    import tacos.Ingredient;

    @CrossOrigin(origins="*")
    public interface IngredientRepository extends ReactiveCrudRepository<Ingredient, String> {

    }

    IngredientRepository는 ReactiveRepository이므로 이것의 메서드는 그냥 도메인 타입이나 


    컬렉션이 아닌 Flux나 Mono 타입으로 도메인 객체를 처리한다. 예를 들어, findAll() 메서드는


    Iterable<Ingredient> 대신 Flux<Ingredient>를 반환한다. 그리고 findById() 메서드는 


    Optional<Ingredient> 대신 Mono<Ingredient>를 반환한다. 따라서 이 Reactive Repository는


    엔드-to-엔드 Reactive flow의 일부가 될 수있다.


    다음은 몽고DB의 문서로 Taco 객체를 저장하는 Repository를 정의하자. 색재료 문서와는 다르게


    타코 문서는 자주 생성된다. 따라서 ReactiveMongoRepository의 최적화된 insert() 메서드를


    사용해야 한다.

    package tacos.data;

    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import reactor.core.publisher.Flux;
    import tacos.Taco;


    public interface TacoRepository extends ReactiveMongoRepository<Taco, String> {
    Flux<Taco> findByOrderByCreatedAtDesc();
    }

    ReactiveCrudRepository에 비해 ReactiveMongoRepository를 사용할 때의 유일한 단점은 바로 


    몽고DB에 특화되어 있다는 점이다. 그래서 다른 DB에는 사용할 수 없다. 따라서 이 단점을 


    감안하고 사용해야 한다.


    TacoRepository에는 새로운 메서드가 있다. 이 메서드는 최근 생성된 타코들의 리스트를 


    조회하여 반환한다.


    findByOrderByCreatedAtDesc()는 Flux<Taco>를 반환한다. 따라서 take() 오퍼레이션을 적용하여


    Flux에서 발행되는 처음 12개의 Taco 객체만 반환할 수 있다. 예를 들어, 최근 생성된 타코들을


    보여주는 컨트롤러에서는 다음과 같이 코드를 작성할 수 있다.

    Flux<Taco> recents = repo.findByOrderByCreatedAtDesc().take(12);

    이번엔 OrderRepository 인터페이스를 알아보자.

    package tacos.data;

    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import tacos.Order;

    public interface OrderRepository extends ReactiveMongoRepository<Order, String> {

    }

    Order 문서는 자주 생성될 것이다. 따라서 ReactiveMongoRepository를 확장한다.

    package tacos.data;

    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    import reactor.core.publisher.Mono;
    import tacos.User;

    public interface UserRepository extends ReactiveMongoRepository<User, String> {

    Mono<User> findByUsername(String username);

    }


    댓글

Designed by Tistory.