웹플럭스에서 몽고DB 연동은 "이렇게 해놨는데 동작해?" 라는 느낌이 들 정도로 매우 간단하다.
몇번의 환경설정만 해 주면 데이터베이스에서 이미 동작중인 모습을 볼 수 있다.
먼저 몽고db와의 연동을 위해서 라이브러리를 추가한다.
*Maven 기준
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
라이브러리를 추가한 뒤에 application.properties에서 데이터베이스와 관련된 세팅을 해 준다.
spring.data.mongodb.database=db이름
#spring.data.mongodb.username=만약아이디가 필요하면
#spring.data.mongodb.password=만약비밀번호가 필요하면
spring.data.mongodb.host=주소
spring.data.mongodb.port=포트번호
위 2번의 세팅으로 몽고DB와의 연동이 끝이났다.
프로젝트를 동작시켰는데 아무런 문제가 없다면 이상없이 데이터베이스에 연결이 된 것 이다.
만약 에러가 발생하거나 문제가 존재한다면 데이터베이스에 대한 접속정보가 틀리거나, 메이븐 라이브러리가 다 받아지지 않았기 때문이다.
나머지 작업을 이어가보자.
JPA 방식으로 만들어야하기 때문에 데이터베이스를 매핑할 vo 객체를 만들어 준다.
클래스 이름은 MyDocument로 하였다.
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Document(value="mydoc") //조회할 컬렉션 이름은 mydoc 이다.
@Getter
@Setter
public class MyDocument implements Serializable{
private static final long serialVersionUID = 142466781L;
@Id
private String id;
private String text;
private int number;
private int counts;
private int total;
private String[] texts;
}
Document에서 정의한 value 값은 조회할 컬렉션 이름을 의미한다.
그리고 Id로 보이는 에노테이션이 바로 조회할 몽고DB 컬렉션의 고유 키 값을 의미한다.
기타 나머지 변수들은 몽고DB컬렉션에 존재하는 Document를 의미한다.
위 내용에 대한 실제 몽고DB 컬렉션 모양이다.
저렇게 vo클래스를 만들어 준 다음 데이터베이스와 연동할 저장소 클래스를 만든다.
만들 저장소는 인터페이스 형태이어야 하며, ReactiveMongoRepository 라는 인터페이스를 상속 받아야 한다.
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@Repository
public interface MongoDbRepository extends ReactiveMongoRepository<MyDocument, String> {
Flux<MyDocument> findAll(); //내가만든 조회 메소드
Flux<MyDocument> findByText(String text); //내가만든 조건이 있는 조회 메소드
@Query("{'text': {$regex: ?0 }}")
Flux<MyDocument> findRegexByText(String text); //내가만든 like 모양의 조회 메소드
@Query("{'text': {$regex: ?0 }}")
Flux<MyDocument> findRegexPagingByText(String text, Pageable page); //내가만든 페이징 처리가 가능한 조회 메소드
}
미리 몇개의 메소드를 만들어 보았다.
Query라는 에노테이션을 설정하면 세부적인 조건을 붙여줄 수 있으며 "?숫자" 형식으로 조건을 붙일 수 있다.
라우터 클래스에서 적용시키는 것은 AutoWired 나 AllArgsConstructor 에노테이션을 활용하여 해당 저장소 인터페이스를 불러오면된다.
실제 적용한 라우터 클래스를 한번 살펴보자.
조회, 등록, 수정 및 삭제에 대한 예를 작성하였다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Configuration
public class MyRouter {
@Autowired MongoDbRepository db;
@Bean
public RouterFunction<ServerResponse> findAll() { //find 라는 get방식이 요청오면 동작하는 메소드
final RequestPredicate predicate = RequestPredicates.GET("/find").and(RequestPredicates.accept(MediaType.TEXT_PLAIN));
RouterFunction<ServerResponse> response = RouterFunctions.route(predicate, (request)->{
Flux<MyDocument> mapper = db.findAll(); //만들어준 전체 조회 메소드
db.findByText("good").collectList().subscribe(System.out::println); //만들어준 단일 검색
db.findRegexByText(".*go.*").collectList().subscribe(System.out::println); //만들어준 like 검색
Pageable page = PageRequest.of(0, 2);
db.findRegexPagingByText(".*go.*", page).collectList().subscribe(System.out::println); //만들어준 페이징처리 검색
//등록, insert 메소드는 ReactiveMongoRepository 클래스 메소드이다.
MyDocument doc = new MyDocument();
doc.setNumber(1234567);
doc.setText("insert database!");
db.insert(doc).subscribe(System.out::println);
//수정, save 메소드는 ReactiveMongoRepository 클래스 메소드이다.
db.findByText("good").flatMap( target ->{ //findByText 메소드는 만들어준 단일 검색 메소드이다.
target.setNumber(555555); //flatMap을 통해서 대상의 number필드값을 바꾼 뒤에
return db.save(target); //save를 통해서 해당 필드 내용을 교체하여 주고 있다.
}).subscribe(System.out::println);
//삭제, deleteById 메소드는 ReactiveMongoRepository 클래스 메소드이다.
db.findByText("good")
.flatMap( target -> db.deleteById(target.getId()))
.subscribe(System.out::println);
//위 내용을 토대로 응답은 아래처럼 해 주면 된다.
Mono<ServerResponse> res = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromProducer(mapper, MyDocument.class));
return res;
});
return response;
}
}
직접만든 MongoDbRepository 인터페이스에는 4개의 메소드만 존재해야 되는데, insert 와 save deleteById라는 메소드가 오버라이딩되어 사용 할 수 있었다.
ReactiveMongoRepository 인터페이스에서도 다른 메소드들도 많이 지원하여준다.
등록, 수정, 삭제는 굳이 메소드를 추가하지 않아도 오버라이딩된 메소드만 사용하여도 무방하다.
like처럼 검색할 때는 입력한 조건에 .* 을 붙여주면 된다.
수정 같은 경우는 조금 특별하다.
먼저 조회를 통해 대상을 찾는다. 그리고 찾은 결과에서 변경할 부분을 변경한 뒤에 save메소드를 동작시켜 주었다.
행위가 마치 selectAndUpdate 형태로 된 것을 볼 수 있다.
만약 save 메소드만 단독으로 동작시키면 "교체"가 되어버린다.
무슨말인고 하면, save를 단독으로 아무런 조건 없이 사용하면 교체할 필드값만 바뀌는게 아니라 해당 컬렉션 전부를 교체할 내용으로 save 하게 되어 버린다.
말이 어렵다면...연습삼아 한번 해 보는 것을 추천한다..^^
몽고DB의 집계 기능인 Aggreagate를 사용하는 것도 에노테이션만 잘 활용하여 준다면 쉽게 가능하다.
MongoDbRepository에 Aggreagate기능을 추가하여보았다.
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@Repository
public interface MongoDbRepository extends ReactiveMongoRepository<MyDocument, String> {
Flux<MyDocument> findAll(); //내가만든 조회 메소드
Flux<MyDocument> findByText(String text); //내가만든 조건이 있는 조회 메소드
@Query("{'text': {$regex: ?0 }}")
Flux<MyDocument> findRegexByText(String text); //내가만든 like 모양의 조회 메소드
@Query("{'text': {$regex: ?0 }}")
Flux<MyDocument> findRegexPagingByText(String text, Pageable page); //내가만든 페이징 처리가 가능한 조회 메소드
//pipeline에 aggregate와 관련된 내용을 추가하여준다.
//aggregate와 관련된 문법은 MongoDB 문법과 동일하게 써 주면 된다.
public static final String match ="{ $match : { text : {$regex : ?0 }, number : {$gte:?1} } }";
public static final String group ="{ $group : { _id:null, counts:{$sum : 1}, total:{$sum : '$number'}, texts:{$addToSet : '$text' } } }";
public static final String sort ="{ $sort : {_id:-1} }";
@Aggregation(pipeline = {match, group, sort})
Flux<MyDocument> aggregateText(String text, int number);
}
몽고DB 문법과 동일하게 입력하여주면 되며, pipeline에 n개 만큼 넣어 줄 수 있다.
* group에서 grouping한 명칭이 매핑하는 vo 클래스에 해당 메소드가 없으면 오류가난다! 매핑할 대상까지 꼭 포함하도록 하자.
아..컨트롤러에는 아래 에노테이션이 추가되어야 한다!
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@EnableMongoAuditing //요거랑!
@EnableReactiveMongoRepositories //요거!
@SpringBootApplication
public class 클래스 {
public static void main(String[] args) {
SpringApplication.run(클래스.class, args);
}
}
모든 행위는 Flux와 Mono로 조립되어 최종적인 RouterFunction<ServerResponse> 형태로 전달해 주는 것을 볼 수 있다.
JPA방식을 사용하는 것이 조금 더 깔끔하고 이해하는데 어렵지 않는 것 같다.
다음번에는 웹플럭스에서 필터, AOP 등을 어떻게 사용하는지에 대해서 작성하여 보겠다.
웹플럭스에서 JPA방식으로 몽고db 연결하다보면 언더바(_)형식의 이름의 키 값에 대해서는 상당히 오류가 나타나는 것을 볼 수 있다.
* 예 : 컬렉션 이름을 good_collection 하던지, 아니면 컬렉션의 키 값을 user_id 이런식으로 하던지
가급적이면 카멜 표기법을 사용해서 이러한 오류를 만나지 말도록 하는 것이 정신건강에 이롭다..ㅠ
아니면 org.bson.Document 클래스를 상속받아서 해당 vo객체를 꾸미는 방법이 있는데..
가급적이면 카멜로....
spring webflux!
* 내용을 채우고 수정중입니다.
* 튜토리얼이나 가이드 목적보다도 개념정리에 목적을 두고 쓰고있습니다. 틀린부분이나 누락된 부분은 꼭 알려주세요!
'Spring framework > Spring Webflux' 카테고리의 다른 글
spring webflux 9 (웹플럭스 적용기, AOP) (3) | 2020.04.10 |
---|---|
spring webflux 8 (웹플럭스 적용기, 웹 필터, 인터셉터) (2) | 2020.04.06 |
spring webflux 6 (웹플럭스 적용기, Mysql) (0) | 2020.03.30 |
spring webflux 5 (웹플럭스 적용기, Mono와 Flux의 Processor) (0) | 2020.03.25 |
spring webflux 5 (웹플럭스 적용기, Mono와 Flux 자주 보이는 map) (0) | 2020.03.20 |
댓글