반응형

WebFlux에서 몽고 DB 연동하는 법을 알아 보겠습니다.

 

Webflux에서 몽고 DB 연동하기

1. 프로젝트 생성하기

프로젝트 생성

 

디펜던시도 선택해줍니다.

Lombok, 반응형 스프링용으로 나온 Reactive Mongo, Reactive Web까지 선택하였습니다.

 

그리고 Finish를 해줍니다.

 

2. 뷰 설정

뷰 페이지에서 사용할 thymeleaf가 빠졌네요. 추가된 pom.xml 입니다.

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

	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
		<exclusions>
			<exclusion>
				<groupId>org.junit.vintage</groupId>
				<artifactId>junit-vintage-engine</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>io.projectreactor</groupId>
		<artifactId>reactor-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

 

WebConfig.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

@EnableWebFlux
@Configuration
public class WebConfig implements ApplicationContextAware, WebFluxConfigurer {
	
	ApplicationContext context;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = context;
	}
	
	@Bean
	public ITemplateResolver thymeleafTemplateResolver() {
	    final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
	    resolver.setApplicationContext(this.context);
	    resolver.setPrefix("classpath:templates/");
	    resolver.setSuffix(".html");
	    resolver.setTemplateMode(TemplateMode.HTML);
	    resolver.setCacheable(false);
	    resolver.setCheckExistence(false);
	    return resolver;
	}
	
	@Bean
	public ISpringWebFluxTemplateEngine thymeleafTemplateEngine() {
	    SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
	    templateEngine.setTemplateResolver(thymeleafTemplateResolver());
	    return templateEngine;
	}
	
	@Bean
	public ThymeleafReactiveViewResolver thymeleafReactiveViewResolver() {
	    ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
	    viewResolver.setTemplateEngine(thymeleafTemplateEngine());
	    return viewResolver;
	}
	
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
	    registry.viewResolver(thymeleafReactiveViewResolver());
	}
}

view 설정을 해줍니다.

resources/templates 하위에 있는 html파일을 읽도록 처리할 예정입니다.

 

3. document 설정

Lombok을 활용한 document를 설정합니다.

JPA로 만들기 때문에 매핑할 Movie 클래스를 정의하였습니다.

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

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Document(collection="list")
public class Movie {
	@Id
	private String id;
	private String title;
	private String director;
	private String since;
	private int audienceCnt;
	
	public Movie(String title, String director, String since, int audienceCnt) {
		super();
		this.title = title;
		this.director = director;
		this.since = since;
		this.audienceCnt = audienceCnt;
	}
}

 

몽고 DB 데이터는 아래와 같습니다.

 

 

 

4. repository 생성하기

MovieRepository.java

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

import com.psw.movie.document.Movie;

@Repository
public interface MovieRepository extends ReactiveMongoRepository<Movie, String>{
}

ReactiveMongoRepository를 상속받아 구현합니다.

 

 

5. 서비스 생성

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import com.psw.movie.document.Movie;
import com.psw.movie.repository.MovieRepository;

import reactor.core.publisher.Flux;

@Service
public class MovieService {

	private final ApplicationEventPublisher publisher;
	private final MovieRepository movieRepository;
	
	public MovieService(ApplicationEventPublisher publisher, MovieRepository movieRepository) {
		this.publisher = publisher;
		this.movieRepository = movieRepository;
	}
	
	public Flux<Movie> allMovies(){
		return movieRepository.findAll();
	}
}

생성한 repository에서 데이터를 가공하거나 변경할 서비스를 생성합니다.

해당 포스팅에서는 다른 기능없이 전체를 가져오는 findAll()메소드를 통해 전부 가져올 예정입니다.

 

6. handler 작성

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import com.psw.movie.document.Movie;
import com.psw.movie.service.MovieService;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class MovieHandler {
	
	private final MovieService movieService;
	
	public MovieHandler(MovieService service) {
		this.movieService = service;
	}
	
	final static MediaType TEXT_HTML = MediaType.TEXT_HTML;
	final static MediaType APPLICATION_JSON = MediaType.APPLICATION_JSON;
	
	//movie.html로 변환
	public Mono<ServerResponse> movie(ServerRequest request){
		return ServerResponse.ok().contentType(TEXT_HTML).render("movie");
	}
	
	//요청이오면 JSON형태로 데이터 파싱
	public Mono<ServerResponse> getMovieList(ServerRequest request){
		Flux<Movie> list = this.movieService.allMovies();
		return ServerResponse.ok().contentType(APPLICATION_JSON).body(list, Movie.class);
	}
}

서비스를 사용하여 모든 영화리스트를 가져오는 핸들러를 작성합니다.

 

 

7. 핸들러를 연결할 라우터를 작성합니다.

요청에 따라 파싱해줄 라우터입니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
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 com.psw.movie.handler.MovieHandler;

@Configuration
public class MovieRouter {
	
	final static MediaType TEXT_HTML = MediaType.TEXT_HTML;
	final static MediaType APPLICATION_JSON = MediaType.APPLICATION_JSON;
	
	@Bean
	public RouterFunction<ServerResponse> index(MovieHandler movieHandler){
		return RouterFunctions.route(
				RequestPredicates.GET("/").and(RequestPredicates.accept(TEXT_HTML)), movieHandler::movie)
				.andRoute(RequestPredicates.GET("/movie").and(RequestPredicates.accept(TEXT_HTML)), movieHandler::movie);
	}
	
	@Bean
	public RouterFunction<ServerResponse> getMoiveList(MovieHandler movieHandler){
		return RouterFunctions.route(
					RequestPredicates
						.POST("/getMovieList")
						.and(RequestPredicates.accept(APPLICATION_JSON)), 
						movieHandler::getMovieList);
	}
	
	@Bean
	public RouterFunction<ServerResponse> getMoiveListSearch(MovieHandler movieHandler){
		return RouterFunctions.route(
					RequestPredicates
						.POST("/getMovieList/{req}")
						.and(RequestPredicates.accept(APPLICATION_JSON)), 
						movieHandler::getMovieList);
	}
}

 

 

8. html소스 작성

마지막으로 뷰페이지 html소스를 작성하고 부트 프로젝트를 구동합니다.

movie.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>Movie</title>
		<style>
	    	*{padding:0; margin:0;}
	    	table tr th{background-color: #FFBB00;}
	    	table tr th, td{border: 1px solid black; text-align: center; width: 200px;padding: 5px;}
	    	.inputMovieData tr td{padding: 5px;}
	    </style>
	</head>
	<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
	<body>
		<h1>WebFlux Asynchronous</h1>
		<br/><br/><br/>
		<h1>Movie List</h1>
			<table id="MovieTable">
				<tr>
					<th>제목</th>
					<th>감독</th>
					<th>연도</th>
					<th>관객수</th>
				</tr>
			</table>			
	</body>
	<script>
	$(document).ready(function(){
	    init();
	});
	
	function init(){
		reqAjaxPost("/getMovieList", "", fnSuccess);
	}
	
	function fnSuccess(res){
		if(res != null){
			var tag = "<tr><th>제목</th><th>감독</th><th>연도</th><th>관객수</th>	</tr>";
			res.forEach(function(d){
				tag += "<tr>" +
							"<td>" + d.title + "</td>" +
							"<td>" + d.director + "</td>" +
							"<td>" + d.since + "</td>" +
							"<td>" + d.audienceCnt + "</td>" +
						"</tr>";
			});
			
			$("#MovieTable").empty().append(tag);
		}
	}
	
	function reqAjaxPost(url, param, fnSuccess){
		$.ajax({
			url: url,
			type: "POST",
			data: param,
			dataType: "JSON",
			success: function(args){
				fnSuccess(args);
			},
			error: function(args){
				alert("에러가 발생했습니다.");
			}
		});
	}
	</script>
</html>

 

 

완성된 소스의 구조

프로젝트 구조

 

 

스프링 부트 프로젝트를 구동합니다.

로컬 몽고DB에서 데이터를 가져와 영화정보를 파싱하는 예제를 작성해보았습니다.

반응형