본문 바로가기

Java

Java Spring WebFlux + MySQL CRUD 예제

Spring WebFlux 와 MySQL을 활용하여 가장 기본적인 CRUD를 만들어 볼 예정입니다.

 

Spring WebFlux란 Spring 진영의 비동기 프레임 워크 입니다.

Reactive Streams 의 Publiser 인터페이스의 구현체인 Mono 와 Flux를 사용하여 구현을 하게 됩니다.

여기서 Mono란 0 ~ 1의 데이터를 처리 할 수 있고, Flux는 0 ~ N 개의 데이터를 처리 할 수 있습니다.

 

의존성 추가

implementation "com.github.jasync-sql:jasync-r2dbc-mysql:2.2.4"
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

이 밖에도 롬복과 스프링 데브 툴을 추가 하여주었습니다.

application.yml

spring:
  r2dbc:
    url: r2dbc:mysql://127.0.0.1:3306/TestSchema
    username: [db 유저]
    password: [db 패스워드]

 

Controller.java

@RestController
@Slf4j
public class Controller {

	private TestTableRepository testTablerepository;

	Controller(TestTableRepository testTablerepository) {
		this.testTablerepository = testTablerepository;
	}

	@GetMapping("/")
	public Flux<TestTable> main() throws Exception {
		return testTablerepository.findAll();
	}

	@GetMapping("/test1/{idx}")
	public ResponseEntity<Mono<TestTable>> test1(@PathVariable("idx") int idx) throws Exception {
		Mono<TestTable> test = testTablerepository.findById(idx);
		return ResponseEntity.status(HttpStatus.OK).body(test);
	}

	@GetMapping("/test2")
	public ResponseEntity<Flux<TestTable>> test2() throws Exception {
		Flux<TestTable> test = testTablerepository.findAll().log();
		Flux<TestTable> test2 = test.map(e -> {
			log.info(Integer.toString(e.getIdx()));
			e.setContents("변경된 내용");
			return e;
		}).log();
		return ResponseEntity.status(HttpStatus.OK).body(test2);
	}

	@PostMapping("/insert")
	public Mono<ResponseEntity<Boolean>> post(@RequestBody TestTable testTable) throws Exception {
		return testTablerepository.save(testTable).map(data -> ResponseEntity.status(HttpStatus.CREATED).body(true))
				.onErrorResume(error -> Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false)));
	}

	@PutMapping("/update/{idx}")
	public Mono<ResponseEntity<Boolean>> put(@PathVariable("idx") int idx, @RequestBody TestTable testTable)
			throws Exception {
		Mono<TestTable> getData = testTablerepository.findById(idx);
		Mono<ResponseEntity<Boolean>> update = getData.flatMap(data -> {
			if (testTable.getContents() != null)
				data.setContents(testTable.getContents());
			return testTablerepository.save(data).map(innerData -> ResponseEntity.status(HttpStatus.OK).body(true))
					.onErrorResume(
							error -> Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false)));
		}).switchIfEmpty(Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).body(false)));
		return update;
	}

	@DeleteMapping("/delete/{idx}")
	public Mono<ResponseEntity<Boolean>> delete(@PathVariable("idx") int idx) throws Exception {
		Mono<TestTable> getData = testTablerepository.findById(idx);
		Mono<ResponseEntity<Boolean>> deleteData = getData.flatMap(data -> {
			return testTablerepository.deleteById(idx).then(
				Mono.just(ResponseEntity.status(HttpStatus.OK).body(true))
			).onErrorReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false));
		}).defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).body(false));
		return deleteData;
	}
}
subscribe(data-> System.out.println("성공"), error -> System.out.println("실패"))

위와 같은 방식으로도 에러를 잡을 수 있지만 이후 더 상세하게 다룰 수 없어 본문에 적혀있는 onErrorResume의 방식으로 진행 하였습니다.

 

또한 Mono와 Flux의 경우 return값이 void인 경우 map또는 flatMap이 호출이 되지 않기 때문에 switchIfEmpty 또는 defaultIfEmpty를 활용하여 예외 처리를 진행하여야 했습니다.

 

Error의 경우 

  • doOnError() : 예외가 발생했을 경우, 특정 행위를 실행시킬 경우 사용
  • onErrorReturn : 예외가 발생했을 때 특정 값을 Return 함
  • onErrorResume : 예외가 발생했을 때 다른 Flux형태로 Return 함
  • onErrorContinue : 예외가 발생했을 때 멈추지 않고 해당 영역만 skip해서 동작함.

뒤에 log를 찍는 경우 상세하게 로그를 볼 수 있습니다.

각각의 api를 실행 시켜 보면 정상적으로 작동 하는 것을 알 수 있습니다.

 

map과 flapMap의 차이

flatMap의 경우는 새로운 비동기적인 함수를 리턴 할 때 사용 하고, map의 경우 단순한 요소를 변환하기 위해서 사용한다.

 

개인적으로 사용하면서 느낀점은 SpringWebFlux자체에서 사용하고 있는 메소드의 종류가 너무 많아서 사용하기 까다로웠던 것 같다. 비동기라서 어렵다기 보단 내가 원하는 로직을 구현 하기 위해서 필요한 메소드를 찾는 것이 가장 어려웠던 것 같다. 또한 에러를 상세하게

를 상세하게 컨트롤하기 좀 어려울 것 같다는 생각이 들었다.