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 확장
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로 제공
DatabaseClient 실행
#
- sql을 실행하여 GenericExecuteSpec을 반환
- fetch() : FetchSpec 반환
- 예제코드를 보니, 여전히 직접 mapping 을 해줘야한다는 단점이 존재한다.
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
Custom Converter Mapping
#
- Configuration을 통해서 converter들을 등록
- read, write 각각의 Converter이 필요 (2개)
ReadConverter
#
- Row를 source로 Entity를 target으로 하는 converter
WriteConverter
#
- Entity를 source로 Row를 target으로 하는 converter
- key : column 이름
- value : parameter.from을 이용해서 entity의 속성 전달
- OutboudRow에 값을 추가하고, DefaultDatabaseClient에서 이를 이용해서 SQL 생성
CustomConverter 등록
#
- AbstractR2dbcConfiguration를 상속하는 Configuration 생성
- AbstractR2dbcConfiguration의 getCustomCOnverters에 custom converter들을 List 형태로 제공