티스토리 뷰

Dependency


<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
  <groupId>io.r2dbc</groupId>
  <artifactId>r2dbc-h2</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>

Spring Data R2DBC 를 이용하기 위해서는 r2dbc-spi, r2dbc-pool, spring-data-r2dbc 의 의존성이 필요한데 spring-boot-starter-data-r2dbc 의존성 하나를 추가하면 이 모든 의존성을 한번에 추가해준다.

 

그리고 H2 Database 를 사용하기 위해서 관련 의존성을 추가해준다.

 

Configuration


@Configuration
@EnableR2dbcRepositories
public class R2dbcConfig extends AbstractR2dbcConfiguration {
    @Override
    public ConnectionFactory connectionFactory() { return null; }
}

Spring Data R2DBC 를 사용하는 방법에는 Spring Data 에서 지원하는 Repository 를 이용하는 방법R2dbcEntityTemplate 를 이용하는 방법이 있다.

 

이번 예제에서는 Repository 를 이용할 것이기 때문에 @EnableR2dbcRepositories 어노테이션을 추가한다.

ConnectionFactory 는 application.yml 에서 설정값으로 생성하기 때문에 생략한다.

 

spring:
  r2dbc:
    url: r2dbc:h2:mem:///test
    username: sa
    password:
logging:
  level:
    org:
      springframework:
        r2dbc: DEBUG
debug: true

 

Domain


@Table
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Schedule {
    @Id
    Long id;
    Integer year;
    Integer month;
    Integer day;
    String title;
    LocalTime startTime;
    LocalTime endTime;
    String writer;
    Integer alarm;
    String comment;

    public static Schedule of(ScheduleDto scheduleDto) {
        return new Schedule(scheduleDto.getId(), 
                scheduleDto.getYear(), 
                scheduleDto.getMonth(), 
                scheduleDto.getDay(), 
                scheduleDto.getTitle(), 
                scheduleDto.getStartTime(), 
                scheduleDto.getEndTime(), 
                scheduleDto.getWriter(), 
                scheduleDto.getAlarm(), 
                scheduleDto.getComment());
    }
}

생성자를 통해서 도메인에서 어떤 필드를 인식할지 결정하기 때문에 @AllArgsConstructor 를 반드시 추가해줘야 한다.

 

테스트만 진행할 것이기 때문에 schema.sql 파일을 생성해 샘플 테이블과 샘플 데이터를 추가한다.

CREATE TABLE schedule (
    id INT(20) AUTO_INCREMENT PRIMARY KEY,
    year NUMBER(4) not null,
    month NUMBER(2) not null,
    day NUMBER(2) not null,
    title VARCHAR(256) not null,
    start_time time not null,
    end_time time not null,
    writer VARCHAR(16) not null,
    alarm INT(1),
    comment VARCHAR(2048)
);

INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 5, '프로젝트 회의', '20:00:00', '22:00:00', 'ch4njun', 1, 'SAMPLE1');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2020, 12, 5, '스터디 회의', '20:00:00', '22:00:00', 'ch4njun', 0, 'SAMPLE2');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 5, '프론트 회의', '23:00:00', '00:00:00', 'ch4njun', 1, 'SAMPLE3');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 5, '프로젝트 회의', '13:00:00', '15:00:00', 'ch4njun', 1, 'SAMPLE4');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 11, 11, '스터디 회의', '10:00:00', '12:00:00', 'ch4njun', 0, 'SAMPLE5');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 6, '회의장 개발 회의', '23:00:00', '00:00:00', 'ch4njun', 1, 'SAMPLE6');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 11, 5, '회식', '18:00:00', '22:00:00', 'ch4njun', 1, 'SAMPLE7');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2020, 9, 5, '스터디 회의', '20:00:00', '22:00:00', 'ch4njun', 0, 'SAMPLE8');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2020, 7, 10, '프론트 회의', '23:00:00', '01:00:00', 'ch4njun', 1, 'SAMPLE9');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2020, 7, 3, '프로젝트 회의', '15:00:00', '17:00:00', 'ch4njun', 1, 'SAMPLE10');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 20, '스터디 회의', '20:00:00', '22:00:00', 'ch4njun', 0, 'SAMPLE11');
INSERT INTO schedule(year, month, day, title, start_time, end_time, writer, alarm, comment)
VALUES (2021, 10, 30, '회의장 개발 회의', '22:00:00', '00:00:00', 'ch4njun', 1, 'SAMPLE12');

 

Repository


package com.pangtudy.conferenceapi.repository;

import com.pangtudy.conferenceapi.entity.Schedule;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;

public interface ScheduleRepository extends ReactiveCrudRepository<Schedule, Long> {
    Flux<Schedule> findByYearOrderByStartTime(int year);
}

Spring Data JPA 와 동일한 방식으로 Repository 코드를 작성하면 된다. 당연하다... Repository 라는 개념이 Spring Data Commons 에서 나온 것이기 때문에 모든 모듈이 동일하게 사용할 수 있다.

 

사용 가능한 반환 타입이나 세부적인 것에는 차이가 있으니 공식문서를 참조해 사용하는게 좋을듯 하다.

 

Controller


CRUD 에 대한 API 를 제공하는 Controller 를 생성해보자.

package com.pangtudy.conferenceapi.controller;

import com.pangtudy.conferenceapi.dto.ScheduleDto;
import com.pangtudy.conferenceapi.service.CalendarService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@RestController
@RequestMapping("/calendar")
@RequiredArgsConstructor
public class CalendarController {
    private final CalendarService calendarService;

    @GetMapping("/{year}/schedules")
    public Flux<ScheduleDto> getSchedulesByYear(@PathVariable int year) {
        return calendarService.retrieveSchedules(year);
    }

    @PostMapping("/schedule")
    public Mono<ScheduleDto> setSchedule(
            @RequestBody ScheduleDto scheduleDto
    ) {
        return calendarService.saveSchedule(scheduleDto);
    }

    @PutMapping("/schedules/{idx}")
    public Mono<ScheduleDto> updateSchedule(
            @PathVariable long idx,
            @RequestBody ScheduleDto scheduleDto
    ) {
        return calendarService.updateSchedule(idx, scheduleDto);
    }

    @DeleteMapping("/schedules/{idx}")
    public Mono<Void> deleteSchedule(
            @PathVariable long idx
    ) {
        return calendarService.deleteSchedule(idx);
    }
}

참고로 그냥 이렇게 생성하고 Front 쪽에서 호출하면 CORS 로 인한 문제가 발생하기 때문에 반드시 WebConfig 쪽에서 이에 대한 설정을 해줘야 한다.

 

Service


package com.pangtudy.conferenceapi.service;

import com.pangtudy.conferenceapi.dto.ScheduleDto;
import com.pangtudy.conferenceapi.entity.Schedule;
import com.pangtudy.conferenceapi.repository.ScheduleRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@Service
@RequiredArgsConstructor
public class CalendarService {
    private final ScheduleRepository scheduleRepository;

    public Flux<ScheduleDto> retrieveSchedules(int year) {
        return scheduleRepository.findByYearOrderByStartTime(year)
                .map(ScheduleDto::of);
    }

    @Transactional
    public Mono<ScheduleDto> saveSchedule(ScheduleDto scheduleDto) {
        return scheduleRepository.save(Schedule.of(scheduleDto))
        		.map(ScheduleDto::of);
    }

    @Transactional
    public Mono<ScheduleDto> updateSchedule(long idx, ScheduleDto scheduleDto) {
        return scheduleRepository.findById(idx)
                .map(schedule -> {
                    schedule.setTitle(scheduleDto.getTitle());
                    schedule.setStartTime(scheduleDto.getStartTime());
                    schedule.setEndTime(scheduleDto.getEndTime());
                    schedule.setComment(scheduleDto.getComment());
                    schedule.setAlarm(scheduleDto.getAlarm());
                    return schedule;
                })
                .flatMap(schedule -> scheduleRepository.save(schedule)
                				.map(ScheduleDto::of));
    }

    @Transactional
    public Mono<Void> deleteSchedule(long idx) {
        return scheduleRepository.deleteById(idx);
    }
}

Spring Data R2DBC 는 기존 JPA 와 다르게 Mono & Flux 로 데이터를 반환할 수 있다. 이렇게 반환받은 데이터를 그대로 반환하면 되고, 중간에 처리할 것이 있다면 Reactor 가 제공하는 연산자를 이용하면 된다.

 

Update 쪽에서 코드를 보면 Reactor 연산중에는 영속성을 가지지 않기 때문에 별도로 Repository.save() 메서드를 통해서 다시 저장을 해줘야 되는 것을 확인할 수 있다.

 

이외에도 Spring Data R2DBC 는 연관관계 매핑을 지원하지 않는다는 단점이 있다.

 


마지막으로 R2dbcEntityTemplate 을 이용한 코드를 첨부하며 포스팅을 마친다.

@Component
@RequiredArgsConstructor
@Slf4j
public class PostRepository {    
    private final R2dbcEntityTemplate template;    
    public Flux<Post> findByTitleContains(String name) {
        return this.template.select(Post.class)
                .matching(Query.query(where("title").like("%" + name + "%")).limit(10).offset(0))
                .all();
    }    
    
    public Flux<Post> findAll() {
        return this.template.select(Post.class).all();
    }    
    
    public Mono<Post> findById(UUID id) {
        return this.template.selectOne(Query.query(where("id").is(id)), Post.class);
    }    
    
    public Mono<UUID> save(Post p) {
        return this.template.insert(Post.class)
                .using(p)
                .map(post -> post.getId());
    }    
    
    public Mono<Integer> update(Post p) {
/*
        return this.template.update(Post.class)
                .matching(Query.query(where("id").is(p.getId())))
                .apply(Update.update("title", p.getTitle())
                        .set("content", p.getContent())
                        .set("status", p.getStatus())
                        .set("metadata", p.getMetadata()));
*/
        return this.template.update(
                Query.query(where("id").is(p.getId())),
                Update.update("title", p.getTitle())
                        .set("content", p.getContent())
                Post.class
        );
    }    
    
    public Mono<Integer> deleteById(UUID id) {
        return this.template.delete(Query.query(where("id").is(id)), Post.class);
    }
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함