003 R2dbc Entity Template

03. R2dbcEntityTemplate #

Entity #

  • 데이터베이스에서 하나의 Row와 매칭되는 클래스
  • R2dbcEntityTemplate, R2dbcRepository 등은 데이터베이스에 요청을 보내고 그 결과를 Entity 형태로 반환
  • Table, Row, Column에 필요한 데이터베이스 metadat를 어노테이션으로 제공

R2dbcEntityTemplate #

  • Spring data r2dbc의 추상화 클래스
  • 메서드 체이닝을 통해서 쿼리를 수행하고 결과를 entity 객체로 받을 수 있다.
  • R2dbcEntityOperations를 구현
public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAware, ApplicationContextAware {

    private final DatabaseClient databaseClient;
    ...
}

R2dbcEntityTemplate 생성 #

  • ConnectionFactory를 제공하거나 R2dbcDialect, R2dbcConverter를 제공하여 constructor로 생성 가능
  • R2dbcDialect : R2dbc 버전의 Dialect 확장 img_1.png

R2dbcEntityTemplate 빈 등록 #

  • R2dbcDataAutoConfiguration
    • 위 클래스를 통해서 DatabaseClient, R2dbcDialect, MappingR2dbcConverter를 주입
@AutoConfiguration(after = R2dbcAutoConfiguration.class)
@ConditionalOnClass({ DatabaseClient.class, R2dbcEntityTemplate.class })
@ConditionalOnSingleCandidate(DatabaseClient.class)
public class R2dbcDataAutoConfiguration {

    private final DatabaseClient databaseClient;

    private final R2dbcDialect dialect;

    public R2dbcDataAutoConfiguration(DatabaseClient databaseClient) {
       this.databaseClient = databaseClient;
       this.dialect = DialectResolver.getDialect(this.databaseClient.getConnectionFactory());
    }

    @Bean
    @ConditionalOnMissingBean
    public R2dbcEntityTemplate r2dbcEntityTemplate(R2dbcConverter r2dbcConverter) {
       return new R2dbcEntityTemplate(this.databaseClient, this.dialect, r2dbcConverter);
    }

    @Bean
    @ConditionalOnMissingBean
    public R2dbcMappingContext r2dbcMappingContext(ObjectProvider<NamingStrategy> namingStrategy,
          R2dbcCustomConversions r2dbcCustomConversions) {
       R2dbcMappingContext relationalMappingContext = new R2dbcMappingContext(
             namingStrategy.getIfAvailable(() -> NamingStrategy.INSTANCE));
       relationalMappingContext.setSimpleTypeHolder(r2dbcCustomConversions.getSimpleTypeHolder());
       return relationalMappingContext;
    }

    @Bean
    @ConditionalOnMissingBean
    public MappingR2dbcConverter r2dbcConverter(R2dbcMappingContext mappingContext,
          R2dbcCustomConversions r2dbcCustomConversions) {
       return new MappingR2dbcConverter(mappingContext, r2dbcCustomConversions);
    }

    ...
}

R2dbcEntityOperations #

  • DatabaseClient와 R2dbcConverter를 제공
    • DatabaseClient
      • ConnectionFactory를 wrapping하여 결과를 Map이나 Integer로 반환
    • R2dbcConverter
      • 주어진 Row를 Entity로 만드는 converter
  • R2dbcEntityTemplate에서는 이 DatabaseClient, R2dbcConverter로 쿼리를 실행하고 결과를 entity로 반환한다.
public interface R2dbcEntityOperations extends FluentR2dbcOperations {

    DatabaseClient getDatabaseClient();

     ...

    R2dbcConverter getConverter();
    ...
}

DatabaseClient #

  • 내부에 포함된 ConnectionFactory에 접근 가능
  • sql 메서드를 통해서 GenericExecuteSpec을 반환한다.
    • bind를 통해서 parameter를 sql에 추가
  • fetch를 통해서 FetchSpec을 반환
public interface DatabaseClient extends ConnectionAccessor {

    ConnectionFactory getConnectionFactory();

    GenericExecuteSpec sql(String sql);

    ...

    interface GenericExecuteSpec {
        GenericExecuteSpec bind(int index, Object value);

        GenericExecuteSpec bindNull(int index, Class<?> type);

        GenericExecuteSpec bind(String name, Object value);

        GenericExecuteSpec bindNull(String name, Class<?> type);

        default GenericExecuteSpec filter(Function<? super Statement, ? extends Statement> filterFunction) {
            Assert.notNull(filterFunction, "Filter function must not be null");
        }

        GenericExecuteSpec filter(StatementFilterFunction filter);

        default <R> RowsFetchSpec<R> map(Function<Row, R> mappingFunction) {
            Assert.notNull(mappingFunction, "Mapping function must not be null");
            return map((row, rowMetadata) -> mappingFunction.apply(row));
        }

        <R> RowsFetchSpec<R> map(BiFunction<Row, RowMetadata, R> mappingFunction);

        FetchSpec<Map<String, Object>> fetch();
        
        Mono<Void> then();
    }
}

FetchSpec #

  • RowsFetchSpec, UpdatedRowFetchSpec을 상속
    • RowsFetchSpec : one, first, all 메서드 제공
      • one : 없거나 혹은 하나의 결과를 Mono로 제공 (그 이상일 경우 에러 반환)
      • first : 첫번째 결과를 Mono로 제공
      • all : 모든 결과를 Flux로 제공
    • UpdatedRowFetchSpec : 쿼리의 영향을 받은 row 수를 Mono로 제공 img_2.png img_3.png

DatabaseClient 실행 #

  • sql을 실행하여 GenericExecuteSpec을 반환
  • fetch() : FetchSpec 반환
  • 예제코드를 보니, 여전히 직접 mapping 을 해줘야한다는 단점이 존재한다. img_4.png

R2dbcConverter #

  • EntityReader, EntityWriter를 상속
  • 구현체로 MappingR2dbcConverter가 존재
public interface R2dbcConverter
       extends EntityReader<Object, Row>, EntityWriter<Object, OutboundRow>, RelationalConverter {
    ...

}
public class MappingR2dbcConverter extends BasicRelationalConverter implements R2dbcConverter {

    /**
     * Creates a new {@link MappingR2dbcConverter} given {@link MappingContext}.
     *
     * @param context must not be {@literal null}.
     */
    public MappingR2dbcConverter(
          MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
       super(context, new R2dbcCustomConversions(R2dbcCustomConversions.STORE_CONVERSIONS, Collections.emptyList()));
    }

    ...
}
  • 다양한 전략을 통해서 Object를 데이터베이스의 row로, 데이터베이스의 row를 Object로 변환
    • custom converter로 mapping
    • Spring data의 object로 mapping
    • convention 기반의 mapping
    • metadata 기반의 mapping img_5.png

Custom Converter Mapping #

  • Configuration을 통해서 converter들을 등록
  • read, write 각각의 Converter이 필요 (2개) img_6.png

ReadConverter #

  • Row를 source로 Entity를 target으로 하는 converter img_7.png

WriteConverter #

  • Entity를 source로 Row를 target으로 하는 converter
  • key : column 이름
  • value : parameter.from을 이용해서 entity의 속성 전달
  • OutboudRow에 값을 추가하고, DefaultDatabaseClient에서 이를 이용해서 SQL 생성 img_8.png

CustomConverter 등록 #

  • AbstractR2dbcConfiguration를 상속하는 Configuration 생성
  • AbstractR2dbcConfiguration의 getCustomCOnverters에 custom converter들을 List 형태로 제공 img_9.png

  1. 강의 : Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지_