feat: 完成非常牛逼的动态类型和关联感知,尽可能使用原生注解实现

This commit is contained in:
wangyu 2024-06-28 11:06:53 +08:00
parent f6c2997c45
commit d84030494c
18 changed files with 248 additions and 144 deletions

View File

@ -1,7 +1,5 @@
package com.flyfish.framework.repository.base; package com.flyfish.framework.repository.base;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.EntityInformation;
/** /**
@ -17,12 +15,4 @@ public interface DomainRepository<T> {
* @return 结果 * @return 结果
*/ */
EntityInformation<T, String> getEntityInformation(); EntityInformation<T, String> getEntityInformation();
/**
* 获取持久化实体信息
*
* @param <P> 持久化属性泛型
* @return 结果
*/
<P extends PersistentProperty<P>> PersistentEntity<T, P> getPersistentEntity();
} }

View File

@ -1,6 +1,5 @@
package com.flyfish.framework.annotations.relation; package com.flyfish.framework.annotations.relation;
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.Transient;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -14,17 +13,20 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Transient @Transient
@Reference
public @interface Association { public @interface Association {
/** /**
* @return 关联在本对象上的字段名称 * @return 关联在本对象上的字段名称
* 可不指定不指定时会自动取得当前对象上 @Reference 注解的字段
* <p>
* 如当前表的detail_id字段对应detail表的id字段则该值为 detail_id * 如当前表的detail_id字段对应detail表的id字段则该值为 detail_id
*/ */
String field() default ""; String field() default "";
/** /**
* @return 关联在外部对象上的字段名称注意禁止循环依赖 * @return 关联在外部对象上的字段名称注意禁止循环依赖
* 可不指定不指定时会自动取得目标类型中 @Reference 注解的字段
* <p>
* 如当前表不存在目标表detail的关联id目标表关联本实体的id为main_id则该值为main_id * 如当前表不存在目标表detail的关联id目标表关联本实体的id为main_id则该值为main_id
* 与field互斥同时指定永远都以field为主 * 与field互斥同时指定永远都以field为主
*/ */

View File

@ -10,10 +10,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
@ -188,9 +185,4 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleReact
private Optional<Query> getQuery(Qo<T> qo) { private Optional<Query> getQuery(Qo<T> qo) {
return qo.getQuery(entityInformation); return qo.getQuery(entityInformation);
} }
@Override
public <P extends PersistentProperty<P>> PersistentEntity<T, P> getPersistentEntity() {
return null;
}
} }

View File

@ -6,7 +6,7 @@ import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
import com.flyfish.framework.r2dbc.metadata.impl.SimpleR2dbcMetadataManager; import com.flyfish.framework.r2dbc.metadata.impl.SimpleR2dbcMetadataManager;
import com.flyfish.framework.r2dbc.operations.R2dbcReactiveEntityOperations; import com.flyfish.framework.r2dbc.operations.R2dbcReactiveEntityOperations;
import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean; import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean;
import com.flyfish.framework.r2dbc.repository.impl.DefaultReactiveRepositoryImpl; import com.flyfish.framework.r2dbc.repository.impl.QueryingR2dbcReactiveRepository;
import com.flyfish.framework.repository.ReactiveEntityOperations; import com.flyfish.framework.repository.ReactiveEntityOperations;
import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -16,6 +16,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.data.mapping.callback.EntityCallback; import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing; import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator; import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
@ -31,7 +32,7 @@ import java.util.Optional;
*/ */
@EnableR2dbcRepositories( @EnableR2dbcRepositories(
repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class, repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class,
repositoryBaseClass = DefaultReactiveRepositoryImpl.class, repositoryBaseClass = QueryingR2dbcReactiveRepository.class,
basePackages = "com.flyfish.framework.*.repository" basePackages = "com.flyfish.framework.*.repository"
) )
@EnableR2dbcAuditing @EnableR2dbcAuditing
@ -49,8 +50,8 @@ public class R2dbcDataConfig {
} }
@Bean @Bean
public R2dbcMetadataManager r2dbcMetadataManager() { public R2dbcMetadataManager r2dbcMetadataManager(R2dbcMappingContext mappingContext) {
return new SimpleR2dbcMetadataManager(); return new SimpleR2dbcMetadataManager(mappingContext);
} }
/** /**

View File

@ -1,7 +1,7 @@
package com.flyfish.framework.r2dbc.config.annotations; package com.flyfish.framework.r2dbc.config.annotations;
import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean; import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean;
import com.flyfish.framework.r2dbc.repository.impl.DefaultReactiveRepositoryImpl; import com.flyfish.framework.r2dbc.repository.impl.QueryingR2dbcReactiveRepository;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@ -14,7 +14,7 @@ import java.lang.annotation.*;
*/ */
@EnableR2dbcRepositories( @EnableR2dbcRepositories(
repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class, repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class,
repositoryBaseClass = DefaultReactiveRepositoryImpl.class repositoryBaseClass = QueryingR2dbcReactiveRepository.class
) )
@Target({ElementType.TYPE}) @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

View File

@ -25,7 +25,10 @@ import org.springframework.data.util.Lazy;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -61,14 +64,14 @@ public class ReferenceR2dbcCallback implements AfterConvertCallback<Domain> {
// 遍历fields找到要注入的数据 // 遍历fields找到要注入的数据
R2dbcTableMetadata metadata = r2dbcMetadataManager.getMetadata(entityClass); R2dbcTableMetadata metadata = r2dbcMetadataManager.getMetadata(entityClass);
// 获取本实体的持久化对象 // 获取本实体的持久化对象
PersistentEntity<?, ?> persistentEntity = getRepository(entityClass).getPersistentEntity(); PersistentEntity<?, ?> persistentEntity = metadata.getEntity();
// 获取属性设置器 // 获取属性设置器
PersistentPropertyAccessor<Domain> accessor = persistentEntity.getPropertyAccessor(entity); PersistentPropertyAccessor<Domain> accessor = persistentEntity.getPropertyAccessor(entity);
// 尝试填充一对一关联 // 尝试填充一对一关联
List<Mono<Domain>> signals = new ArrayList<>(); List<Mono<Domain>> signals = new ArrayList<>();
signals.addAll(fetchAssociation(metadata.getAssociations(), persistentEntity, accessor)); signals.addAll(fetchAssociation(metadata.getAssociations(), accessor));
signals.addAll(fetchCollections(metadata.getCollections(), persistentEntity, accessor)); signals.addAll(fetchCollections(metadata.getCollections(), accessor));
// 尝试填充一对多关联 // 尝试填充一对多关联
if (CollectionUtils.isNotEmpty(signals)) { if (CollectionUtils.isNotEmpty(signals)) {
@ -87,24 +90,26 @@ public class ReferenceR2dbcCallback implements AfterConvertCallback<Domain> {
.collect(Collectors.toMap(repo -> repo.getEntityInformation().getJavaType(), Function.identity()))); .collect(Collectors.toMap(repo -> repo.getEntityInformation().getJavaType(), Function.identity())));
} }
private List<Mono<Domain>> fetchAssociation(List<R2dbcAssociation> associations, PersistentEntity<?, ?> persistentEntity, /**
PersistentPropertyAccessor<Domain> accessor) { * 拉取一对一关联
*
* @param associations 一对一关联集合
* @param accessor 本对象的属性访问器
* @return 结果
*/
private List<Mono<Domain>> fetchAssociation(List<R2dbcAssociation> associations, PersistentPropertyAccessor<Domain> accessor) {
List<Mono<Domain>> signals = new ArrayList<>(); List<Mono<Domain>> signals = new ArrayList<>();
if (CollectionUtils.isNotEmpty(associations)) { if (CollectionUtils.isNotEmpty(associations)) {
// 分区将判断提在外面提升性能 // 分区将判断提在外面提升性能
Map<Boolean, List<R2dbcAssociation>> partitions = associations.stream() Map<Boolean, List<R2dbcAssociation>> partitions = associations.stream()
.filter(R2dbcAssociation::isValid)
.collect(Collectors.partitioningBy(R2dbcAssociation::isInner)); .collect(Collectors.partitioningBy(R2dbcAssociation::isInner));
// 处理内部查询 // 处理内部查询
if (CollectionUtils.isNotEmpty(partitions.get(true))) { if (CollectionUtils.isNotEmpty(partitions.get(true))) {
partitions.get(true).forEach(association -> { partitions.get(true).forEach(association -> {
// 获取对方的仓库 // 获取对方的仓库
DefaultReactiveRepository<Domain> repository = getRepository(association.getEntityClass()); DefaultReactiveRepository<Domain> repository = getRepository(association.getEntity().getType());
// 内部查询以对象内的值作为查询条件 // 内部查询以对象内的值作为查询条件
PersistentProperty<?> property = persistentEntity.getPersistentProperty(association.getField()); Object value = accessor.getProperty(association.getField());
if (null == property) return;
// 得到条件值
Object value = accessor.getProperty(property);
if (ObjectUtils.isEmpty(value)) return; if (ObjectUtils.isEmpty(value)) return;
// 查询 // 查询
Mono<Domain> signal = repository.findById(String.valueOf(value)) Mono<Domain> signal = repository.findById(String.valueOf(value))
@ -120,12 +125,9 @@ public class ReferenceR2dbcCallback implements AfterConvertCallback<Domain> {
// 获取条件 // 获取条件
String id = accessor.getBean().getId(); String id = accessor.getBean().getId();
// 准备查询 // 准备查询
DefaultReactiveRepository<Domain> repository = getRepository(association.getEntityClass()); DefaultReactiveRepository<Domain> repository = getRepository(association.getEntity().getType());
// 获取目标属性名
PersistentProperty<?> property = repository.getPersistentEntity().getPersistentProperty(association.getField());
if (null == property) return;
// 直接通过外部字段查询 // 直接通过外部字段查询
Mono<Domain> signal = repository.findOne(Queries.where(getColumnName(property)).eq(id).wrap()) Mono<Domain> signal = repository.findOne(Queries.where(getColumnName(association.getField())).eq(id).wrap())
.map(result -> association.setValue(accessor.getBean(), result)) .map(result -> association.setValue(accessor.getBean(), result))
.defaultIfEmpty(accessor.getBean()); .defaultIfEmpty(accessor.getBean());
signals.add(signal); signals.add(signal);
@ -135,11 +137,40 @@ public class ReferenceR2dbcCallback implements AfterConvertCallback<Domain> {
return signals; return signals;
} }
private List<Mono<Domain>> fetchCollections(List<R2dbcCollection> collections, PersistentEntity<?, ?> persistentEntity, /**
* 拉取一对多关联实体
*
* @param collections 一对多关联
* @param accessor 本对象的属性访问器
* @return 结果
*/
private List<Mono<Domain>> fetchCollections(List<R2dbcCollection> collections,
PersistentPropertyAccessor<Domain> accessor) { PersistentPropertyAccessor<Domain> accessor) {
return Collections.emptyList(); List<Mono<Domain>> signals = new ArrayList<>();
if (CollectionUtils.isNotEmpty(collections)) {
// 外部查询以当前实体的id为条件去外部表的字段上查询
collections.forEach(collection -> {
// 获取条件
String id = accessor.getBean().getId();
// 准备查询
DefaultReactiveRepository<Domain> repository = getRepository(collection.getEntity().getType());
// 直接通过外部字段查询
Mono<Domain> signal = repository.findAll(Queries.where(getColumnName(collection.getField())).eq(id).wrap())
.collectList()
.map(result -> collection.setValue(accessor.getBean(), result))
.defaultIfEmpty(accessor.getBean());
signals.add(signal);
});
}
return signals;
} }
/**
* 通过持久化属性获取真实的列名
*
* @param property 属性
* @return 结果
*/
private String getColumnName(PersistentProperty<?> property) { private String getColumnName(PersistentProperty<?> property) {
if (property instanceof RelationalPersistentProperty) { if (property instanceof RelationalPersistentProperty) {
return ((RelationalPersistentProperty) property).getColumnName().getReference(); return ((RelationalPersistentProperty) property).getColumnName().getReference();

View File

@ -1,10 +1,12 @@
package com.flyfish.framework.r2dbc.metadata; package com.flyfish.framework.r2dbc.metadata;
import com.flyfish.framework.annotations.Property; import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.domain.base.Domain; import com.flyfish.framework.r2dbc.metadata.reference.FieldSetter;
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation; import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection; import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
import lombok.Data; import lombok.Data;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -18,11 +20,8 @@ import java.util.List;
@Property("表的元数据,用于关联查询") @Property("表的元数据,用于关联查询")
public class R2dbcTableMetadata { public class R2dbcTableMetadata {
@Property("表名") @Property("表实体信息")
private String tableName; private PersistentEntity<?, ?> entity;
@Property("表实体类名")
private Class<? extends Domain> entityClass;
@Property("一对一关联信息") @Property("一对一关联信息")
private List<R2dbcAssociation> associations = new ArrayList<>(); private List<R2dbcAssociation> associations = new ArrayList<>();
@ -30,12 +29,16 @@ public class R2dbcTableMetadata {
@Property("一对多关联信息") @Property("一对多关联信息")
private List<R2dbcCollection> collections = new ArrayList<>(); private List<R2dbcCollection> collections = new ArrayList<>();
public R2dbcTableMetadata(Class<? extends Domain> entityClass) { public R2dbcTableMetadata(PersistentEntity<?, ?> entity) {
this.entityClass = entityClass; this.entity = entity;
} }
public void addAssociation(org.springframework.data.mapping.model.Property property, String field, boolean inner) { public void addAssociation(PersistentEntity<?, ?> entity, PersistentProperty<?> property, FieldSetter setter, boolean inner) {
this.associations.add(R2dbcAssociation.of(property, field, inner)); this.associations.add(R2dbcAssociation.of(entity, property, setter, inner));
}
public void addCollection(PersistentEntity<?, ?> entity, PersistentProperty<?> property, FieldSetter setter) {
this.collections.add(R2dbcCollection.of(entity, property, setter));
} }
} }

View File

@ -4,24 +4,28 @@ import com.flyfish.framework.annotations.relation.Association;
import com.flyfish.framework.domain.base.Domain; import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager; import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata; import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata;
import com.flyfish.framework.r2dbc.metadata.reference.FieldSetter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.mapping.model.Property; import org.springframework.data.annotation.Reference;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.mapping.PersistentEntity;
import org.springframework.lang.NonNull; import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.ReflectionUtils; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Function; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.StreamSupport;
/** /**
* 简单的元数据管理器 * 简单的元数据管理器
@ -29,8 +33,10 @@ import java.util.stream.Collectors;
* @author wangyu * @author wangyu
* 基于线程安全的反射方法进行加载并持有 * 基于线程安全的反射方法进行加载并持有
*/ */
@RequiredArgsConstructor
public class SimpleR2dbcMetadataManager implements R2dbcMetadataManager { public class SimpleR2dbcMetadataManager implements R2dbcMetadataManager {
private final R2dbcMappingContext r2dbcMappingContext;
private final ConcurrentMap<Class<? extends Domain>, R2dbcTableMetadata> METADATA_STORE = new ConcurrentHashMap<>(); private final ConcurrentMap<Class<? extends Domain>, R2dbcTableMetadata> METADATA_STORE = new ConcurrentHashMap<>();
/** /**
@ -51,55 +57,121 @@ public class SimpleR2dbcMetadataManager implements R2dbcMetadataManager {
* @return 结果 * @return 结果
*/ */
private R2dbcTableMetadata computeMetadata(Class<? extends Domain> entityClass) { private R2dbcTableMetadata computeMetadata(Class<? extends Domain> entityClass) {
MetadataCreator creator = new MetadataCreator(entityClass); PersistentEntity<?, ?> entity = r2dbcMappingContext.getRequiredPersistentEntity(entityClass);
ReflectionUtils.doWithFields(entityClass, creator); MetadataCreator creator = new MetadataCreator(entity);
FieldUtils.getFieldsListWithAnnotation(entityClass, Association.class).forEach(creator);
return creator.metadata; return creator.metadata;
} }
private static class MetadataCreator implements ReflectionUtils.FieldCallback { private class MetadataCreator implements Consumer<Field> {
private final Map<String, PropertyDescriptor> descriptors; private final PersistentEntity<?, ?> entity;
private final ClassTypeInformation<? extends Domain> typeInformation;
private final R2dbcTableMetadata metadata; private final R2dbcTableMetadata metadata;
private MetadataCreator(Class<? extends Domain> entityClass) { private MetadataCreator(PersistentEntity<?, ?> entity) {
this.descriptors = Arrays.stream(BeanUtils.getPropertyDescriptors(entityClass)) this.entity = entity;
.collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity())); this.metadata = new R2dbcTableMetadata(entity);
this.typeInformation = ClassTypeInformation.from(entityClass); }
this.metadata = new R2dbcTableMetadata(entityClass);
private void invokeSetter(Method method, Object obj, Object value) {
try {
method.invoke(obj, value);
} catch (Exception ignored) {
}
}
private FieldSetter getSetter(Field field) {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(entity.getType(), field.getName());
return Optional.ofNullable(descriptor).map(PropertyDescriptor::getWriteMethod)
.map(method -> (FieldSetter) (obj, value) -> invokeSetter(method, obj, value))
.orElse(null);
} }
/** /**
* Perform an operation using the given field. * 处理所有关联属性
* *
* @param field the field to operate on * @param field 属性永远都不为空
*/ */
@Override @Override
public void doWith(@NonNull Field field) throws IllegalArgumentException, IllegalAccessException { public void accept(Field field) {
if (!descriptors.containsKey(field.getName())) return;
Property property = Property.of(typeInformation, field, descriptors.get(field.getName()));
Class<?> fieldType = property.getType();
ReflectionUtils.makeAccessible(field);
Association association = AnnotatedElementUtils.findMergedAnnotation(field, Association.class); Association association = AnnotatedElementUtils.findMergedAnnotation(field, Association.class);
if (null != association) { if (null == association) return;
if (ClassUtils.isAssignable(fieldType, Domain.class)) { FieldSetter setter = getSetter(field);
if (null == setter) return;
// 获取具体类型
Class<?> type = field.getType();
if (ClassUtils.isAssignable(type, Domain.class)) {
// 目标类型
PersistentEntity<?, ?> targetEntity = r2dbcMappingContext.getRequiredPersistentEntity(type);
// 一对一关联 // 一对一关联
if (StringUtils.isNotBlank(association.field())) { if (StringUtils.isNotBlank(association.field())) {
metadata.addAssociation(property, association.field(), true); // 获取内部属性
PersistentProperty<?> property = entity.getRequiredPersistentProperty(association.field());
// 添加内部关联
metadata.addAssociation(targetEntity, property, setter, true);
} else if (StringUtils.isNotBlank(association.foreignField())) { } else if (StringUtils.isNotBlank(association.foreignField())) {
metadata.addAssociation(property, association.foreignField(), false); // 获取外部属性
PersistentProperty<?> property = targetEntity.getRequiredPersistentProperty(association.foreignField());
// 添加外部关联
metadata.addAssociation(targetEntity, property, setter, false);
} else { } else {
// 尚且不支持空白策略 // 都为空从当前对象查找目标类型的reference并自动添加关联
Optional<PersistentProperty<?>> inner = findInnerKey(type);
if (inner.isPresent()) {
metadata.addAssociation(targetEntity, inner.get(), setter, true);
} else {
findForeignKey(targetEntity).ifPresent(property -> metadata.addAssociation(targetEntity, property, setter, false));
}
}
} else if (ClassUtils.isAssignable(type, Collection.class)) {
// 获取真实的类型
Class<?> entityType = ResolvableType.forField(field).resolveGeneric(0);
if (null == entityType) return;
// 目标类型
PersistentEntity<?, ?> targetEntity = r2dbcMappingContext.getRequiredPersistentEntity(entityType);
// 一对多关联仅支持外部字段
if (StringUtils.isNotBlank(association.foreignField())) {
// 获取外部属性
PersistentProperty<?> property = targetEntity.getRequiredPersistentProperty(association.foreignField());
// 添加外部关联
metadata.addCollection(targetEntity, property, setter);
} else {
// 都为空从当前对象查找目标类型的reference并自动添加关联
findForeignKey(targetEntity).ifPresent(property -> metadata.addCollection(targetEntity, property, setter));
}
}
}
/**
* 从当前实体查找目的实体的外键
* 从当前实体找到关联目标实体的id
*
* @param entityClass 目的实体类型
* @return 结果
*/
private Optional<PersistentProperty<?>> findInnerKey(Class<?> entityClass) {
return StreamSupport.stream(entity.getPersistentProperties(Reference.class).spliterator(), false)
.filter(property -> ClassUtils.isAssignable(entityClass, property.getRequiredAnnotation(Reference.class).value()))
.findFirst()
.map(p -> (PersistentProperty<?>) p);
}
/**
* 查找目的实体的外键
* 从目标实体中找到关联本实体的id
*
* @param targetEntity 目的实体信息
* @return 结果
*/
private Optional<PersistentProperty<?>> findForeignKey(PersistentEntity<?, ?> targetEntity) {
Class<?> ownerEntityClass = entity.getType();
return StreamSupport.stream(targetEntity.getPersistentProperties(Reference.class).spliterator(), false)
.filter(property -> ClassUtils.isAssignable(ownerEntityClass, property.getRequiredAnnotation(Reference.class).value()))
.findFirst()
.map(p -> (PersistentProperty<?>) p);
} }
} else if (ClassUtils.isAssignable(fieldType, Collection.class)) {
// 一对多关联
} }
}
}
}
} }

View File

@ -0,0 +1,8 @@
package com.flyfish.framework.r2dbc.metadata.reference;
import java.util.function.BiConsumer;
@FunctionalInterface
public interface FieldSetter extends BiConsumer<Object, Object> {
}

View File

@ -1,10 +1,11 @@
package com.flyfish.framework.r2dbc.metadata.reference; package com.flyfish.framework.r2dbc.metadata.reference;
import com.flyfish.framework.annotations.Property; import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.domain.base.Domain;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.springframework.data.mapping.PersistentEntity;
import java.lang.reflect.Method; import org.springframework.data.mapping.PersistentProperty;
@Data @Data
@Property("一对一关联查询") @Property("一对一关联查询")
@ -12,37 +13,23 @@ import java.lang.reflect.Method;
public class R2dbcAssociation { public class R2dbcAssociation {
@Property("关联实体类") @Property("关联实体类")
private Class<?> entityClass; private PersistentEntity<?, ?> entity;
@Property("关联字段") @Property("关联字段")
private String field; private PersistentProperty<?> field;
@Property("目标字段") @Property("目标字段")
private org.springframework.data.mapping.model.Property targetField; private FieldSetter setter;
@Property("字段是否在当前实体内部") @Property("字段是否在当前实体内部")
boolean inner; boolean inner;
public static R2dbcAssociation of(org.springframework.data.mapping.model.Property targetField, String field, boolean inner) { public static R2dbcAssociation of(PersistentEntity<?, ?> entity, PersistentProperty<?> field, FieldSetter setter, boolean inner) {
return new R2dbcAssociation(targetField.getType(), field, targetField, inner); return new R2dbcAssociation(entity, field, setter, inner);
} }
public boolean isValid() { public <T extends Domain> T setValue(T obj, Object value) {
return targetField.hasAccessor(); setter.accept(obj, value);
}
public <T> T setValue(T obj, Object value) {
if (null != value && targetField.hasAccessor()) {
targetField.getSetter().ifPresent(method -> invokeSetter(method, obj, value));
}
return obj; return obj;
} }
private void invokeSetter(Method method, Object obj, Object value) {
try {
method.invoke(obj, value);
} catch (Exception ignored) {
}
}
} }

View File

@ -2,15 +2,31 @@ package com.flyfish.framework.r2dbc.metadata.reference;
import com.flyfish.framework.annotations.Property; import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.domain.base.Domain; import com.flyfish.framework.domain.base.Domain;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@Data @Data
@Property("一对多关联查询") @Property("一对多关联查询")
@AllArgsConstructor
public class R2dbcCollection { public class R2dbcCollection {
@Property("关联实体类") @Property("关联实体类")
private Class<? extends Domain> entityClass; private PersistentEntity<?, ?> entity;
@Property("关联字段") @Property("关联字段")
private String field; private PersistentProperty<?> field;
@Property("目标字段")
private FieldSetter setter;
public static R2dbcCollection of(PersistentEntity<?, ?> entity, PersistentProperty<?> field, FieldSetter setter) {
return new R2dbcCollection(entity, field, setter);
}
public <T extends Domain> T setValue(T obj, Object value) {
setter.accept(obj, value);
return obj;
}
} }

View File

@ -1,6 +1,6 @@
package com.flyfish.framework.r2dbc.repository.factory; package com.flyfish.framework.r2dbc.repository.factory;
import com.flyfish.framework.r2dbc.repository.impl.DefaultReactiveRepositoryImpl; import com.flyfish.framework.r2dbc.repository.impl.QueryingR2dbcReactiveRepository;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
@ -16,12 +16,12 @@ public class DefaultReactiveRepositoryFactory extends R2dbcRepositoryFactory {
*/ */
public DefaultReactiveRepositoryFactory(R2dbcEntityOperations operations) { public DefaultReactiveRepositoryFactory(R2dbcEntityOperations operations) {
super(operations); super(operations);
this.setRepositoryBaseClass(DefaultReactiveRepositoryImpl.class); this.setRepositoryBaseClass(QueryingR2dbcReactiveRepository.class);
} }
@Override @Override
@NonNull @NonNull
protected Class<?> getRepositoryBaseClass(@NonNull RepositoryMetadata metadata) { protected Class<?> getRepositoryBaseClass(@NonNull RepositoryMetadata metadata) {
return DefaultReactiveRepositoryImpl.class; return QueryingR2dbcReactiveRepository.class;
} }
} }

View File

@ -13,12 +13,10 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository; import org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.relational.repository.query.RelationalEntityInformation;
import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.util.Lazy;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -32,7 +30,7 @@ import java.util.Optional;
* *
* @param <T> 泛型 * @param <T> 泛型
*/ */
public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleR2dbcRepository<T, String> public class QueryingR2dbcReactiveRepository<T extends Domain> extends SimpleR2dbcRepository<T, String>
implements DefaultReactiveRepository<T> { implements DefaultReactiveRepository<T> {
@Getter @Getter
@ -40,8 +38,6 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleR2dbc
private final R2dbcEntityOperations entityOperations; private final R2dbcEntityOperations entityOperations;
private final Lazy<RelationalPersistentEntity<T>> persistentEntity;
/** /**
* 构造新的默认仓库 * 构造新的默认仓库
* *
@ -49,13 +45,11 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleR2dbc
* @param entityOperations 数据库操作模板 * @param entityOperations 数据库操作模板
* @param converter 转换器 * @param converter 转换器
*/ */
public DefaultReactiveRepositoryImpl(RelationalEntityInformation<T, String> entity, R2dbcEntityOperations entityOperations, R2dbcConverter converter) { public QueryingR2dbcReactiveRepository(RelationalEntityInformation<T, String> entity, R2dbcEntityOperations entityOperations, R2dbcConverter converter) {
super(entity, entityOperations, converter); super(entity, entityOperations, converter);
this.entity = entity; this.entity = entity;
this.entityOperations = entityOperations; this.entityOperations = entityOperations;
this.persistentEntity = Lazy.of(() -> (RelationalPersistentEntity<T>) converter
.getMappingContext()
.getRequiredPersistentEntity(entity.getJavaType()));
} }
/** /**
@ -236,12 +230,6 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleR2dbc
return entity; return entity;
} }
@SuppressWarnings("unchecked")
@Override
public RelationalPersistentEntity<T> getPersistentEntity() {
return persistentEntity.get();
}
private Mono<Query> getQuery(Qo<T> qo) { private Mono<Query> getQuery(Qo<T> qo) {
return Mono.justOrEmpty(qo.getQuery(entity)); return Mono.justOrEmpty(qo.getQuery(entity));
} }

View File

@ -1,6 +1,7 @@
package com.flyfish.framework.r2dbc; package com.flyfish.framework.r2dbc;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.flyfish.framework.r2dbc.config.R2dbcDataConfig; import com.flyfish.framework.r2dbc.config.R2dbcDataConfig;
import com.flyfish.framework.r2dbc.domain.TestDO; import com.flyfish.framework.r2dbc.domain.TestDO;
import com.flyfish.framework.r2dbc.domain.TestQO; import com.flyfish.framework.r2dbc.domain.TestQO;
@ -26,7 +27,7 @@ public class R2DbcRepositoryTest {
@Test @Test
public void test() { public void test() {
TestQO qo = new TestQO(); TestQO qo = new TestQO();
qo.setName("2"); qo.setName("1");
// 开启关联查询 // 开启关联查询
qo.setFetchRef(true); qo.setFetchRef(true);
@ -36,7 +37,7 @@ public class R2DbcRepositoryTest {
test.setName("测试名称"); test.setName("测试名称");
test.setOtherId("1"); test.setOtherId("1");
System.out.println(JSON.toJSONString(testRepository.insert(test).block())); System.out.println(JSON.toJSONString(testRepository.insert(test).block(), SerializerFeature.PrettyFormat));
System.out.println(JSON.toJSONString(testRepository.findAll(qo).collectList().block())); System.out.println(JSON.toJSONString(testRepository.findAll(qo).collectList().block(), SerializerFeature.PrettyFormat));
} }
} }

View File

@ -6,24 +6,32 @@ import com.flyfish.framework.r2dbc.domain.reference.TestAsso;
import com.flyfish.framework.r2dbc.domain.reference.TestChild; import com.flyfish.framework.r2dbc.domain.reference.TestChild;
import com.flyfish.framework.r2dbc.domain.reference.TestOther; import com.flyfish.framework.r2dbc.domain.reference.TestOther;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Reference;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Table;
import java.util.List; import java.util.List;
/**
* 测试实体
* 体现了所有的情况
*
* @author wangyu
*/
@Table("test") @Table("test")
@Data @Data
public class TestDO extends AuditDomain { public class TestDO extends AuditDomain {
@Column("other_id") @Column("other_id")
@Reference(TestAsso.class)
private String otherId; private String otherId;
@Association(field = "otherId") @Association
private TestAsso asso; private TestAsso asso;
@Association(foreignField = "testId") @Association
private TestOther other; private TestOther other;
@Association(foreignField = "parentId") @Association
private List<TestChild> children; private List<TestChild> children;
} }

View File

@ -1,7 +1,9 @@
package com.flyfish.framework.r2dbc.domain.reference; package com.flyfish.framework.r2dbc.domain.reference;
import com.flyfish.framework.domain.base.AuditDomain; import com.flyfish.framework.domain.base.AuditDomain;
import com.flyfish.framework.r2dbc.domain.TestDO;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Reference;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Table;
@ -10,5 +12,6 @@ import org.springframework.data.relational.core.mapping.Table;
public class TestChild extends AuditDomain { public class TestChild extends AuditDomain {
@Column("parent_id") @Column("parent_id")
@Reference(TestDO.class)
private String parentId; private String parentId;
} }

View File

@ -1,9 +1,9 @@
package com.flyfish.framework.r2dbc.domain.reference; package com.flyfish.framework.r2dbc.domain.reference;
import com.flyfish.framework.annotations.relation.Association;
import com.flyfish.framework.domain.base.AuditDomain; import com.flyfish.framework.domain.base.AuditDomain;
import com.flyfish.framework.r2dbc.domain.TestDO; import com.flyfish.framework.r2dbc.domain.TestDO;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Reference;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Table;
@ -12,5 +12,6 @@ import org.springframework.data.relational.core.mapping.Table;
public class TestOther extends AuditDomain { public class TestOther extends AuditDomain {
@Column("test_id") @Column("test_id")
@Reference(TestDO.class)
private String testId; private String testId;
} }

View File

@ -1,3 +1,4 @@
DROP TABLE IF EXISTS `test`; DROP TABLE IF EXISTS `test`;
DROP TABLE IF EXISTS `test_child`; DROP TABLE IF EXISTS `test_child`;
DROP TABLE IF EXISTS `test_asso`; DROP TABLE IF EXISTS `test_asso`;
DROP TABLE IF EXISTS `test_other`;