Compare commits
No commits in common. "master" and "captcha" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,8 +4,6 @@
|
||||
**/*.iml
|
||||
**/.idea/**
|
||||
|
||||
.flattened-pom.xml
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -17,20 +17,14 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-relational</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-web</artifactId>
|
||||
<version>${revision}</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -4,8 +4,8 @@ import com.flyfish.framework.annotations.EnumValue;
|
||||
import com.flyfish.framework.annotations.Order;
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.approval.enums.ApproveStatus;
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.authorized.AuthorizedQo;
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@ -24,7 +24,7 @@ public class ApprovalDomainQo<T extends ApprovalDomain> extends AuthorizedQo<T>
|
||||
private String status;
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
return super.queryBuilder().mutate().and(T::getApproveStatus).eq(status);
|
||||
public CriteriaBuilder<T> criteriaBuilder() {
|
||||
return super.criteriaBuilder().with("status", "approveStatus");
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* 审批记录
|
||||
@ -14,7 +12,6 @@ import org.springframework.data.relational.core.mapping.Table;
|
||||
* @author wangyu
|
||||
*/
|
||||
@Document(collection = "approve-records")
|
||||
@Table("t_approve_records")
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApproveRecord extends AuditDomain {
|
||||
@ -26,11 +23,9 @@ public class ApproveRecord extends AuditDomain {
|
||||
private String module;
|
||||
|
||||
// 模块名称
|
||||
@Column("module_name")
|
||||
private String moduleName;
|
||||
|
||||
// 数据id
|
||||
@Column("data_id")
|
||||
private String dataId;
|
||||
|
||||
// 审批人
|
||||
|
@ -5,7 +5,6 @@ import com.flyfish.framework.approval.enums.ApproveAction;
|
||||
import com.flyfish.framework.domain.base.Vo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -24,7 +23,7 @@ public class ApproveRecordListVo implements Vo<ApproveRecord> {
|
||||
private String approver;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
||||
private LocalDateTime approveTime;
|
||||
private Date approveTime;
|
||||
|
||||
private String action;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.flyfish.framework.approval.domain.record;
|
||||
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@ -27,12 +27,7 @@ public class ApproveRecordQo extends NameLikeQo<ApproveRecord> {
|
||||
private String approver;
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
return super.queryBuilder()
|
||||
.mutate()
|
||||
.and(ApproveRecord::getModule).eq(module)
|
||||
.and(ApproveRecord::getDataId).eq(dataId)
|
||||
.and(ApproveRecord::getApproved).eq(approved)
|
||||
.and(ApproveRecord::getApprover).eq(approver);
|
||||
public CriteriaBuilder<ApproveRecord> criteriaBuilder() {
|
||||
return super.criteriaBuilder().with("module", "dataId", "approved", "approver");
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import com.flyfish.framework.approval.domain.record.ApproveRecord;
|
||||
import com.flyfish.framework.domain.base.Vo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@ -25,7 +24,7 @@ public class ApprovalListVo implements Vo<ApproveRecord> {
|
||||
private String dataId;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
|
||||
private LocalDateTime createTime;
|
||||
private Date createTime;
|
||||
|
||||
private String creator;
|
||||
|
||||
|
@ -39,11 +39,6 @@ public class ModuleDelegateService {
|
||||
// 审批的服务们
|
||||
private Map<String, BaseReactiveService<ApprovalDomain>> approvalServices;
|
||||
|
||||
private String getModuleName(BaseReactiveService<ApprovalDomain> service) {
|
||||
return ReflectionUtils.getGenericType(service.getClass()).map(this::moduleName)
|
||||
.orElseThrow(() -> new InvalidBusinessException("系统模块名称抽取失败!"));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("all")
|
||||
public void setApprovalServices(ObjectProvider<BaseReactiveService> services) {
|
||||
@ -52,7 +47,7 @@ public class ModuleDelegateService {
|
||||
.map(clazz -> ClassUtils.isAssignable(clazz, ApprovalDomain.class))
|
||||
.orElse(false))
|
||||
.map(service -> (BaseReactiveService<ApprovalDomain>) service)
|
||||
.collect(Collectors.toMap(this::getModuleName, s -> s));
|
||||
.collect(Collectors.toMap(s -> s.getEntityInformation().getCollectionName(), s -> s));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,21 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `t_approve_records`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`approved` BIT(1) NULL DEFAULT b'0' COMMENT '是否已审批',
|
||||
`module` VARCHAR(50) NOT NULL COMMENT '模块',
|
||||
`module_name` VARCHAR(50) NULL COMMENT '模块名称',
|
||||
`data_id` VARCHAR(36) NULL COMMENT '数据id',
|
||||
`approver` VARCHAR(36) NULL COMMENT '审批人',
|
||||
`action` VARCHAR(24) NULL COMMENT '审批动作',
|
||||
`opinion` VARCHAR(500) NULL COMMENT '审批意见',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT '审批记录';
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -17,20 +17,10 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-relational</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-web</artifactId>
|
||||
<version>${revision}</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -6,7 +6,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* 系统备份
|
||||
@ -16,7 +15,6 @@ import org.springframework.data.relational.core.mapping.Table;
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "backups")
|
||||
@Table("t_backup")
|
||||
public class Backup extends AuditDomain {
|
||||
|
||||
// 文件路径
|
||||
|
@ -3,11 +3,10 @@ package com.flyfish.framework.backup.domain;
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 版本号实体
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
@ -23,5 +22,5 @@ public class Version {
|
||||
private String script;
|
||||
|
||||
@Property("创建时间")
|
||||
private LocalDateTime time;
|
||||
private Date time;
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ package com.flyfish.framework.backup.scheduler;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.flyfish.framework.backup.domain.Backup;
|
||||
import com.flyfish.framework.context.DateContext;
|
||||
import com.flyfish.framework.domain.base.DomainService;
|
||||
import com.flyfish.framework.query.Query;
|
||||
import com.flyfish.framework.repository.ReactiveEntityOperations;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -15,8 +12,8 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.util.CastUtils;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
@ -25,11 +22,12 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.AsynchronousFileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@ -47,9 +45,9 @@ public class BackupScheduler {
|
||||
// data buffer 工厂
|
||||
private final DataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
// 用于注入所有集合名称
|
||||
private List<EntityInformation<?, String>> collections;
|
||||
private List<MongoEntityInformation<?, String>> collections;
|
||||
// 异步的mongo操作,可以快速备份
|
||||
private ReactiveEntityOperations operations;
|
||||
private ReactiveMongoOperations operations;
|
||||
// 备份路径
|
||||
private String backupPath = "/opt/flyfish/backup";
|
||||
|
||||
@ -59,8 +57,8 @@ public class BackupScheduler {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setOperations(ReactiveEntityOperations reactiveEntityOperations) {
|
||||
this.operations = reactiveEntityOperations;
|
||||
public void setOperations(ReactiveMongoOperations reactiveMongoOperations) {
|
||||
this.operations = reactiveMongoOperations;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@ -83,7 +81,7 @@ public class BackupScheduler {
|
||||
String parent = createIfNotExists(backupPath + "/" + code);
|
||||
// 开始备份,先构造一个指示器
|
||||
Backup backup = new Backup();
|
||||
backup.setCreateTime(LocalDateTime.now());
|
||||
backup.setCreateTime(new Date());
|
||||
backup.setCreator("系统");
|
||||
backup.setCode(code);
|
||||
backup.setStatus(Backup.Status.RUNNING);
|
||||
@ -94,8 +92,8 @@ public class BackupScheduler {
|
||||
operations.save(backup)
|
||||
.thenMany(Flux.fromIterable(this.collections))
|
||||
.flatMap(info -> operations
|
||||
.findAll(Query.empty(), CastUtils.cast(info)).collectList()
|
||||
.map(list -> new BackupIndex.BackupContent(info.getJavaType().getSimpleName(), JSON.toJSONBytes(list)))
|
||||
.findAll(info.getJavaType(), info.getCollectionName()).collectList()
|
||||
.map(list -> new BackupIndex.BackupContent(info.getCollectionName(), JSON.toJSONBytes(list)))
|
||||
)
|
||||
.flatMap(content -> writeContents(content, parent))
|
||||
.collectList()
|
||||
@ -117,7 +115,7 @@ public class BackupScheduler {
|
||||
})
|
||||
.then(Mono.defer(() -> {
|
||||
backup.setLog("成功备份");
|
||||
backup.setPeriod(DateContext.distance(backup.getCreateTime(), LocalDateTime.now()));
|
||||
backup.setPeriod(new Date().getTime() - backup.getCreateTime().getTime());
|
||||
backup.setStatus(Backup.Status.SUCCESS);
|
||||
backup.setSize(DataSize.ofBytes(sizeCounter.get()).toKilobytes() + "KB");
|
||||
return operations.save(backup);
|
||||
@ -127,7 +125,7 @@ public class BackupScheduler {
|
||||
}, e -> {
|
||||
backup.setStatus(Backup.Status.FAILED);
|
||||
backup.setLog(e.getMessage());
|
||||
backup.setPeriod(DateContext.distance(backup.getCreateTime(), LocalDateTime.now()));
|
||||
backup.setPeriod(new Date().getTime() - backup.getCreateTime().getTime());
|
||||
operations.save(backup).subscribe();
|
||||
});
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `t_backup`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`filepath` VARCHAR(1000) NULL COMMENT '文件路径',
|
||||
`log` TEXT NULL COMMENT '备份日志',
|
||||
`status` VARCHAR(24) NULL COMMENT '备份状态',
|
||||
`period` BIGINT NULL COMMENT '备份耗时',
|
||||
`size` VARCHAR(64) NULL COMMENT '备份大小',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT '备份记录表';
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -59,6 +59,10 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
@ -2,7 +2,6 @@ package com.flyfish.framework.compiler;
|
||||
|
||||
import com.flyfish.framework.compiler.support.DelegateJavaCompiler;
|
||||
import com.flyfish.framework.compiler.support.SimpleJavaCompiler;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@ -38,18 +37,7 @@ public interface DynamicJavaCompiler extends Closeable {
|
||||
*
|
||||
* @param name 文件名
|
||||
* @param source 源码
|
||||
* @return 完整类名
|
||||
*/
|
||||
@Nullable
|
||||
String compile(String name, String source) throws IOException;
|
||||
|
||||
/**
|
||||
* 加载类
|
||||
* 用于全部编译后,再加载
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
Class<?> load(String name);
|
||||
Class<?> compile(String name, String source) throws ClassNotFoundException, IOException;
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ package com.flyfish.framework.compiler.core;
|
||||
*/
|
||||
public interface ClassLoaders {
|
||||
|
||||
DynamicClassLoader CLASS_LOADER = new MemoryClassLoader();
|
||||
MemoryClassLoader CLASS_LOADER = new MemoryClassLoader();
|
||||
|
||||
static DynamicClassLoader memory() {
|
||||
static MemoryClassLoader memory() {
|
||||
return CLASS_LOADER;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
package com.flyfish.framework.compiler.core;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态类加载器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public abstract class DynamicClassLoader extends URLClassLoader {
|
||||
|
||||
DynamicClassLoader() {
|
||||
super(new URL[0], DynamicClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的字节码
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public abstract Map<String, byte[]> getClassBytes();
|
||||
}
|
@ -1,31 +1,30 @@
|
||||
package com.flyfish.framework.compiler.core;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* In-memory compile Java source code as String.
|
||||
*
|
||||
* @author michael
|
||||
*/
|
||||
@Slf4j
|
||||
class JavaStringCompiler implements VendorJavaCompiler {
|
||||
public class JavaStringCompiler implements Closeable {
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
private final MemoryJavaFileManager manager;
|
||||
private final MemoryClassLoader classLoader;
|
||||
|
||||
JavaStringCompiler(DynamicClassLoader classLoader) {
|
||||
public JavaStringCompiler(MemoryClassLoader classLoader) {
|
||||
this.compiler = ToolProvider.getSystemJavaCompiler();
|
||||
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
|
||||
this.manager = new MemoryJavaFileManager(stdManager, classLoader);
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,27 +34,27 @@ class JavaStringCompiler implements VendorJavaCompiler {
|
||||
* @param source The source code as String.
|
||||
* @return The compiled results as Map that contains class name as key,
|
||||
* class binary as value.
|
||||
* @throws IOException If compile error.
|
||||
*/
|
||||
@Override
|
||||
public String compile(String fileName, String source) {
|
||||
public Map<String, byte[]> compile(String fileName, String source) {
|
||||
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
|
||||
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Collections.singletonList(javaFileObject));
|
||||
String className = StringUtils.substringBeforeLast(fileName, ".");
|
||||
if (BooleanUtils.isTrue(task.call())) {
|
||||
return manager.getClassBytes().keySet().stream().filter(key -> key.endsWith(className))
|
||||
.findFirst().orElse(null);
|
||||
Boolean result = task.call();
|
||||
if (result == null || !result) {
|
||||
throw new RuntimeException("Compilation failed.");
|
||||
}
|
||||
return null;
|
||||
return manager.getClassBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态classLoader
|
||||
* Load class from compiled classes.
|
||||
*
|
||||
* @return 结果
|
||||
* @param name Full class name.
|
||||
* @return The Class instance.
|
||||
* @throws ClassNotFoundException If class not found.
|
||||
*/
|
||||
@Override
|
||||
public DynamicClassLoader getClassLoader() {
|
||||
return ClassLoaders.memory();
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
return classLoader.loadClass(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,9 +63,11 @@ class JavaStringCompiler implements VendorJavaCompiler {
|
||||
if (null != manager) {
|
||||
manager.close();
|
||||
}
|
||||
getClassLoader().close();
|
||||
if (null != classLoader) {
|
||||
classLoader.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("关闭类编译器失败!", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package com.flyfish.framework.compiler.core;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -10,11 +12,15 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*
|
||||
* @author michael
|
||||
*/
|
||||
class MemoryClassLoader extends DynamicClassLoader {
|
||||
class MemoryClassLoader extends URLClassLoader {
|
||||
|
||||
// class name to class bytes:
|
||||
private final Map<String, byte[]> classBytes = new ConcurrentHashMap<>();
|
||||
|
||||
public MemoryClassLoader() {
|
||||
super(new URL[0], MemoryClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] buf = classBytes.get(name);
|
||||
@ -25,7 +31,6 @@ class MemoryClassLoader extends DynamicClassLoader {
|
||||
return defineClass(name, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, byte[]> getClassBytes() {
|
||||
return classBytes;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.flyfish.framework.compiler.core;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.tools.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
@ -9,82 +7,87 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* In-memory java file manager.
|
||||
*
|
||||
* @author michael
|
||||
*/
|
||||
@Getter
|
||||
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
// compiled classes in bytes:
|
||||
private final Map<String, byte[]> classBytes;
|
||||
// compiled classes in bytes:
|
||||
private final Map<String, byte[]> classBytes;
|
||||
|
||||
MemoryJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
|
||||
super(fileManager);
|
||||
classBytes = classLoader.getClassBytes();
|
||||
}
|
||||
MemoryJavaFileManager(JavaFileManager fileManager, MemoryClassLoader classLoader) {
|
||||
super(fileManager);
|
||||
classBytes = classLoader.getClassBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
public Map<String, byte[]> getClassBytes() {
|
||||
return this.classBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
classBytes.clear();
|
||||
}
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind,
|
||||
FileObject sibling) throws IOException {
|
||||
if (kind == JavaFileObject.Kind.CLASS) {
|
||||
return new MemoryOutputJavaFileObject(className);
|
||||
} else {
|
||||
return super.getJavaFileForOutput(location, className, kind, sibling);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
classBytes.clear();
|
||||
}
|
||||
|
||||
JavaFileObject makeStringSource(String name, String code) {
|
||||
return new MemoryInputJavaFileObject(name, code);
|
||||
}
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind,
|
||||
FileObject sibling) throws IOException {
|
||||
if (kind == JavaFileObject.Kind.CLASS) {
|
||||
return new MemoryOutputJavaFileObject(className);
|
||||
} else {
|
||||
return super.getJavaFileForOutput(location, className, kind, sibling);
|
||||
}
|
||||
}
|
||||
|
||||
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
|
||||
JavaFileObject makeStringSource(String name, String code) {
|
||||
return new MemoryInputJavaFileObject(name, code);
|
||||
}
|
||||
|
||||
final String code;
|
||||
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
|
||||
|
||||
MemoryInputJavaFileObject(String name, String code) {
|
||||
super(URI.create("string:///" + name), Kind.SOURCE);
|
||||
this.code = code;
|
||||
}
|
||||
final String code;
|
||||
|
||||
@Override
|
||||
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
|
||||
return CharBuffer.wrap(code);
|
||||
}
|
||||
}
|
||||
MemoryInputJavaFileObject(String name, String code) {
|
||||
super(URI.create("string:///" + name), Kind.SOURCE);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
|
||||
final String name;
|
||||
@Override
|
||||
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
|
||||
return CharBuffer.wrap(code);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryOutputJavaFileObject(String name) {
|
||||
super(URI.create("string:///" + name), Kind.CLASS);
|
||||
this.name = name;
|
||||
}
|
||||
class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
|
||||
final String name;
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return new FilterOutputStream(new ByteArrayOutputStream()) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
|
||||
classBytes.put(name, bos.toByteArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
MemoryOutputJavaFileObject(String name) {
|
||||
super(URI.create("string:///" + name), Kind.CLASS);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return new FilterOutputStream(new ByteArrayOutputStream()) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
|
||||
classBytes.put(name, bos.toByteArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
package com.flyfish.framework.compiler.core;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* 第三方java编译器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface VendorJavaCompiler extends Closeable {
|
||||
|
||||
static VendorJavaCompiler string() {
|
||||
return new JavaStringCompiler(ClassLoaders.memory());
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译并返回编译结果
|
||||
*
|
||||
* @param name 文件名
|
||||
* @param source 源码
|
||||
* @return 完整类名
|
||||
*/
|
||||
String compile(String name, String source);
|
||||
|
||||
/**
|
||||
* 获取动态classLoader
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
DynamicClassLoader getClassLoader();
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
package com.flyfish.framework.compiler.support;
|
||||
|
||||
import com.flyfish.framework.compiler.DynamicJavaCompiler;
|
||||
import com.flyfish.framework.compiler.core.VendorJavaCompiler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.flyfish.framework.compiler.core.ClassLoaders;
|
||||
import com.flyfish.framework.compiler.core.JavaStringCompiler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 使用第三方成熟框架
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public class DelegateJavaCompiler implements DynamicJavaCompiler {
|
||||
|
||||
// 第三方编译器
|
||||
private final VendorJavaCompiler compiler = VendorJavaCompiler.string();
|
||||
private final JavaStringCompiler compiler = new JavaStringCompiler(ClassLoaders.memory());
|
||||
|
||||
private DelegateJavaCompiler() {
|
||||
}
|
||||
@ -29,32 +29,22 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
|
||||
*
|
||||
* @param name 文件名
|
||||
* @param source 源码
|
||||
* @return 完整类名
|
||||
*/
|
||||
@Override
|
||||
public String compile(String name, String source) {
|
||||
return compiler.compile(name, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载类
|
||||
* 用于全部编译后,再加载
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Class<?> load(String name) {
|
||||
try {
|
||||
return compiler.getClassLoader().loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.warn("类不存在!{}", name);
|
||||
return null;
|
||||
public Class<?> compile(String name, String source) throws ClassNotFoundException {
|
||||
Map<String, byte[]> byteMap = compiler.compile(name, source);
|
||||
String className = StringUtils.substringBeforeLast(name, ".");
|
||||
for (String key : byteMap.keySet()) {
|
||||
if (key.contains(className)) {
|
||||
return compiler.loadClass(key);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("【java编译器】源码中没有可以编译的类!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
public void close() {
|
||||
compiler.close();
|
||||
}
|
||||
|
||||
@ -62,4 +52,12 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
|
||||
|
||||
private static final DelegateJavaCompiler INSTANCE = new DelegateJavaCompiler();
|
||||
}
|
||||
|
||||
|
||||
// public static void main(String[] args) throws ClassNotFoundException, IOException {
|
||||
// long time = System.currentTimeMillis();
|
||||
// System.out.println(StreamUtils.copyToString(DelegateJavaCompiler.class.getResourceAsStream("./"), StandardCharsets.UTF_8));
|
||||
// Class<?> clazz = getInstance().compile("Fuck.java", "package com.flyfish.project; public class Fuck {}");
|
||||
// System.out.println(clazz + "耗时:" + (System.currentTimeMillis() - time) / 1000.0 + "秒");
|
||||
// }
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package com.flyfish.framework.compiler.support;
|
||||
|
||||
import com.flyfish.framework.compiler.DynamicJavaCompiler;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
@ -45,10 +44,10 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public String compile(String name, String source) {
|
||||
public Class<?> compile(String name, String source) throws ClassNotFoundException {
|
||||
// 存在缓存,写入
|
||||
if (classMap.containsKey(name)) {
|
||||
return classMap.get(name).getName();
|
||||
return classMap.get(name);
|
||||
}
|
||||
// 构建java文件对象
|
||||
JavaFileObject fileObject = new StringJavaObject(name, source);
|
||||
@ -56,32 +55,12 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
|
||||
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null,
|
||||
options, null, Collections.singletonList(fileObject));
|
||||
// 阻塞编译
|
||||
if (BooleanUtils.isTrue(task.call())) {
|
||||
return "com.flyfish.project." + name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载类
|
||||
* 用于全部编译后,再加载
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Class<?> load(String name) {
|
||||
String simpleName = StringUtils.substringAfterLast(name, ".");
|
||||
if (classMap.containsKey(simpleName)) {
|
||||
return classMap.get(simpleName);
|
||||
}
|
||||
try {
|
||||
Class<?> clazz = Class.forName(name);
|
||||
classMap.put(clazz.getSimpleName(), clazz);
|
||||
if (task.call()) {
|
||||
Class<?> clazz = Class.forName("com.flyfish.project." + name);
|
||||
classMap.put(name, clazz);
|
||||
return clazz;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
throw new RuntimeException("【java编译器】尝试编译源代码出现异常!");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -12,7 +12,6 @@ import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -23,28 +22,26 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class TemplateCompiler {
|
||||
|
||||
// 变量池缓存
|
||||
private static final Map<Class<?>, Map<String, Method>> POOL_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 编译模板文件和源码数据为源代码字符串
|
||||
*
|
||||
* @param dataClass 数据class
|
||||
* @param filename 文件名
|
||||
* @param filename 文件名
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T> Function<T, String> compile(Class<T> dataClass, String filename) {
|
||||
public static Function<JavaSource, String> compile(Class<?> dataClass, String filename) {
|
||||
// 获取文件内容
|
||||
InputStream fis = TemplateCompiler.class.getResourceAsStream(filename);
|
||||
try {
|
||||
// 获取模板内容
|
||||
String template = StreamUtils.copyToString(fis, StandardCharsets.UTF_8);
|
||||
// 准备变量池
|
||||
Map<String, Method> pool = methodPool(dataClass);
|
||||
Map<String, PropertyDescriptor> pool = Arrays.stream(BeanUtils.getPropertyDescriptors(dataClass))
|
||||
.collect(Collectors.toMap(descriptor -> "#{" + descriptor.getName() + "}", a -> a));
|
||||
// 编译
|
||||
return cfg -> pool.keySet().stream().reduce(template, (result, key) -> {
|
||||
// 获取读取方法
|
||||
Method method = pool.get(key);
|
||||
Method method = pool.get(key).getReadMethod();
|
||||
Object value = null;
|
||||
try {
|
||||
// 取得结果
|
||||
@ -60,17 +57,6 @@ public class TemplateCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据类方法池
|
||||
*
|
||||
* @param dataClass 数据类
|
||||
* @return 结果
|
||||
*/
|
||||
private static Map<String, Method> methodPool(Class<?> dataClass) {
|
||||
return POOL_CACHE.computeIfAbsent(dataClass, k -> Arrays.stream(BeanUtils.getPropertyDescriptors(k))
|
||||
.collect(Collectors.toMap(descriptor -> "#{" + descriptor.getName() + "}", PropertyDescriptor::getReadMethod)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个值
|
||||
*
|
||||
|
@ -5,9 +5,6 @@ import org.springframework.stereotype.Component;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -23,7 +20,6 @@ public class DateContext {
|
||||
private static DateContext instance;
|
||||
private ThreadLocal<DateFormat> formatThreadLocal = new ThreadLocal<>();
|
||||
private ThreadLocal<Map<String, DateFormat>> mapThreadLocal = new ThreadLocal<>();
|
||||
private static final ZoneId zoneId = ZoneId.of("Asia/Shanghai");
|
||||
|
||||
public DateContext() {
|
||||
instance = this;
|
||||
@ -33,19 +29,6 @@ public class DateContext {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static LocalDateTime ofMillis(long millis) {
|
||||
Instant instant = Instant.ofEpochMilli(millis);
|
||||
return LocalDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
|
||||
public static long toMillis(LocalDateTime localDateTime) {
|
||||
return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static long distance(LocalDateTime start, LocalDateTime end) {
|
||||
return toMillis(end) - toMillis(start);
|
||||
}
|
||||
|
||||
public static DateFormat simpleFormat() {
|
||||
DateFormat format = instance.formatThreadLocal.get();
|
||||
if (format == null) {
|
||||
|
@ -1,15 +0,0 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
/**
|
||||
* 构建器表示
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Builder<T> {
|
||||
|
||||
/**
|
||||
* 构建对象
|
||||
*
|
||||
* @return 构建结果
|
||||
*/
|
||||
T build();
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 内部的包扫描器,提供特定注解扫描
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class ClassPathResourceScanner extends ClassPathScanningCandidateComponentProvider {
|
||||
|
||||
private final TypeFilter filter;
|
||||
|
||||
/**
|
||||
* 扫描某个特定注解
|
||||
*
|
||||
* @param annoType 注解类型
|
||||
* @return 结果
|
||||
*/
|
||||
public static ClassPathResourceScanner forAnnotation(Class<? extends Annotation> annoType) {
|
||||
return new ClassPathResourceScanner(new AnnotationTypeFilter(annoType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描子类
|
||||
*
|
||||
* @param superType 父类型
|
||||
* @return 结果
|
||||
*/
|
||||
public static ClassPathResourceScanner forSuperType(Class<?> superType) {
|
||||
return new ClassPathResourceScanner(new AssignableTypeFilter(superType));
|
||||
}
|
||||
|
||||
private ClassPathResourceScanner(TypeFilter filter) {
|
||||
super(false);
|
||||
this.filter = filter;
|
||||
resetFilters(false);
|
||||
addIncludeFilter(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateComponent(@NonNull MetadataReader metadataReader) throws IOException {
|
||||
return filter.match(metadataReader, getMetadataReaderFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateComponent(@NonNull AnnotatedBeanDefinition beanDefinition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Class<?> resolveType(BeanDefinition bf, ClassLoader cl) {
|
||||
if (null != bf.getBeanClassName()) {
|
||||
try {
|
||||
return ClassUtils.forName(bf.getBeanClassName(), cl);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描类
|
||||
*
|
||||
* @param packageNames 包名
|
||||
* @return 结果
|
||||
*/
|
||||
public Set<Class<?>> scan(Collection<String> packageNames) {
|
||||
// 获取扫描器的ClassLoader,保证同源
|
||||
ClassLoader cl = this.getClass().getClassLoader();
|
||||
Set<Class<?>> scanned = new HashSet<>();
|
||||
for (String packageName : packageNames) {
|
||||
Set<BeanDefinition> bfs = findCandidateComponents(packageName);
|
||||
// 不存在,不要浪费性能
|
||||
if (CollectionUtils.isEmpty(bfs)) continue;
|
||||
// 代理并生成子类,并注册到ioc容器
|
||||
for (BeanDefinition bf : bfs) {
|
||||
Class<?> resolved = resolveType(bf, cl);
|
||||
if (null != resolved) {
|
||||
scanned.add(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
return scanned;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描类
|
||||
*
|
||||
* @param packageNames 包名
|
||||
* @return 结果
|
||||
*/
|
||||
public Set<Class<?>> scan(String... packageNames) {
|
||||
return scan(Arrays.asList(packageNames));
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* LRU (least recently used)最近最久未使用缓存<br>
|
||||
* 根据使用时间来判定对象是否被持续缓存<br>
|
||||
* 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。<br>
|
||||
* 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。<br>
|
||||
* 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。<br>
|
||||
* 缺点是当缓存满时,不能被很快的访问。
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author wangyu
|
||||
*/
|
||||
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int maxSize;
|
||||
|
||||
public LRUCache(int maxSize) {
|
||||
this(maxSize, 16, 0.75f, false);
|
||||
}
|
||||
|
||||
public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder) {
|
||||
super(initialCapacity, loadFactor, accessOrder);
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return this.size() > this.maxSize;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
public interface Supportable<T> {
|
||||
|
||||
/**
|
||||
* 判断是否支持实体
|
||||
*
|
||||
* @param entity 实体信息
|
||||
* @return 结果
|
||||
*/
|
||||
boolean supports(T entity);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
# 核心数据框架
|
||||
|
||||
本模块是flyfish framework的最核心能力
|
||||
其提供无感知的多种数据源支持,并完整适配了mongodb和rdbms的查询表现,
|
||||
用户只需要用一套api即可完成数据的查询或修改,无需为了底层实现而大费周章重新开发。
|
||||
|
||||
## 核心架构
|
||||
1. 基于Spring SPI模式,声明式注入查询实现工厂,动态替换查询构建逻辑
|
||||
2. 按需引入,不浪费任何依赖,节省空间。使用maven的按需引入模式,让打包后的结果不必因为重量级的框架而变得冗余
|
||||
3. 指哪打哪,稳定快速。集成最新版本的spring mongo引擎和spring data r2dbc
|
||||
4. 核心框架广泛使用,保证"0bug"。
|
||||
|
||||
## 无限扩展
|
||||
1. 根据实际业务需求,采用mongo、jpa、r2dbc的方式,动态选择实现,而业务和底层存储实现无关,完全解耦
|
||||
2. 健壮的架构,能够支撑99%的业务
|
||||
3. 完善的关联机制,多表关联做到全自动化
|
@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-data-common</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data-domain</artifactId>
|
||||
<version>${revision}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ulisesbocchio</groupId>
|
||||
<artifactId>jasypt-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -1,42 +0,0 @@
|
||||
package com.flyfish.framework.domain.authorized;
|
||||
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
/**
|
||||
* 带鉴权的查询实体,主要以部门隔绝
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthorizedQo<T extends AuthorizedDomain> extends AbstractAuthorizedQo<T> {
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
// 超级管理员拥有查看所有草稿的权限
|
||||
if (user.getType() == UserType.SUPER_ADMIN) {
|
||||
return super.queryBuilder().mutate().and(this.withPublished());
|
||||
}
|
||||
// 查询草稿,只查询自己的
|
||||
if (BooleanUtils.isFalse(published)) {
|
||||
return super.queryBuilder()
|
||||
.mutate()
|
||||
.and(this.withPublished())
|
||||
.and(Queries.where(T::getCreatorId).eq(user.getId()));
|
||||
}
|
||||
// 普通查询,根据权限配置查询
|
||||
return super.queryBuilder()
|
||||
.mutate()
|
||||
.and(this.withPublished())
|
||||
.and(Queries.where(T::getAuthorizeId).in(authorizeIds())
|
||||
.or(Queries.where(T::getCreatorId).eq(user.getId())
|
||||
.and(T::getAuthorizeId).in(((AuthorizedUserDetails) user).getVisibleDeparts()))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.flyfish.framework.domain.authorized.advanced;
|
||||
|
||||
import com.flyfish.framework.domain.authorized.AbstractAuthorizedQo;
|
||||
import com.flyfish.framework.domain.authorized.AuthorizedUserDetails;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
/**
|
||||
* 拥有归属权的授权查询
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @author wangyu
|
||||
* 将会额外查询归属者的内容
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class OwnedAuthorizedQo<T extends OwnedAuthorizedDomain> extends AbstractAuthorizedQo<T> {
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
// 超级管理员拥有查看所有草稿的权限
|
||||
if (user.getType() == UserType.SUPER_ADMIN) {
|
||||
return super.queryBuilder().mutate().and(this.withPublished());
|
||||
}
|
||||
// 查询草稿,只查询自己的
|
||||
if (BooleanUtils.isFalse(published)) {
|
||||
return super.queryBuilder()
|
||||
.mutate()
|
||||
.and(this.withPublished())
|
||||
// 此处原意为使用用户id匹配owners列中的id,修改后框架会自动判定并解包,存在耦合性
|
||||
.and(Queries
|
||||
.where(T::getCreatorId).eq(user.getId())
|
||||
.or(T::getOwners).hasId(user.getId()));
|
||||
}
|
||||
// 普通查询,根据权限配置查询
|
||||
return super.queryBuilder()
|
||||
.mutate()
|
||||
.and(this.withPublished())
|
||||
.and(() -> Queries.where(T::getAuthorizeId).in(authorizeIds())
|
||||
.or(Queries.where(T::getCreatorId).eq(user.getId())
|
||||
.and(T::getAuthorizeId).in(((AuthorizedUserDetails) user).getVisibleDeparts()))
|
||||
.or(Queries.where(T::getOwners).hasId(user.getId()))
|
||||
);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 基本查询qo
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleQo<T extends Domain> extends BaseQo<T> {
|
||||
|
||||
private final QueryDefinition query;
|
||||
|
||||
public static <T extends Domain> Qo<T> of(QueryDefinition query) {
|
||||
return new SimpleQo<>(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
package com.flyfish.framework.domain;
|
||||
/**
|
||||
* 提供应用级别的实体
|
||||
*/
|
@ -1,37 +0,0 @@
|
||||
package com.flyfish.framework.domain.tree;
|
||||
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
import com.flyfish.framework.query.QueryDefinition;
|
||||
import com.flyfish.framework.query.LambdaQueryChain;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 属性菜单的qo
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class TreeQo<T extends TreeDomain<T>> extends NameLikeQo<T> {
|
||||
|
||||
private Integer depth;
|
||||
|
||||
private String parentId;
|
||||
|
||||
private List<String> parentIds;
|
||||
|
||||
private Boolean recursive;
|
||||
|
||||
@Override
|
||||
public QueryDefinition queryBuilder() {
|
||||
LambdaQueryChain<T> chain = super.queryBuilder().mutate().and(T::getDepth).eq(depth);
|
||||
if (BooleanUtils.isTrue(recursive)) {
|
||||
chain.and(T::getParentIds).has(parentId).and(T::getParentIds).in(parentId);
|
||||
} else {
|
||||
chain.and(T::getParentId).eq(parentId).and(T::getParentId).in(parentIds);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.Queries.Combinator;
|
||||
import com.flyfish.framework.query.support.DomainFunction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* lambda查询条件
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface LambdaQueryChain<T extends Domain> extends QueryChain<LambdaQueryChain<T>, DomainFunction<T>> {
|
||||
|
||||
/**
|
||||
* 以且连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> column);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(Supplier<QueryChain<V, ?>> supplier);
|
||||
|
||||
/**
|
||||
* 条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
default <V extends QueryChain<V, ?>> LambdaQueryChain<T> and(List<QueryChain<V, ?>> chains) {
|
||||
return QueryChain.super.and(chains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
/**
|
||||
* 以或连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> column);
|
||||
|
||||
/**
|
||||
* 以或连接条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 以或连接嵌套多个子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(Supplier<QueryChain<V, ?>> supplier);
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.query.Queries.Combinator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 查询链条
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface NamedQueryChain extends QueryChain<NamedQueryChain, String> {
|
||||
|
||||
/**
|
||||
* 以且连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
QueryCondition<NamedQueryChain> and(String column);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain and(Supplier<QueryChain<V, ?>> supplier);
|
||||
|
||||
/**
|
||||
* 条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain and(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
default <V extends QueryChain<V, ?>> NamedQueryChain and(List<QueryChain<V, ?>> chains) {
|
||||
return QueryChain.super.and(chains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain and(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
/**
|
||||
* 以或连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
QueryCondition<NamedQueryChain> or(String column);
|
||||
|
||||
/**
|
||||
* 以或连接条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain or(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain or(Supplier<QueryChain<V, ?>> supplier);
|
||||
|
||||
/**
|
||||
* 以或连接嵌套多个子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
<V extends QueryChain<V, ?>> NamedQueryChain or(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.chain.DefaultLambdaQueryChain;
|
||||
import com.flyfish.framework.query.chain.DefaultNamedQueryChain;
|
||||
import com.flyfish.framework.query.support.DomainFunction;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
/**
|
||||
* 查询工具类
|
||||
* 基于Fluent API风格实现
|
||||
* 底层基于适配器兼容各种数据库,包括关系型数据库和
|
||||
* 自动判定空值,空值默认情况不会进行查询
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public final class Queries {
|
||||
|
||||
/**
|
||||
* 查询转换
|
||||
*
|
||||
* @param query 通用查询
|
||||
* @param entityInformation 实体信息
|
||||
* @param <T> 具体查询泛型
|
||||
* @return 转换结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(Query query, EntityInformation<? extends Domain, String> entityInformation) {
|
||||
return QueryFactories.getFactory(entityInformation)
|
||||
.map(factory -> (T) factory.getConverter().convert(query))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于字符串字段名的查询
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
public static QueryCondition<NamedQueryChain> where(String column) {
|
||||
NamedQueryChain chain = new DefaultNamedQueryChain();
|
||||
return chain.and(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基于lambda字段引用的查询
|
||||
*
|
||||
* @param getter 列引用
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T extends Domain> QueryCondition<LambdaQueryChain<T>> where(DomainFunction<T> getter) {
|
||||
LambdaQueryChain<T> chain = new DefaultLambdaQueryChain<>();
|
||||
return chain.and(getter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以嵌套条件开始
|
||||
*
|
||||
* @param chain 要嵌套的条件
|
||||
* @return 结果
|
||||
*/
|
||||
public static NamedQueryChain within(NamedQueryChain chain) {
|
||||
NamedQueryChain created = new DefaultNamedQueryChain();
|
||||
return created.and(chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以嵌套条件开始
|
||||
*
|
||||
* @param chain 要嵌套的条件
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T extends Domain> LambdaQueryChain<T> within(LambdaQueryChain<T> chain) {
|
||||
LambdaQueryChain<T> created = new DefaultLambdaQueryChain<>();
|
||||
return created.and(chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 传入多个查询列表时的连接方式
|
||||
*/
|
||||
public enum Combinator {
|
||||
|
||||
AND, OR
|
||||
}
|
||||
|
||||
/**
|
||||
* 方向,用于描述查询和部分语法
|
||||
*/
|
||||
public enum Direction {
|
||||
|
||||
LEFT, RIGHT, ALL
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 抽象查询实体
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
public class Query {
|
||||
|
||||
// 查询定义
|
||||
private QueryDefinition definition;
|
||||
// 查询用例
|
||||
private Example<?> example;
|
||||
// 排序
|
||||
private Sort sort;
|
||||
// 分页
|
||||
private Pageable pageable;
|
||||
// 字段列表
|
||||
private List<String> fields;
|
||||
|
||||
/**
|
||||
* 初始化一个查询
|
||||
*
|
||||
* @param definition 查询定义
|
||||
* @return 结果
|
||||
*/
|
||||
public static Query query(QueryDefinition definition) {
|
||||
Query query = new Query();
|
||||
query.setDefinition(definition);
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个空查询
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static Query empty() {
|
||||
return new Query();
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定选择的字段
|
||||
*
|
||||
* @param columns 字段们
|
||||
* @return 当前自身
|
||||
*/
|
||||
public Query select(String... columns) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定选择的字段
|
||||
*
|
||||
* @param getters 字段们
|
||||
* @return 当前自身
|
||||
*/
|
||||
public <T extends Domain> Query select(Function<T, ?>... getters) {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.query.Queries.Combinator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 查询链
|
||||
* 此处为高度抽象,便于上层调用
|
||||
* 下层重写方法实现
|
||||
*
|
||||
* @param <C> 本类型泛型
|
||||
* @param <P> 参数泛型
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryChain<C extends QueryChain<C, P>, P> extends QueryDefinition {
|
||||
|
||||
/**
|
||||
* 以且连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
QueryCondition<C> and(P column);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @param <V> 泛型,支持其他类型的链
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C and(Supplier<QueryChain<V, ?>> supplier);
|
||||
|
||||
/**
|
||||
* 条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C and(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @return 结果
|
||||
*/
|
||||
default <V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains) {
|
||||
return and(chains, Combinator.AND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
/**
|
||||
* 以或连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
QueryCondition<C> or(P column);
|
||||
|
||||
/**
|
||||
* 以或连接条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C or(QueryChain<V, ?> chain);
|
||||
|
||||
/**
|
||||
* 以或连接嵌套多个子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @return 结果
|
||||
*/
|
||||
default <V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains) {
|
||||
return and(chains, Combinator.AND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或连接嵌套多个子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains, Combinator combinator);
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @param <V> 泛型,支持其他类型的链
|
||||
* @return 结果
|
||||
*/
|
||||
<V extends QueryChain<V, ?>> C or(Supplier<QueryChain<V, ?>> supplier);
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询条件
|
||||
*
|
||||
* @param <C> 条件本类型
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryCondition<C extends QueryChain<C, ?>> {
|
||||
|
||||
/**
|
||||
* 相等判定
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C eq(Object value);
|
||||
|
||||
/**
|
||||
* 判定某列的值中存在指定值,特指json array数据类型且子类型中带有id的场景
|
||||
* 用于兼容mongodb查询,mysql查询使用JSON_CONTAINS进行判定。
|
||||
* 当且仅当mongodb会拼接.$id,mysql一律匹配id字段
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C hasId(Object value);
|
||||
|
||||
/**
|
||||
* 对于mongodb,自动处理。对于关系型数据库,代表json array中是否包含对应值,值仅支持基本数据类型
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param value 基本数据类型的值
|
||||
* @return 结果
|
||||
*/
|
||||
C has(Object value);
|
||||
|
||||
/**
|
||||
* 不等判定
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C ne(Object value);
|
||||
|
||||
/**
|
||||
* 值介于两者之间
|
||||
*
|
||||
* @param items 双值列表
|
||||
* @return 结果
|
||||
*/
|
||||
C between(List<?> items);
|
||||
|
||||
/**
|
||||
* 模糊匹配,这里是全模糊
|
||||
*
|
||||
* @param keyword 查询关键字
|
||||
* @return 结果
|
||||
*/
|
||||
C like(String keyword);
|
||||
|
||||
/**
|
||||
* 根据指定的方向进行模糊查询
|
||||
*
|
||||
* @param keyword 关键字
|
||||
* @param direction 方向,可以匹配开头和结尾
|
||||
* @return 结果
|
||||
*/
|
||||
C like(String keyword, Queries.Direction direction);
|
||||
|
||||
/**
|
||||
* 判定为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
C isNull();
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
*
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
C in(Collection<?> values);
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
C in(Object... values);
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
C nin(Object... values);
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
*
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
C nin(Collection<?> values);
|
||||
|
||||
/**
|
||||
* 小于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C lt(Object value);
|
||||
|
||||
/**
|
||||
* 小于等于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C lte(Object value);
|
||||
|
||||
/**
|
||||
* 大于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C gt(Object value);
|
||||
|
||||
/**
|
||||
* 大于等于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C gte(Object value);
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.domain.base.SimpleQo;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
|
||||
/**
|
||||
* 可构建的
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryDefinition {
|
||||
|
||||
/**
|
||||
* 构建结果
|
||||
*
|
||||
* @param adaptor 查询适配器
|
||||
* @param <T> 泛型
|
||||
* @return 构建结果
|
||||
*/
|
||||
<T> T build(CriteriaAdaptor<T> adaptor);
|
||||
|
||||
/**
|
||||
* 包装快捷方法
|
||||
*
|
||||
* @param <D> 实体类型泛型
|
||||
* @return 结果
|
||||
*/
|
||||
default <D extends Domain> Qo<D> wrap() {
|
||||
return new SimpleQo<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改,此修改会直接接着查询条件进行拼接
|
||||
*
|
||||
* @return 修改句柄
|
||||
*/
|
||||
QueryMutation mutate();
|
||||
|
||||
/**
|
||||
* 会直接添加嵌套查询到当前查询末尾
|
||||
*
|
||||
* @return 修改句柄
|
||||
*/
|
||||
QueryMutation within();
|
||||
|
||||
/**
|
||||
* 判断查询条件是否为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean isEmpty();
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.spi.QueryFactory;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 查询工厂持有者
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
class QueryFactories {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final List<QueryFactory> FACTORIES =
|
||||
SpringFactoriesLoader.loadFactories(QueryFactory.class, null);
|
||||
|
||||
/**
|
||||
* 获取目标查询工厂
|
||||
*
|
||||
* @param entityInformation 实体信息
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static Optional<QueryFactory> getFactory(EntityInformation<? extends Domain, String> entityInformation) {
|
||||
return FACTORIES.stream().filter(factory -> factory.supports(entityInformation))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package com.flyfish.framework.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.support.DomainFunction;
|
||||
|
||||
/**
|
||||
* 查询修改逻辑
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryMutation {
|
||||
|
||||
/**
|
||||
* 以lambda的形式修改
|
||||
* 注意,如果之前用过lambda的形式,此处应该校验类型
|
||||
*
|
||||
* @param getter 方法引用
|
||||
* @param <T> 实体泛型
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain> QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> getter);
|
||||
|
||||
/**
|
||||
* 以column的形式修改
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
QueryCondition<NamedQueryChain> and(String column);
|
||||
|
||||
/**
|
||||
* 直接以and拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain> LambdaQueryChain<T> and(LambdaQueryChain<T> chain);
|
||||
|
||||
/**
|
||||
* 直接以and拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
NamedQueryChain and(NamedQueryChain chain);
|
||||
|
||||
/**
|
||||
* 以lambda的形式修改
|
||||
* 注意,如果之前用过lambda的形式,此处应该校验类型
|
||||
*
|
||||
* @param getter 方法引用
|
||||
* @param <T> 实体泛型
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain> QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> getter);
|
||||
|
||||
/**
|
||||
* 以column的形式修改
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
QueryCondition<NamedQueryChain> or(String column);
|
||||
|
||||
/**
|
||||
* 直接以or拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain> LambdaQueryChain<T> or(LambdaQueryChain<T> chain);
|
||||
|
||||
/**
|
||||
* 直接以or拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
NamedQueryChain or(NamedQueryChain chain);
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
# 查询构建Fluent Api适配器
|
||||
同时适配关系型数据库mysql和非关系型数据库mongodb
|
||||
|
||||
未来将会支持更多,采用覆盖的方式进行对象组合
|
@ -1,42 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.LambdaQueryChain;
|
||||
import com.flyfish.framework.query.QueryCondition;
|
||||
import com.flyfish.framework.query.support.DomainFunction;
|
||||
|
||||
/**
|
||||
* 默认按lambda的查询链实现
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class DefaultLambdaQueryChain<T extends Domain> extends DefaultQueryChain<LambdaQueryChain<T>, DomainFunction<T>> implements LambdaQueryChain<T> {
|
||||
|
||||
private QueryCondition<LambdaQueryChain<T>> createCondition(DomainFunction<T> column) {
|
||||
return createCondition(column.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 以且连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> column) {
|
||||
and();
|
||||
return createCondition(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> column) {
|
||||
or();
|
||||
return createCondition(column);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.query.NamedQueryChain;
|
||||
import com.flyfish.framework.query.QueryCondition;
|
||||
|
||||
/**
|
||||
* 默认按名称的查询链实现
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class DefaultNamedQueryChain extends DefaultQueryChain<NamedQueryChain, String> implements NamedQueryChain {
|
||||
|
||||
/**
|
||||
* 以且连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<NamedQueryChain> and(String column) {
|
||||
and();
|
||||
return createCondition(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或连接下一个字段
|
||||
*
|
||||
* @param column 列
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<NamedQueryChain> or(String column) {
|
||||
or();
|
||||
return createCondition(column);
|
||||
}
|
||||
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.query.QueryChain;
|
||||
import com.flyfish.framework.query.QueryCondition;
|
||||
import com.flyfish.framework.query.QueryMutation;
|
||||
import com.flyfish.framework.query.holder.QueryChainHolder;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.util.CastUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 默认的查询定义,交给holder提供构建
|
||||
* 在执行时,我们并不知道adaptor是谁,只有最后构建阶段才能明确
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Getter
|
||||
abstract class DefaultQueryChain<C extends QueryChain<C, P>, P> implements QueryChain<C, P> {
|
||||
|
||||
protected final DefaultQueryChainHolder<?> holder = new DefaultQueryChainHolder<>();
|
||||
|
||||
/**
|
||||
* 以且的关系进行拼接
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
protected QueryChainHolder<?> and() {
|
||||
return holder.link(adaptor -> adaptor::and);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或的关系进行拼接
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
protected QueryChainHolder<?> or() {
|
||||
return holder.link(adaptor -> adaptor::or);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建条件,使用默认实现的条件
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
protected QueryCondition<C> createCondition(String column) {
|
||||
return new DefaultQueryCondition<>(self(), holder, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回自身真实类型
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private C self() {
|
||||
return CastUtils.cast(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建结果
|
||||
*
|
||||
* @param adaptor 查询适配器
|
||||
* @return 构建结果
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T build(CriteriaAdaptor<T> adaptor) {
|
||||
QueryChainHolder<T> casted = (QueryChainHolder<T>) holder;
|
||||
return casted.get(adaptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改,此修改会直接接着查询条件进行拼接
|
||||
*
|
||||
* @return 修改句柄
|
||||
*/
|
||||
@Override
|
||||
public QueryMutation mutate() {
|
||||
return new DefaultQueryMutation(this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会直接添加嵌套查询到当前查询末尾
|
||||
*
|
||||
* @return 修改句柄
|
||||
*/
|
||||
@Override
|
||||
public QueryMutation within() {
|
||||
return new DefaultQueryMutation(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断查询条件是否为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return holder.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C and(Supplier<QueryChain<V, ?>> supplier) {
|
||||
and().with(adaptor -> supplier.get().build(adaptor));
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C and(QueryChain<V, ?> chain) {
|
||||
and().with(chain::build);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个嵌套子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
|
||||
return and(combine(chains, combinator));
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或连接条件列表
|
||||
*
|
||||
* @param chain 多个条件们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C or(QueryChain<V, ?> chain) {
|
||||
or().with(chain::build);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 以或连接嵌套多个子条件列表
|
||||
*
|
||||
* @param chains 多条链
|
||||
* @param combinator 各个链条之间的连接方式
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
|
||||
return or(combine(chains, combinator));
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接拼接提供者,此处懒加载,最终build才会执行
|
||||
*
|
||||
* @param supplier 提供者
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <V extends QueryChain<V, ?>> C or(Supplier<QueryChain<V, ?>> supplier) {
|
||||
or().with(adaptor -> supplier.get().build(adaptor));
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结合多个查询链,并根据组合方式进行处理
|
||||
*
|
||||
* @param chains 查询链集合
|
||||
* @param combinator 组合方式
|
||||
* @return 结果
|
||||
*/
|
||||
private <V extends QueryChain<V, ?>> C combine(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
|
||||
return chains.stream().reduce((left, right) -> combinator == Queries.Combinator.OR ? left.or(right) : left.and(right))
|
||||
.map(this::and)
|
||||
.orElse(self());
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.query.holder.QueryChainHolder;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 默认的查询保持器
|
||||
*
|
||||
* @param <C> 泛型,交给适配器处理
|
||||
*/
|
||||
class DefaultQueryChainHolder<C> implements QueryChainHolder<C> {
|
||||
|
||||
private Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator;
|
||||
|
||||
private final List<BiFunction<C, CriteriaAdaptor<C>, C>> builders = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 等待下一步消费
|
||||
* 如果不调用with,该状态将一直保持,可被其他link调用替换
|
||||
* 在调用with后,自动消费,并完成拼接
|
||||
*
|
||||
* @param operator 操作方法
|
||||
* @return 本身
|
||||
*/
|
||||
@Override
|
||||
public QueryChainHolder<C> link(Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator) {
|
||||
this.operator = operator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接条件
|
||||
*
|
||||
* @param criteria 条件
|
||||
*/
|
||||
@Override
|
||||
public void with(Function<CriteriaAdaptor<C>, C> criteria) {
|
||||
Assert.notNull(operator, "连接操作不可为空");
|
||||
Function<CriteriaAdaptor<C>, BinaryOperator<C>> finalOperator = operator;
|
||||
this.operator = null;
|
||||
BiFunction<C, CriteriaAdaptor<C>, C> builder = (previous, adaptor) -> {
|
||||
// 连接逻辑
|
||||
BinaryOperator<C> linker = finalOperator.apply(adaptor);
|
||||
// 具体条件
|
||||
C next = criteria.apply(adaptor);
|
||||
// 之前的条件
|
||||
return linker.apply(previous, next);
|
||||
};
|
||||
builders.add(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发动作,得到最终的
|
||||
*
|
||||
* @return 本身
|
||||
*/
|
||||
@Override
|
||||
public C get(CriteriaAdaptor<C> adaptor) {
|
||||
if (CollectionUtils.isNotEmpty(builders)) {
|
||||
return builders.stream()
|
||||
.reduce(adaptor.empty(), (result, builder) -> builder.apply(result, adaptor), (a, b) -> a);
|
||||
}
|
||||
return adaptor.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断条件是否为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return builders.isEmpty();
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.query.QueryChain;
|
||||
import com.flyfish.framework.query.QueryCondition;
|
||||
import com.flyfish.framework.query.holder.QueryChainHolder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 条件操作实现
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class DefaultQueryCondition<C extends QueryChain<C, ?>> implements QueryCondition<C> {
|
||||
|
||||
private final C chain;
|
||||
private final QueryChainHolder<?> holder;
|
||||
private final String column;
|
||||
|
||||
/**
|
||||
* 相等判定
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C eq(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.eq(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定某列的值中存在指定值,特指json array数据类型且子类型中带有id的场景
|
||||
* 用于兼容mongodb查询,mysql查询使用JSON_CONTAINS进行判定。
|
||||
* 当且仅当mongodb会拼接.$id,mysql一律匹配id字段
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C hasId(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.hasId(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于mongodb,自动处理。对于关系型数据库,代表json array中是否包含对应值,值仅支持基本数据类型
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param value 基本数据类型的值
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C has(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.has(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不等判定
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C ne(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.ne(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值介于两者之间
|
||||
*
|
||||
* @param items 双值列表
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C between(List<?> items) {
|
||||
if (CollectionUtils.size(items) == 2) {
|
||||
holder.with(adaptor -> adaptor.between(column, items));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊匹配,这里是全模糊
|
||||
*
|
||||
* @param keyword 查询关键字
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C like(String keyword) {
|
||||
if (StringUtils.isNotBlank(keyword)) {
|
||||
holder.with(adaptor -> adaptor.like(column, keyword, Queries.Direction.ALL));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的方向进行模糊查询
|
||||
*
|
||||
* @param keyword 关键字
|
||||
* @param direction 方向,可以匹配开头和结尾
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C like(String keyword, Queries.Direction direction) {
|
||||
if (StringUtils.isNotBlank(keyword)) {
|
||||
holder.with(adaptor -> adaptor.like(column, keyword, direction));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C isNull() {
|
||||
holder.with(adaptor -> adaptor.isNull(column));
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
*
|
||||
* @param list 集合
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C in(Collection<?> list) {
|
||||
if (CollectionUtils.isNotEmpty(list)) {
|
||||
holder.with(adaptor -> adaptor.in(column, list));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C in(Object... values) {
|
||||
if (ArrayUtils.isNotEmpty(values)) {
|
||||
holder.with(adaptor -> adaptor.in(column, values));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C nin(Object... values) {
|
||||
if (ArrayUtils.isNotEmpty(values)) {
|
||||
holder.with(adaptor -> adaptor.nin(column, values));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
*
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C nin(Collection<?> values) {
|
||||
if (CollectionUtils.isNotEmpty(values)) {
|
||||
holder.with(adaptor -> adaptor.nin(column, values));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C lt(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.lt(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小于等于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C lte(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.lte(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C gt(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.gt(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大于等于指定的值
|
||||
*
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public C gte(Object value) {
|
||||
if (null != value) {
|
||||
holder.with(adaptor -> adaptor.gte(column, value));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
package com.flyfish.framework.query.chain;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.*;
|
||||
import com.flyfish.framework.query.support.DomainFunction;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 默认的查询修改逻辑
|
||||
*
|
||||
* @author wangyu
|
||||
* <p>
|
||||
* 不修改原条件,而是在此基础上新增
|
||||
* 理论上查询不允许直接修改,而是通过该类做一个桥接
|
||||
* 也可以修改链的形式
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultQueryMutation implements QueryMutation {
|
||||
|
||||
private final QueryChain<?, ?> previous;
|
||||
|
||||
private final boolean within;
|
||||
|
||||
/**
|
||||
* 根据实际的参数合并查询链
|
||||
*
|
||||
* @param newChain 新链
|
||||
* @param <P> 参数类型
|
||||
* @param <C> 查询链类型
|
||||
* @return 结果
|
||||
*/
|
||||
private <P, C extends QueryChain<C, P>> C merge(C newChain) {
|
||||
if (within) {
|
||||
// 内联模式,旧条件不修改
|
||||
newChain.and(Collections.singletonList(previous));
|
||||
} else {
|
||||
// 修改模式,直接修改
|
||||
previous.and(newChain);
|
||||
}
|
||||
return newChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以lambda的形式修改
|
||||
* 注意,如果之前用过lambda的形式,此处应该校验类型
|
||||
*
|
||||
* @param getter 方法引用
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> getter) {
|
||||
return merge(new DefaultLambdaQueryChain<T>()).and(getter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以column的形式修改
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<NamedQueryChain> and(String column) {
|
||||
return merge(new DefaultNamedQueryChain()).and(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接以and拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> LambdaQueryChain<T> and(LambdaQueryChain<T> chain) {
|
||||
return merge(chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接以and拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public NamedQueryChain and(NamedQueryChain chain) {
|
||||
return merge(chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以lambda的形式修改
|
||||
* 注意,如果之前用过lambda的形式,此处应该校验类型
|
||||
*
|
||||
* @param getter 方法引用
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> getter) {
|
||||
return merge(new DefaultLambdaQueryChain<T>()).or(getter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以column的形式修改
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public QueryCondition<NamedQueryChain> or(String column) {
|
||||
return merge(new DefaultNamedQueryChain()).or(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接以or拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> LambdaQueryChain<T> or(LambdaQueryChain<T> chain) {
|
||||
return merge(chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接以or拼接其他的所有条件
|
||||
*
|
||||
* @param chain 传入的查询
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public NamedQueryChain or(NamedQueryChain chain) {
|
||||
return merge(chain);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.flyfish.framework.query.holder;
|
||||
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 查询链保持器,将所有拼接工作后置,可以做检查和处理
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryChainHolder<C> {
|
||||
|
||||
/**
|
||||
* 等待下一步消费
|
||||
* 如果不调用with,该状态将一直保持,可被其他link调用替换
|
||||
* 在调用with后,自动消费,并完成拼接
|
||||
*
|
||||
* @param operator 操作方法
|
||||
* @return 本身
|
||||
*/
|
||||
QueryChainHolder<C> link(Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator);
|
||||
|
||||
/**
|
||||
* 拼接条件
|
||||
*
|
||||
* @param criteria 条件
|
||||
*/
|
||||
void with(Function<CriteriaAdaptor<C>, C> criteria);
|
||||
|
||||
/**
|
||||
* 触发动作,得到最终的
|
||||
*
|
||||
* @return 本身
|
||||
*/
|
||||
C get(CriteriaAdaptor<C> adaptor);
|
||||
|
||||
/**
|
||||
* 判断条件是否为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean isEmpty();
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package com.flyfish.framework.query.spi;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import com.flyfish.framework.query.spi.converter.QueryConverter;
|
||||
import com.flyfish.framework.utils.Supportable;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
/**
|
||||
* 查询工厂,提供统一的实例
|
||||
*
|
||||
* @param <C> 查询条件对象泛型
|
||||
* @param <Q> 查询对象泛型
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryFactory<C, Q> extends Supportable<EntityInformation<? extends Domain, String>> {
|
||||
|
||||
/**
|
||||
* 获取查询转换器
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
QueryConverter<Q> getConverter();
|
||||
|
||||
/**
|
||||
* 获取查询适配器
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
CriteriaAdaptor<C> getAdaptor();
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
package com.flyfish.framework.query.spi.adaptor;
|
||||
|
||||
import com.flyfish.framework.query.Queries;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询适配器
|
||||
* 直接根据条件操作得到查询条件片段
|
||||
* 适配不同的数据库抽取通用操作
|
||||
*
|
||||
* @author wangyu
|
||||
* 由工厂进行实例化,并输出
|
||||
*/
|
||||
public interface CriteriaAdaptor<C> {
|
||||
|
||||
/**
|
||||
* 一个空的查询,用于起手
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
C empty();
|
||||
|
||||
/**
|
||||
* 且的关系进行拼接
|
||||
*
|
||||
* @param first 起始条件
|
||||
* @param other 其他条件
|
||||
* @return 结果
|
||||
*/
|
||||
C and(C first, C other);
|
||||
|
||||
/**
|
||||
* 或的关系进行拼接
|
||||
*
|
||||
* @param first 起始条件
|
||||
* @param other 其他条件
|
||||
* @return 结果
|
||||
*/
|
||||
C or(C first, C other);
|
||||
|
||||
/**
|
||||
* 相等判定
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C eq(String column, Object value);
|
||||
|
||||
/**
|
||||
* 判定某列的值中存在指定值,特指json array数据类型且子类型中带有id的场景
|
||||
* 用于兼容mongodb查询,mysql查询使用JSON_CONTAINS进行判定。
|
||||
* 当且仅当mongodb会拼接.$id,mysql一律匹配id字段
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C hasId(String column, Object value);
|
||||
|
||||
/**
|
||||
* 对于mongodb,自动处理。对于关系型数据库,代表json array中是否包含对应值,值仅支持基本数据类型
|
||||
* <p>
|
||||
* 等价于 .eq(value)
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 基本数据类型的值
|
||||
* @return 结果
|
||||
*/
|
||||
C has(String column, Object value);
|
||||
|
||||
/**
|
||||
* 不等判定
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
C ne(String column, Object value);
|
||||
|
||||
/**
|
||||
* 值介于两者之间
|
||||
*
|
||||
* @param column 列名
|
||||
* @param items 双值列表
|
||||
* @return 结果
|
||||
*/
|
||||
C between(String column, List<?> items);
|
||||
|
||||
/**
|
||||
* 根据指定的方向进行模糊查询
|
||||
*
|
||||
* @param column 列名
|
||||
* @param keyword 关键字
|
||||
* @param direction 方向,可以匹配开头和结尾
|
||||
* @return 结果
|
||||
*/
|
||||
C like(String column, String keyword, Queries.Direction direction);
|
||||
|
||||
/**
|
||||
* 判定为空
|
||||
*
|
||||
* @param column 列名
|
||||
* @return 结果
|
||||
*/
|
||||
C isNull(String column);
|
||||
|
||||
/**
|
||||
* 小于指定的值
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C lt(String column, Object value);
|
||||
|
||||
/**
|
||||
* 小于等于指定的值
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C lte(String column, Object value);
|
||||
|
||||
/**
|
||||
* 大于指定的值
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C gt(String column, Object value);
|
||||
|
||||
/**
|
||||
* 大于等于指定的值
|
||||
*
|
||||
* @param column 列名
|
||||
* @param value 类型
|
||||
* @return 结果
|
||||
*/
|
||||
C gte(String column, Object value);
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
*
|
||||
* @param column 列名
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
C in(String column, Collection<?> values);
|
||||
|
||||
/**
|
||||
* 包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param column 列名
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
C in(String column, Object... values);
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
* 注意,需要根据字段类型推断。
|
||||
* 如果是json数组,需要进行双向匹配
|
||||
*
|
||||
* @param column 列名
|
||||
* @param values 值们
|
||||
* @return 结果
|
||||
*/
|
||||
C nin(String column, Object... values);
|
||||
|
||||
/**
|
||||
* 不包含在内
|
||||
*
|
||||
* @param column 列名
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
C nin(String column, Collection<?> values);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.flyfish.framework.query.spi.converter;
|
||||
|
||||
import com.flyfish.framework.query.Query;
|
||||
|
||||
/**
|
||||
* 原生查询转换器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface QueryConverter<T> {
|
||||
|
||||
/**
|
||||
* 转换查询
|
||||
*
|
||||
* @param query 封装的查询
|
||||
* @return 本地查询
|
||||
*/
|
||||
T convert(Query query);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.flyfish.framework.query.support;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
|
||||
/**
|
||||
* 支持序列化的实体方法
|
||||
*
|
||||
* @param <T> 泛型,父类为实体
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DomainFunction<T extends Domain> extends SFunction<T, Object> {
|
||||
|
||||
/**
|
||||
* 快速获取名称
|
||||
*
|
||||
* @return 序列化后的名称
|
||||
*/
|
||||
default String getName() {
|
||||
return EntityNameUtils.getName(this);
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
package com.flyfish.framework.query.support;
|
||||
|
||||
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.utils.LRUCache;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class EntityNameUtils {
|
||||
|
||||
// SerializedLambda 反序列化缓存
|
||||
private static final Map<String, WeakReference<SerializedLambda>> FUNC_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
// 列别名缓存
|
||||
private static final Map<Class<?>, Map<String, String>> COLUMN_CACHE = new LRUCache<>(5);
|
||||
|
||||
private static final String MONGO_FIELD = "org.springframework.data.mongodb.core.mapping.Field";
|
||||
|
||||
private static final String RELATIONAL_COLUMN = "org.springframework.data.relational.core.mapping.Column";
|
||||
|
||||
/**
|
||||
* 方法转为属性
|
||||
*
|
||||
* @param name 方法名称
|
||||
* @return 最终属性
|
||||
*/
|
||||
public static String methodToProperty(String name) {
|
||||
if (name.startsWith("is")) {
|
||||
name = name.substring(2);
|
||||
} else {
|
||||
if (!name.startsWith("get") && !name.startsWith("set")) {
|
||||
throw new InvalidParameterException("不是正确的getter或setter");
|
||||
}
|
||||
name = name.substring(3);
|
||||
}
|
||||
if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
|
||||
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将驼峰命名转化成下划线
|
||||
*
|
||||
* @param param 参数
|
||||
* @return 结果
|
||||
*/
|
||||
public static String camelToUnderline(String param) {
|
||||
if (param.length() < 3) {
|
||||
return param.toLowerCase();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(param);
|
||||
int temp = 0;//定位
|
||||
//从第三个字符开始 避免命名不规范
|
||||
for (int i = 2; i < param.length(); i++) {
|
||||
if (Character.isUpperCase(param.charAt(i))) {
|
||||
sb.insert(i + temp, "_");
|
||||
temp += 1;
|
||||
}
|
||||
}
|
||||
return sb.toString().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析列并使用处理函数处理
|
||||
*
|
||||
* @param func 方法引用
|
||||
* @param <T> 实体泛型
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static <T> String getName(SFunction<T, ?> func) {
|
||||
SerializedLambda lambda = resolve(func);
|
||||
String property = methodToProperty(lambda.getImplMethodName());
|
||||
Class<?> beanClass = resolveEntityClass(lambda);
|
||||
return tryCache(beanClass).getOrDefault(property, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析获得实体类
|
||||
*
|
||||
* @param lambda 序列化的lambda
|
||||
* @return 最终获取的类
|
||||
*/
|
||||
private static Class<?> resolveEntityClass(SerializedLambda lambda) {
|
||||
Class<?> type = lambda.getInstantiatedType();
|
||||
tryCache(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试缓存
|
||||
*
|
||||
* @param entityClass bean的类型
|
||||
*/
|
||||
private static Map<String, String> tryCache(Class<?> entityClass) {
|
||||
return COLUMN_CACHE.computeIfAbsent(entityClass, EntityNameUtils::buildFieldsCache);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建字段缓存
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 构建后的缓存
|
||||
*/
|
||||
private static Map<String, String> buildFieldsCache(Class<?> type) {
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
ReflectionUtils.doWithFields(type, field -> fields.put(field.getName(), resolveFinalName(field)));
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析字段注解或直接取用下划线逻辑
|
||||
*
|
||||
* @return 解析结果
|
||||
*/
|
||||
private static String resolveFinalName(Field field) {
|
||||
MergedAnnotations annotations = MergedAnnotations.from(field);
|
||||
if (annotations.isPresent(Property.class)) {
|
||||
String key = annotations.get(Property.class).getString("key");
|
||||
if (StringUtils.hasText(key)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
if (annotations.isPresent(MONGO_FIELD)) {
|
||||
String name = annotations.get(MONGO_FIELD).getString("name");
|
||||
if (StringUtils.hasText(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
if (annotations.isPresent(RELATIONAL_COLUMN)) {
|
||||
String name = annotations.get(RELATIONAL_COLUMN).getString("value");
|
||||
if (StringUtils.hasText(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析方法引用为序列化lambda实例
|
||||
* 该方式会使用缓存
|
||||
*
|
||||
* @param func 方法引用
|
||||
* @param <T> 泛型
|
||||
* @return 解析结果
|
||||
*/
|
||||
private static <T> SerializedLambda resolve(SFunction<T, ?> func) {
|
||||
Class<?> clazz = func.getClass();
|
||||
String name = clazz.getName();
|
||||
return Optional.ofNullable(FUNC_CACHE.get(name))
|
||||
.map(WeakReference::get)
|
||||
.orElseGet(() -> {
|
||||
SerializedLambda lambda = SerializedLambda.resolve(func);
|
||||
FUNC_CACHE.put(name, new WeakReference<>(lambda));
|
||||
return lambda;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.flyfish.framework.query.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 支持序列化的function
|
||||
*
|
||||
* @param <T> 泛型入参
|
||||
* @param <R> 泛型返回值
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SFunction<T, R> extends Function<T, R>, Serializable {
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2021, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.flyfish.framework.query.support;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* 这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的,
|
||||
* 字段信息完全一样
|
||||
* <p>负责将一个支持序列的 Function 序列化为 SerializedLambda</p>
|
||||
*
|
||||
* @author mbp
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
class SerializedLambda implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8025925345765570181L;
|
||||
|
||||
private Class<?> capturingClass;
|
||||
private String functionalInterfaceClass;
|
||||
private String functionalInterfaceMethodName;
|
||||
private String functionalInterfaceMethodSignature;
|
||||
private String implClass;
|
||||
/**
|
||||
* -- GETTER --
|
||||
* 获取实现者的方法名称
|
||||
*
|
||||
* @return 方法名称
|
||||
*/
|
||||
@Getter
|
||||
private String implMethodName;
|
||||
private String implMethodSignature;
|
||||
private int implMethodKind;
|
||||
private String instantiatedMethodType;
|
||||
private Object[] capturedArgs;
|
||||
|
||||
/**
|
||||
* 通过反序列化转换 lambda 表达式,该方法只能序列化 lambda 表达式,不能序列化接口实现或者正常非 lambda 写法的对象
|
||||
*
|
||||
* @param lambda lambda对象
|
||||
* @return 返回解析后的 SerializedLambda
|
||||
*/
|
||||
public static SerializedLambda resolve(SFunction<?, ?> lambda) {
|
||||
Assert.isTrue(lambda.getClass().isSynthetic(), "该方法仅能传入 lambda 表达式产生的合成类");
|
||||
byte[] stream = SerializationUtils.serialize(lambda);
|
||||
Assert.notNull(stream, "序列化类失败!");
|
||||
try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(stream)) {
|
||||
@Override
|
||||
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = forName(objectStreamClass.getName());
|
||||
} catch (Exception ex) {
|
||||
clazz = super.resolveClass(objectStreamClass);
|
||||
}
|
||||
return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
|
||||
}
|
||||
}) {
|
||||
return (SerializedLambda) objIn.readObject();
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
throw new NullPointerException("不应该发生这件事");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以类名实例化类,会隐藏报错,需要在确定有该类的情况下调用
|
||||
*
|
||||
* @param name 类型
|
||||
* @return 实例
|
||||
*/
|
||||
private static Class<?> forName(String name) {
|
||||
try {
|
||||
return ClassUtils.forName(name, null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口 class
|
||||
*
|
||||
* @return 返回 class 名称
|
||||
*/
|
||||
public String getFunctionalInterfaceClassName() {
|
||||
return normalizedName(functionalInterfaceClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实现的 class
|
||||
*
|
||||
* @return 实现类
|
||||
*/
|
||||
public Class<?> getImplClass() {
|
||||
return forName(getImplClassName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 class 的名称
|
||||
*
|
||||
* @return 类名
|
||||
*/
|
||||
public String getImplClassName() {
|
||||
return normalizedName(implClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正常化类名称,将类名称中的 / 替换为 .
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 正常的类名
|
||||
*/
|
||||
private String normalizedName(String name) {
|
||||
return name.replace('/', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获取实例化方法的类型
|
||||
*/
|
||||
public Class<?> getInstantiatedType() {
|
||||
String instantiatedTypeName = normalizedName(instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(';')));
|
||||
return forName(instantiatedTypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 字符串形式
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String interfaceName = getFunctionalInterfaceClassName();
|
||||
String implName = getImplClassName();
|
||||
return String.format("%s -> %s::%s",
|
||||
interfaceName.substring(interfaceName.lastIndexOf('.') + 1),
|
||||
implName.substring(implName.lastIndexOf('.') + 1),
|
||||
implMethodName);
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.repository.base.DomainRepository;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
/**
|
||||
* 默认的持久层dao
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface DefaultReactiveRepository<T> extends ReactiveCrudRepository<T, String>, ReactiveQueryModelExecutor<T>, DomainRepository<T> {
|
||||
|
||||
/**
|
||||
* 插入给定的实体. 假设该实体是新的,以便应用插入优化。
|
||||
* 使用返回的实例进行后续操作,因为保存操作可能会完全改变实体实例。
|
||||
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API。
|
||||
*
|
||||
* @param entity 要插入的实体,不能为空
|
||||
* @return 保存后的实体
|
||||
*/
|
||||
@NonNull
|
||||
<S extends T> Mono<S> insert(@NonNull S entity);
|
||||
|
||||
/**
|
||||
* 插入给定的实体列表. 假设这些实体是新的,以便应用插入优化。
|
||||
* 使用返回的实例进行后续操作,因为保存操作可能会完全改变实体实例。
|
||||
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API。
|
||||
*
|
||||
* @param entities 实体列表,不能为空
|
||||
* @return 保存后的实体列表
|
||||
*/
|
||||
<S extends T> Flux<S> insert(Iterable<S> entities);
|
||||
|
||||
/**
|
||||
* 插入给定的实体发布者发布的实体. 假设这些实体是新的,以便应用插入优化。
|
||||
* 使用返回的实例进行后续操作,因为保存操作可能会完全改变实体实例。
|
||||
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API。
|
||||
*
|
||||
* @param entities 发布的实体列表,不能为空
|
||||
* @return 保存后的实体列表
|
||||
*/
|
||||
<S extends T> Flux<S> insert(Publisher<S> entities);
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.repository.base.DomainRepository;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认的仓库
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface DefaultRepository<T extends Domain> extends CrudRepository<T, String>, PagingAndSortingRepository<T, String>,
|
||||
QueryModelExecutor<T>,
|
||||
DomainRepository<T> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.CrudRepository#saveAll(java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
<S extends T> List<S> saveAll(Iterable<S> entities);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.CrudRepository#findAll()
|
||||
*/
|
||||
@Override
|
||||
List<T> findAll();
|
||||
|
||||
/**
|
||||
* 插入新对象
|
||||
*
|
||||
* @param entity 实体
|
||||
* @param <S> 泛型
|
||||
* @return 插入的实体,带id
|
||||
*/
|
||||
<S extends T> S insert(S entity);
|
||||
|
||||
/**
|
||||
* 批量插入对象
|
||||
*
|
||||
* @param entities 对象列表
|
||||
* @param <S> 泛型
|
||||
* @return 结果列表
|
||||
*/
|
||||
<S extends T> List<S> insert(Iterable<S> entities);
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.query.Query;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 高层封装的模型操作
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface ReactiveEntityOperations {
|
||||
|
||||
/**
|
||||
* 查询,通过查询条件和类进行精确查询
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @param <T> 实体类的实例化结果
|
||||
* @param <E> 实体信息具体实现类
|
||||
* @return 查询结果
|
||||
*/
|
||||
<T extends Domain, E extends EntityInformation<T, String>> Mono<T> find(Query query, E entityInformation);
|
||||
|
||||
/**
|
||||
* 查询全部符合条件的实体
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @param <T> 实体类的实例化结果
|
||||
* @param <E> 实体信息具体实现类
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain, E extends EntityInformation<T, String>> Flux<T> findAll(Query query, E entityInformation);
|
||||
|
||||
/**
|
||||
* 保存实体
|
||||
*
|
||||
* @param bean 实体
|
||||
* @param <T> 实体类的实例化结果
|
||||
* @return 结果
|
||||
*/
|
||||
<T extends Domain> Mono<T> save(T bean);
|
||||
|
||||
/**
|
||||
* 转换为原生查询
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @param <R> 泛型,原生查询类型
|
||||
* @param <T> 泛型,实体类型
|
||||
* @return 结果
|
||||
*/
|
||||
default <R, T extends Domain> R toNative(Query query, EntityInformation<T, String> entityInformation) {
|
||||
return Queries.convert(query, entityInformation);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>flyfish-data-domain</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data-relational</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,100 +0,0 @@
|
||||
package com.flyfish.framework.annotations.query;
|
||||
|
||||
import com.flyfish.framework.enums.NamedEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 查询字段注解,声明映射,查询逻辑
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface QueryField {
|
||||
|
||||
/**
|
||||
* @return 绑定的持久层字段
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* @return 绑定的持久层字段
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* @return 表达式类型
|
||||
*/
|
||||
Type type() default Type.EQ;
|
||||
|
||||
/**
|
||||
* 拼接在某个字段前
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String before() default "";
|
||||
|
||||
/**
|
||||
* @return 和前面条件表达式的连接方式
|
||||
*/
|
||||
Logistic[] start() default Logistic.AND;
|
||||
|
||||
/**
|
||||
* @return 和后面条件表达式的连接方式
|
||||
*/
|
||||
Logistic[] end() default Logistic.NULL;
|
||||
|
||||
/**
|
||||
* @return 当前类内排序值
|
||||
*/
|
||||
int order() default 0;
|
||||
|
||||
/**
|
||||
* 查询类型
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
enum Type implements NamedEnum {
|
||||
|
||||
EQ("等于字段值"),
|
||||
NE("不等于字段值"),
|
||||
GT("大于字段值"),
|
||||
GTE("大于等于字段值"),
|
||||
LT("小于字段值"),
|
||||
LTE("小于等于字段值"),
|
||||
LIKE("模糊匹配字段值"),
|
||||
LIKE_LEFT("匹配左半部分字段值"),
|
||||
LIKE_RIGHT("匹配有半部分字段值"),
|
||||
IN("在字段值列表内"),
|
||||
NIN("不再字段值列表内"),
|
||||
NOT_NULL("不为空"),
|
||||
IS_NULL("为空"),
|
||||
BETWEEN("介于列表下标0和1的值之间"),
|
||||
DATE_GTE("日期大于字段值"),
|
||||
DATE_LTE("日期小于字段值");
|
||||
|
||||
private final String name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询逻辑
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
enum Logistic implements NamedEnum {
|
||||
|
||||
NULL("无"),
|
||||
AND("与"),
|
||||
OR("或"),
|
||||
BRACKET_START("左括号"),
|
||||
BRACKET_END("右括号");
|
||||
|
||||
private final String name;
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `t_user`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`type` VARCHAR(24) NULL COMMENT '用户类型',
|
||||
`status` VARCHAR(24) NULL COMMENT '用户状态',
|
||||
`phone` VARCHAR(16) NOT NULL COMMENT '电话号码',
|
||||
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(128) NULL COMMENT '密码',
|
||||
`enable` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用',
|
||||
`app` BIT(1) NOT NULL DEFAULT b'0' COMMENT '能否登录移动端',
|
||||
`valid_date` DATE NULL COMMENT '有效期至',
|
||||
`open_id` VARCHAR(64) NULL COMMENT '微信openid',
|
||||
`authority` VARCHAR(50) NULL COMMENT '当前用户鉴权标记',
|
||||
`error_count` INT NOT NULL DEFAULT 0 COMMENT '错误次数',
|
||||
`last_time` DATETIME NULL COMMENT '上次登录时间',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY uniq_name (`username`)
|
||||
) COMMENT '用户表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `t_department`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`full_name` VARCHAR(300) NOT NULL COMMENT '完整名称',
|
||||
`primary` BIT(1) NOT NULL DEFAULT b'0' COMMENT '默认选中',
|
||||
`manager` VARCHAR(36) NULL COMMENT '负责人',
|
||||
`phone` VARCHAR(15) NULL COMMENT '联系电话',
|
||||
`address` VARCHAR(150) NULL COMMENT '联系地址',
|
||||
`leaf` BIT(1) NULL COMMENT '是否是叶子节点',
|
||||
`pos` INT NOT NULL DEFAULT b'0' COMMENT '排序字段',
|
||||
`parent_id` VARCHAR(36) NULL DEFAULT NULL COMMENT '上级id',
|
||||
`parent_ids` JSON NULL COMMENT '冗余的上级id集合',
|
||||
`depth` INT NULL COMMENT '深度',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT '部门表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `rel_user_depart`
|
||||
(
|
||||
`user_id` VARCHAR(36) NOT NULL COMMENT '用户id',
|
||||
`depart_id` VARCHAR(36) NOT NULL COMMENT '部门id',
|
||||
PRIMARY KEY (`user_id`, `depart_id`)
|
||||
) COMMENT '用户部门关系表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `t_role`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`description` VARCHAR(500) NULL COMMENT '描述',
|
||||
`admin` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否是管理员',
|
||||
`system` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否是系统内置',
|
||||
`type` VARCHAR(24) NOT NULL COMMENT '所属平台',
|
||||
`authorities` JSON NULL COMMENT '角色拥有的数据权限',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT '角色表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `rel_user_role`
|
||||
(
|
||||
`user_id` VARCHAR(36) NOT NULL COMMENT '用户id',
|
||||
`role_id` VARCHAR(36) NOT NULL COMMENT '角色id',
|
||||
PRIMARY KEY (`user_id`, `role_id`)
|
||||
) COMMENT '用户角色关系表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `t_permission`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`type` VARCHAR(24) NOT NULL COMMENT '权限类型',
|
||||
`admin` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否是管理员权限',
|
||||
`leaf` BIT(1) NULL COMMENT '是否是叶子节点',
|
||||
`parent_id` VARCHAR(36) NULL DEFAULT NULL COMMENT '上级id',
|
||||
`parent_ids` JSON NULL COMMENT '冗余的上级id集合',
|
||||
`depth` INT NULL COMMENT '深度',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT '权限定义表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `rel_role_permission`
|
||||
(
|
||||
`role_id` VARCHAR(36) NOT NULL COMMENT '角色id',
|
||||
`permission_id` VARCHAR(36) NOT NULL COMMENT '权限id',
|
||||
PRIMARY KEY (`role_id`, `permission_id`)
|
||||
) COMMENT '角色权限关系表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `t_excel_mapping`
|
||||
(
|
||||
`id` VARCHAR(36) NOT NULL COMMENT '主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '编码',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '名称',
|
||||
`content` JSON NOT NULL COMMENT '映射关系',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
|
||||
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
|
||||
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
|
||||
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
|
||||
`delete` BIT(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT 'Excel映射方案';
|
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-data-mongodb</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -1,74 +0,0 @@
|
||||
package com.flyfish.framework.mongodb.config;
|
||||
|
||||
import com.flyfish.framework.mongodb.config.annotations.EnableMongoRepo;
|
||||
import com.flyfish.framework.mongodb.config.annotations.EnableReactiveMongoRepo;
|
||||
import com.flyfish.framework.mongodb.operations.MongoReactiveEntityOperations;
|
||||
import com.flyfish.framework.mongodb.repository.factory.DefaultReactiveRepositoryFactoryBean;
|
||||
import com.flyfish.framework.mongodb.repository.factory.DefaultRepositoryFactoryBean;
|
||||
import com.flyfish.framework.mongodb.repository.impl.DefaultReactiveRepositoryImpl;
|
||||
import com.flyfish.framework.mongodb.repository.impl.DefaultRepositoryImpl;
|
||||
import com.flyfish.framework.repository.ReactiveEntityOperations;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.mongodb.config.EnableMongoAuditing;
|
||||
import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.flyfish.framework.mongodb.config.MongoDataConfig.MongoRepositoriesImportSelector;
|
||||
|
||||
@AutoConfiguration(before = MongoDataAutoConfiguration.class)
|
||||
@Import(MongoRepositoriesImportSelector.class)
|
||||
public class MongoDataConfig {
|
||||
|
||||
static class MongoRepositoriesImportSelector implements ImportSelector {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String[] selectImports(@NonNull AnnotationMetadata annotationMetadata) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
if (annotationMetadata.hasMetaAnnotation(EnableMongoRepo.class.getName())) {
|
||||
imports.add(MongoRepoConfig.class.getName());
|
||||
}
|
||||
if (annotationMetadata.hasMetaAnnotation(EnableReactiveMongoRepo.class.getName())) {
|
||||
imports.add(ReactiveMongoRepoConfig.class.getName());
|
||||
}
|
||||
return imports.toArray(new String[]{});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EnableReactiveMongoRepositories(
|
||||
repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class,
|
||||
repositoryBaseClass = DefaultReactiveRepositoryImpl.class,
|
||||
basePackages = "com.flyfish.framework.*.repository"
|
||||
)
|
||||
@EnableReactiveMongoAuditing
|
||||
public static class ReactiveMongoRepoConfig {
|
||||
|
||||
@Bean
|
||||
public ReactiveEntityOperations mongoReactiveEntityOperations(ReactiveMongoOperations reactiveMongoOperations) {
|
||||
return new MongoReactiveEntityOperations(reactiveMongoOperations);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableMongoRepositories(
|
||||
repositoryFactoryBeanClass = DefaultRepositoryFactoryBean.class,
|
||||
repositoryBaseClass = DefaultRepositoryImpl.class,
|
||||
basePackages = "com.flyfish.framework.*.repository"
|
||||
)
|
||||
@EnableMongoAuditing
|
||||
public static class MongoRepoConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package com.flyfish.framework.mongodb.operations;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.Query;
|
||||
import com.flyfish.framework.repository.ReactiveEntityOperations;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.util.CastUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MongoReactiveEntityOperations implements ReactiveEntityOperations {
|
||||
|
||||
private final ReactiveMongoOperations reactiveOperations;
|
||||
|
||||
/**
|
||||
* 查询,通过查询条件和类进行精确查询
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain, E extends EntityInformation<T, String>> Mono<T> find(Query query, E entityInformation) {
|
||||
return reactiveOperations.findOne(toNative(query, entityInformation), cast(entityInformation).getJavaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部符合条件的实体
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain, E extends EntityInformation<T, String>> Flux<T> findAll(Query query, E entityInformation) {
|
||||
return reactiveOperations.find(toNative(query, entityInformation), cast(entityInformation).getJavaType());
|
||||
}
|
||||
|
||||
private <T extends Domain> MongoEntityInformation<T, String> cast(EntityInformation<T, String> entityInformation) {
|
||||
return CastUtils.cast(entityInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存实体
|
||||
*
|
||||
* @param bean 实体
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> Mono<T> save(T bean) {
|
||||
return reactiveOperations.save(bean);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.flyfish.framework.mongodb.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.mongodb.query.adaptor.MongoCriteriaAdaptor;
|
||||
import com.flyfish.framework.mongodb.query.converter.MongoQueryConverter;
|
||||
import com.flyfish.framework.query.spi.QueryFactory;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import com.flyfish.framework.query.spi.converter.QueryConverter;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
/**
|
||||
* mongo查询工厂
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class MongoQueryFactory implements QueryFactory<Criteria, Query> {
|
||||
|
||||
private final CriteriaAdaptor<Criteria> adaptor = new MongoCriteriaAdaptor();
|
||||
|
||||
private final QueryConverter<Query> converter = new MongoQueryConverter(adaptor);
|
||||
|
||||
@Override
|
||||
public boolean supports(EntityInformation<? extends Domain, String> entity) {
|
||||
return entity instanceof MongoEntityInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryConverter<Query> getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CriteriaAdaptor<Criteria> getAdaptor() {
|
||||
return adaptor;
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package com.flyfish.framework.mongodb.query.adaptor;
|
||||
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class MongoCriteriaAdaptor implements CriteriaAdaptor<Criteria> {
|
||||
|
||||
@Override
|
||||
public Criteria empty() {
|
||||
return new Criteria();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria and(Criteria first, Criteria other) {
|
||||
return first.andOperator(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria or(Criteria first, Criteria other) {
|
||||
return first.orOperator(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria eq(String column, Object value) {
|
||||
return Criteria.where(column).is(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria hasId(String column, Object value) {
|
||||
return Criteria.where(column + ".$id").is(new ObjectId(String.valueOf(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria has(String column, Object value) {
|
||||
return Criteria.where(column).is(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria ne(String column, Object value) {
|
||||
return Criteria.where(column).ne(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria between(String column, List<?> items) {
|
||||
return Criteria.where(column).gte(items.get(0)).lte(items.get(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria like(String column, String keyword, Queries.Direction direction) {
|
||||
// 匹配开头
|
||||
if (direction == Queries.Direction.LEFT) {
|
||||
keyword = "^" + keyword;
|
||||
} else if (direction == Queries.Direction.RIGHT) {
|
||||
keyword = keyword + "$";
|
||||
}
|
||||
return Criteria.where(column).regex(keyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria isNull(String column) {
|
||||
return Criteria.where(column).isNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria lt(String column, Object value) {
|
||||
return Criteria.where(column).lt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria lte(String column, Object value) {
|
||||
return Criteria.where(column).lte(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria gt(String column, Object value) {
|
||||
return Criteria.where(column).gt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria gte(String column, Object value) {
|
||||
return Criteria.where(column).gte(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria in(String column, Collection<?> values) {
|
||||
return Criteria.where(column).in(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria in(String column, Object... values) {
|
||||
return Criteria.where(column).in(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria nin(String column, Object... values) {
|
||||
return Criteria.where(column).nin(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria nin(String column, Collection<?> values) {
|
||||
return Criteria.where(column).nin(values);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.flyfish.framework.mongodb.query.converter;
|
||||
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import com.flyfish.framework.query.spi.converter.QueryConverter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MongoQueryConverter implements QueryConverter<Query> {
|
||||
|
||||
private final CriteriaAdaptor<Criteria> adaptor;
|
||||
|
||||
/**
|
||||
* 转换查询
|
||||
*
|
||||
* @param query 封装的查询
|
||||
* @return 本地查询
|
||||
*/
|
||||
@Override
|
||||
public Query convert(com.flyfish.framework.query.Query query) {
|
||||
Criteria condition = Criteria.where("delete").ne(true);
|
||||
// 添加条件
|
||||
Criteria criteria = getCriteria(query);
|
||||
if (null != criteria) {
|
||||
condition = condition.andOperator(criteria);
|
||||
}
|
||||
// 构建查询
|
||||
Query result = Query.query(condition);
|
||||
if (null != query.getSort()) {
|
||||
result = result.with(query.getSort());
|
||||
}
|
||||
if (null != query.getPageable()) {
|
||||
result = result.with(query.getPageable());
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(query.getFields())) {
|
||||
result.fields().include(query.getFields().toArray(new String[]{}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取真实条件
|
||||
*
|
||||
* @param query 查询信息
|
||||
* @return 结果
|
||||
*/
|
||||
private Criteria getCriteria(com.flyfish.framework.query.Query query) {
|
||||
// 当且仅当有example且正确生成条件时,才允许使用example构造
|
||||
if (null != query.getExample()) {
|
||||
return new Criteria().alike(query.getExample());
|
||||
}
|
||||
// 如果存在查询定义,直接使用查询定义
|
||||
if (null != query.getDefinition()) {
|
||||
return query.getDefinition().build(adaptor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.flyfish.framework.mongodb.repository.impl;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Support for query execution using {@link Pageable}. Using {@link ReactivePageableExecutionUtils} assumes that data
|
||||
* queries are cheaper than {@code COUNT} queries and so some cases can take advantage of optimizations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
abstract class ReactivePageableExecutionUtils {
|
||||
|
||||
private ReactivePageableExecutionUtils() {}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link Mono} applying
|
||||
* optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the
|
||||
* result size and {@link Pageable}.
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param pageable must not be {@literal null}.
|
||||
* @param totalSupplier must not be {@literal null}.
|
||||
* @return the {@link Page}.
|
||||
*/
|
||||
public static <T> Mono<Page<T>> getPage(List<T> content, Pageable pageable, Mono<Long> totalSupplier) {
|
||||
|
||||
Assert.notNull(content, "Content must not be null!");
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
Assert.notNull(totalSupplier, "TotalSupplier must not be null!");
|
||||
|
||||
if (pageable.isUnpaged() || pageable.getOffset() == 0) {
|
||||
|
||||
if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
|
||||
return Mono.just(new PageImpl<>(content, pageable, content.size()));
|
||||
}
|
||||
|
||||
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
|
||||
}
|
||||
|
||||
if (!content.isEmpty() && pageable.getPageSize() > content.size()) {
|
||||
return Mono.just(new PageImpl<>(content, pageable, pageable.getOffset() + content.size()));
|
||||
}
|
||||
|
||||
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
com.flyfish.framework.query.spi.QueryFactory=\
|
||||
com.flyfish.framework.mongodb.query.MongoQueryFactory
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.flyfish.framework.mongodb.config.MongoDataConfig
|
@ -1,39 +0,0 @@
|
||||
package com.flyfish.test.mongo;
|
||||
|
||||
import com.flyfish.test.mongo.repository.TestDO;
|
||||
import com.flyfish.test.mongo.repository.TestQO;
|
||||
import com.flyfish.test.mongo.repository.TestRepository;
|
||||
import com.flyfish.framework.mongodb.config.MongoDataConfig;
|
||||
import com.flyfish.framework.mongodb.config.annotations.EnableReactiveMongoRepo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(classes = MongoRepositoryTest.class)
|
||||
@SpringBootApplication(scanBasePackages = "com.flyfish.framework")
|
||||
@Import(MongoDataConfig.class)
|
||||
@EnableReactiveMongoRepo
|
||||
public class MongoRepositoryTest {
|
||||
|
||||
@Resource
|
||||
private TestRepository testRepository;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
TestQO qo = new TestQO();
|
||||
qo.setName("名");
|
||||
TestDO test = new TestDO();
|
||||
test.setId("1");
|
||||
test.setCode("ttt");
|
||||
test.setName("测试名称");
|
||||
|
||||
System.out.println(testRepository.insert(test).block());
|
||||
System.out.println(testRepository.findAll(qo).collectList().block());
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.flyfish.test.mongo.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Document("test")
|
||||
@Data
|
||||
public class TestDO extends AuditDomain {
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.flyfish.test.mongo.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.NameLikeQo;
|
||||
|
||||
public class TestQO extends NameLikeQo<TestDO> {
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.flyfish.test.mongo.repository;
|
||||
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
|
||||
/**
|
||||
* 测试仓库
|
||||
*/
|
||||
public interface TestRepository extends DefaultReactiveRepository<TestDO> {
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
host: 127.0.0.1
|
||||
port: 27017
|
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-data-r2dbc</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data-relational</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.asyncer</groupId>
|
||||
<artifactId>r2dbc-mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -1,101 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.config.callback.ReferenceR2dbcCallback;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.metadata.impl.SimpleR2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.operations.R2dbcReactiveEntityOperations;
|
||||
import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean;
|
||||
import com.flyfish.framework.r2dbc.repository.impl.QueryingR2dbcReactiveRepository;
|
||||
import com.flyfish.framework.repository.ReactiveEntityOperations;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
|
||||
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.r2dbc.connection.init.CompositeDatabasePopulator;
|
||||
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||
import org.springframework.r2dbc.connection.init.DatabasePopulator;
|
||||
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* r2dbc数据配置
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@EnableR2dbcRepositories(
|
||||
repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class,
|
||||
repositoryBaseClass = QueryingR2dbcReactiveRepository.class,
|
||||
basePackages = "com.flyfish.framework.*.repository"
|
||||
)
|
||||
@EnableR2dbcAuditing
|
||||
@AutoConfiguration(before = R2dbcAutoConfiguration.class)
|
||||
public class R2dbcDataConfig {
|
||||
|
||||
@Bean
|
||||
public ReactiveEntityOperations r2dbcReactiveEntityOperations(R2dbcEntityOperations r2dbcEntityOperations) {
|
||||
return new R2dbcReactiveEntityOperations(r2dbcEntityOperations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EntityCallback<Domain> referenceR2dbcCallback(R2dbcMetadataManager metadataManager) {
|
||||
return new ReferenceR2dbcCallback(metadataManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public R2dbcMetadataManager r2dbcMetadataManager(R2dbcMappingContext mappingContext) {
|
||||
return new SimpleR2dbcMetadataManager(mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库初始化
|
||||
*
|
||||
* @param connectionFactory 链接仓库
|
||||
* @return 结果
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactoryInitializer connectionFactoryInitializer(ConnectionFactory connectionFactory) {
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||
// 添加主要脚本
|
||||
preparePopulator("classpath*:schema.sql").ifPresent(populator::addPopulators);
|
||||
// 动态执行方言脚本
|
||||
preparePopulator("classpath*:dialect/" + connectionFactory.getMetadata().getName().toLowerCase() + ".sql")
|
||||
.ifPresent(populator::addPopulators);
|
||||
initializer.setDatabasePopulator(populator);
|
||||
// 添加清理脚本
|
||||
preparePopulator("classpath*:schema-clean.sql").ifPresent(initializer::setDatabaseCleaner);
|
||||
// 返回初始化器
|
||||
return initializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态判定存在性,提供健壮的架构
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return 结果
|
||||
*/
|
||||
private Optional<DatabasePopulator> preparePopulator(String path) {
|
||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
try {
|
||||
Resource[] resources = resolver.getResources(path);
|
||||
if (ArrayUtils.isNotEmpty(resources)) {
|
||||
return Optional.of(new ResourceDatabasePopulator(resources));
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.annotations;
|
||||
|
||||
import com.flyfish.framework.r2dbc.repository.factory.DefaultReactiveRepositoryFactoryBean;
|
||||
import com.flyfish.framework.r2dbc.repository.impl.QueryingR2dbcReactiveRepository;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 启用注解,能够指定基本路径
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@EnableR2dbcRepositories(
|
||||
repositoryFactoryBeanClass = DefaultReactiveRepositoryFactoryBean.class,
|
||||
repositoryBaseClass = QueryingR2dbcReactiveRepository.class
|
||||
)
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface EnableR2dbcRepo {
|
||||
|
||||
/**
|
||||
* 扫描的基本路径
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@AliasFor(annotation = EnableR2dbcRepositories.class)
|
||||
String[] basePackages() default {};
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.callback;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.Queries;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcRelation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import com.flyfish.framework.r2dbc.metadata.visitor.MetadataHandler;
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.core.StatementMapper;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class AssociationFetcher implements MetadataHandler {
|
||||
|
||||
private final RepositoryProvider repositories;
|
||||
|
||||
private final Supplier<R2dbcEntityOperations> operations;
|
||||
|
||||
/**
|
||||
* 处理association
|
||||
*
|
||||
* @param association 一对一关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleAssociation(R2dbcAssociation association, PersistentPropertyAccessor<Domain> accessor) {
|
||||
// 准备查询, 获取对方的仓库
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(association.getEntity());
|
||||
// 准备设置器
|
||||
FieldAccessor field = association.getAccessor();
|
||||
if (association.isInner()) {
|
||||
// 处理内部查询,以对象内的值作为查询条件
|
||||
Object value = accessor.getProperty(association.getField());
|
||||
if (ObjectUtils.isEmpty(value)) return null;
|
||||
// 查询
|
||||
return repository.findById(String.valueOf(value))
|
||||
.map(result -> field.set(accessor.getBean(), result))
|
||||
.defaultIfEmpty(accessor.getBean());
|
||||
} else {
|
||||
// 处理外部关联查询
|
||||
String id = accessor.getBean().getId();
|
||||
// 直接通过外部字段查询
|
||||
return repository.findOne(Queries.where(getColumnName(association.getField())).eq(id).wrap())
|
||||
.map(result -> field.set(accessor.getBean(), result))
|
||||
.defaultIfEmpty(accessor.getBean());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理collection
|
||||
*
|
||||
* @param collection 一对多关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleCollection(R2dbcCollection collection, PersistentPropertyAccessor<Domain> accessor) {
|
||||
// 获取条件
|
||||
String id = accessor.getBean().getId();
|
||||
// 准备查询
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(collection.getEntity());
|
||||
// 准备设置
|
||||
FieldAccessor field = collection.getAccessor();
|
||||
// 直接通过外部字段查询
|
||||
return repository.findAll(Queries.where(getColumnName(collection.getField())).eq(id).wrap())
|
||||
.collectList()
|
||||
.map(result -> field.set(accessor.getBean(), result))
|
||||
.defaultIfEmpty(accessor.getBean());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理relation
|
||||
*
|
||||
* @param relation 多对多关联
|
||||
* @param accessor 访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleRelation(R2dbcRelation relation, PersistentPropertyAccessor<Domain> accessor) {
|
||||
// 准备目标对象的repo
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(relation.getEntity());
|
||||
// 准备查询参数
|
||||
String id = accessor.getBean().getId();
|
||||
// 查询关联id集合
|
||||
Flux<String> ids = fetchRelationIds(relation, id);
|
||||
// 准备设置
|
||||
FieldAccessor field = relation.getAccessor();
|
||||
// 查询最终结果
|
||||
return repository.findAllById(ids)
|
||||
.collectList()
|
||||
.map(result -> field.set(accessor.getBean(), result))
|
||||
.defaultIfEmpty(accessor.getBean());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询当前实体关联的ids
|
||||
*
|
||||
* @param relation 关联信息
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private Flux<String> fetchRelationIds(R2dbcRelation relation, String id) {
|
||||
// 准备查询器
|
||||
R2dbcEntityOperations entityOperations = operations.get();
|
||||
StatementMapper statementMapper = entityOperations.getDataAccessStrategy().getStatementMapper();
|
||||
DatabaseClient databaseClient = entityOperations.getDatabaseClient();
|
||||
// 构建查询
|
||||
StatementMapper.SelectSpec select = statementMapper.createSelect(relation.getTableName())
|
||||
.withProjection(relation.getForeignField()).withCriteria(Criteria.where(relation.getField()).is(id));
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(select);
|
||||
// 执行查询
|
||||
return databaseClient.sql(operation)
|
||||
.map(row -> row.get(0, String.class))
|
||||
.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过持久化属性获取真实的列名
|
||||
*
|
||||
* @param property 属性
|
||||
* @return 结果
|
||||
*/
|
||||
private String getColumnName(PersistentProperty<?> property) {
|
||||
if (property instanceof RelationalPersistentProperty) {
|
||||
return ((RelationalPersistentProperty) property).getColumnName().getReference();
|
||||
}
|
||||
return property.getName();
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.callback;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcRelation;
|
||||
import com.flyfish.framework.r2dbc.metadata.visitor.MetadataHandler;
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.core.StatementMapper;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class AssociationWriter implements MetadataHandler {
|
||||
|
||||
private final RepositoryProvider repositories;
|
||||
|
||||
private final Supplier<R2dbcEntityOperations> operations;
|
||||
|
||||
/**
|
||||
* 处理association
|
||||
*
|
||||
* @param association 一对一关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleAssociation(R2dbcAssociation association, PersistentPropertyAccessor<Domain> accessor) {
|
||||
Domain entity = accessor.getBean();
|
||||
// 获取目标值
|
||||
Domain value = association.getAccessor().get(entity);
|
||||
if (null == value) return null;
|
||||
// 准备仓库
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(association.getEntity());
|
||||
// 执行逻辑
|
||||
if (association.isInner()) {
|
||||
// 保存内部关联对象,并设置关联值。如果有id,更新,没有id,插入
|
||||
if (association.getEntity().isNew(value)) {
|
||||
// 先保存,再设置
|
||||
return repository.insert(value).map(saved -> {
|
||||
// 设置内部id关联
|
||||
accessor.setProperty(association.getField(), saved.getId());
|
||||
return entity;
|
||||
});
|
||||
} else {
|
||||
// 设置内部id关联
|
||||
accessor.setProperty(association.getField(), value.getId());
|
||||
// 尝试保存
|
||||
return repository.save(value).thenReturn(entity);
|
||||
}
|
||||
} else {
|
||||
// 保存外部关联对象,并更新外部关联当前实体的id
|
||||
PersistentPropertyAccessor<Domain> otherAccessor = association.getEntity().getPropertyAccessor(value);
|
||||
// 先设置目标id
|
||||
otherAccessor.setProperty(association.getField(), entity.getId());
|
||||
// 再保存
|
||||
return repository.save(value).thenReturn(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理collection
|
||||
*
|
||||
* @param collection 一对多关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleCollection(R2dbcCollection collection, PersistentPropertyAccessor<Domain> accessor) {
|
||||
Domain entity = accessor.getBean();
|
||||
// 获取目标值
|
||||
List<Domain> value = collection.getAccessor().get(entity);
|
||||
if (CollectionUtils.isEmpty(value)) return null;
|
||||
// 获取目标实体信息
|
||||
PersistentEntity<?, ?> targetEntity = collection.getEntity();
|
||||
// 保存外部关联对象,并更新外部关联当前实体的id
|
||||
value.forEach(item -> {
|
||||
PersistentPropertyAccessor<Domain> otherAccessor = collection.getEntity().getPropertyAccessor(item);
|
||||
otherAccessor.setProperty(collection.getField(), entity.getId());
|
||||
});
|
||||
// 准备仓库
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(targetEntity);
|
||||
// 批量保存
|
||||
return repository.saveAll(value).collectList()
|
||||
.map(list -> collection.getAccessor().set(entity, list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理relation
|
||||
*
|
||||
* @param relation 多对多关联
|
||||
* @param accessor 访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Domain> handleRelation(R2dbcRelation relation, PersistentPropertyAccessor<Domain> accessor) {
|
||||
// 保存多对多关系,需要先分别插入两边的数据,然后在关联表新增或更新
|
||||
Domain entity = accessor.getBean();
|
||||
// 获取目标值
|
||||
List<Domain> value = relation.getAccessor().get(entity);
|
||||
if (CollectionUtils.isEmpty(value)) return null;
|
||||
// 获取目标实体信息
|
||||
PersistentEntity<?, ?> targetEntity = relation.getEntity();
|
||||
// 准备仓库
|
||||
DefaultReactiveRepository<Domain> repository = repositories.get(targetEntity);
|
||||
// 批量保存
|
||||
return repository.saveAll(value).collectList()
|
||||
.flatMap(list -> {
|
||||
// 回填数据
|
||||
relation.getAccessor().set(entity, list);
|
||||
// 关系表保存
|
||||
return saveRelationIds(relation, entity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存关系表
|
||||
*
|
||||
* @param relation 关系信息
|
||||
* @param entity 实体信息
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private Mono<Domain> saveRelationIds(R2dbcRelation relation, Domain entity) {
|
||||
// 准备查询器
|
||||
R2dbcEntityOperations entityOperations = operations.get();
|
||||
StatementMapper statementMapper = entityOperations.getDataAccessStrategy().getStatementMapper();
|
||||
DatabaseClient databaseClient = entityOperations.getDatabaseClient();
|
||||
// 构建保存语句,先删后增
|
||||
StatementMapper.DeleteSpec deleteSpec = statementMapper.createDelete(relation.getTableName())
|
||||
.withCriteria(Criteria.where(relation.getField()).is(entity.getId()));
|
||||
PreparedOperation<?> deletion = statementMapper.getMappedObject(deleteSpec);
|
||||
Mono<Domain> deleting = execute(databaseClient, deletion, entity);
|
||||
// 构建插入语句,多条
|
||||
List<Domain> items = relation.getAccessor().get(entity);
|
||||
List<Mono<Domain>> insertions = items.stream()
|
||||
.map(item -> {
|
||||
StatementMapper.InsertSpec insertion = statementMapper.createInsert(relation.getTableName())
|
||||
.withColumn(relation.getField(), Parameter.from(entity.getId()))
|
||||
.withColumn(relation.getForeignField(), Parameter.from(item.getId()));
|
||||
return statementMapper.getMappedObject(insertion);
|
||||
})
|
||||
.map(operation -> execute(databaseClient, operation, entity))
|
||||
.collect(Collectors.toList());
|
||||
// 执行查询
|
||||
return deleting.flatMap(list -> Mono.zip(insertions, results -> entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行更新语句的逻辑,保证多行每一行都有执行到
|
||||
*
|
||||
* @param databaseClient 数据库客户端
|
||||
* @param operation 预编译的sql
|
||||
* @param entity 实体
|
||||
* @return 结果
|
||||
*/
|
||||
private Mono<Domain> execute(DatabaseClient databaseClient, PreparedOperation<?> operation, Domain entity) {
|
||||
return databaseClient.sql(operation)
|
||||
.fetch()
|
||||
.rowsUpdated()
|
||||
.thenReturn(entity);
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.callback;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata;
|
||||
import com.flyfish.framework.r2dbc.metadata.visitor.MetadataHandler;
|
||||
import com.flyfish.framework.r2dbc.metadata.visitor.R2dbcTableMetadataVisitor;
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.util.CastUtils;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ReferenceR2dbcCallback implements AfterConvertCallback<Domain>, AfterSaveCallback<Domain>, RepositoryProvider, Supplier<R2dbcEntityOperations> {
|
||||
|
||||
private final R2dbcMetadataManager r2dbcMetadataManager;
|
||||
private final MetadataHandler fetchHandler;
|
||||
private final MetadataHandler writeHandler;
|
||||
private Lazy<R2dbcEntityOperations> operations;
|
||||
private Lazy<Map<Class<?>, DefaultReactiveRepository<?>>> repositories;
|
||||
|
||||
public ReferenceR2dbcCallback(R2dbcMetadataManager r2dbcMetadataManager) {
|
||||
this.r2dbcMetadataManager = r2dbcMetadataManager;
|
||||
this.fetchHandler = new AssociationFetcher(this, this);
|
||||
this.writeHandler = new AssociationWriter(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Publisher<Domain> onAfterConvert(@NonNull Domain entity, @NonNull SqlIdentifier table) {
|
||||
return Mono.deferContextual(ctx -> {
|
||||
Optional<Qo<? extends Domain>> query = ctx.getOrEmpty(Qo.KEY);
|
||||
query.ifPresent(qo -> entity.setCurrentUser(qo.getUser()));
|
||||
// 判断是否需要获取关联信息
|
||||
boolean fetchRefs = query.map(Qo::isFetchRef).orElse(false);
|
||||
if (fetchRefs) {
|
||||
R2dbcTableMetadata metadata = r2dbcMetadataManager.getMetadata(entity.getClass());
|
||||
R2dbcTableMetadataVisitor visitor = new R2dbcTableMetadataVisitor(entity, fetchHandler);
|
||||
metadata.visit(visitor);
|
||||
return visitor.getResult();
|
||||
}
|
||||
return Mono.just(entity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存完成后的回调,用于保存额外的关联对象
|
||||
*
|
||||
* @param entity 已经保存的实体,带有id信息
|
||||
* @param outboundRow {@link OutboundRow} 输出行,包含实体的完整内容 {@code entity}.
|
||||
* @param table 表名
|
||||
* @return 当前已经实例化的对象
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public Publisher<Domain> onAfterSave(@NonNull Domain entity, @NonNull OutboundRow outboundRow, @NonNull SqlIdentifier table) {
|
||||
Class<? extends Domain> entityClass = entity.getClass();
|
||||
// 遍历fields,找到要注入的数据
|
||||
R2dbcTableMetadata metadata = r2dbcMetadataManager.getMetadata(entityClass);
|
||||
if (metadata.hasAssociation()) {
|
||||
R2dbcTableMetadataVisitor visitor = new R2dbcTableMetadataVisitor(entity, writeHandler);
|
||||
metadata.visit(visitor);
|
||||
return visitor.getResult();
|
||||
}
|
||||
return Mono.just(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Domain, R extends DefaultReactiveRepository<T>> R get(PersistentEntity<?, ?> entity) {
|
||||
return CastUtils.cast(repositories.get().get(entity.getType()));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRepositories(ObjectProvider<DefaultReactiveRepository<?>> repositories) {
|
||||
this.repositories = Lazy.of(() -> repositories.stream()
|
||||
.collect(Collectors.toMap(repo -> repo.getEntityInformation().getJavaType(), Function.identity())));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setR2dbcEntityOperations(ObjectProvider<R2dbcEntityOperations> operations) {
|
||||
this.operations = Lazy.of(operations::getIfAvailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体操作
|
||||
*/
|
||||
@Override
|
||||
public R2dbcEntityOperations get() {
|
||||
return operations.get();
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.config.callback;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
|
||||
@FunctionalInterface
|
||||
interface RepositoryProvider {
|
||||
|
||||
<T extends Domain, R extends DefaultReactiveRepository<T>> R get(PersistentEntity<?, ?> entity);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
|
||||
/**
|
||||
* r2dbc元数据管理器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface R2dbcMetadataManager {
|
||||
|
||||
/**
|
||||
* 获取元数据
|
||||
*
|
||||
* @param entityClass 实体类名
|
||||
* @return 结果
|
||||
*/
|
||||
R2dbcTableMetadata getMetadata(Class<? extends Domain> entityClass);
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata;
|
||||
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcRelation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import com.flyfish.framework.relational.mapping.Association;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.Visitable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 表元数据
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
@Property("表的元数据,用于关联查询")
|
||||
public class R2dbcTableMetadata implements Visitable {
|
||||
|
||||
@Property("表实体信息")
|
||||
private PersistentEntity<?, ?> entity;
|
||||
|
||||
@Property("一对一关联信息")
|
||||
private List<R2dbcAssociation> associations = new ArrayList<>();
|
||||
|
||||
@Property("一对多关联信息")
|
||||
private List<R2dbcCollection> collections = new ArrayList<>();
|
||||
|
||||
@Property("多对多关联信息")
|
||||
private List<R2dbcRelation> relations = new ArrayList<>();
|
||||
|
||||
public R2dbcTableMetadata(PersistentEntity<?, ?> entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public void addAssociation(PersistentEntity<?, ?> entity, PersistentProperty<?> property, FieldAccessor accessor, boolean inner) {
|
||||
this.associations.add(R2dbcAssociation.of(entity, property, accessor, inner));
|
||||
}
|
||||
|
||||
public void addCollection(PersistentEntity<?, ?> entity, PersistentProperty<?> property, FieldAccessor accessor) {
|
||||
this.collections.add(R2dbcCollection.of(entity, property, accessor));
|
||||
}
|
||||
|
||||
public void addRelation(PersistentEntity<?, ?> entity, Association association, FieldAccessor accessor) {
|
||||
this.relations.add(R2dbcRelation.of(entity, association, accessor));
|
||||
}
|
||||
|
||||
public boolean hasAssociation() {
|
||||
return !associations.isEmpty() || !collections.isEmpty() || !relations.isEmpty();
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.impl;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import com.flyfish.framework.relational.mapping.Association;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* 简单的元数据管理器
|
||||
*
|
||||
* @author wangyu
|
||||
* 基于线程安全的反射方法进行加载并持有
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleR2dbcMetadataManager implements R2dbcMetadataManager {
|
||||
|
||||
private final R2dbcMappingContext r2dbcMappingContext;
|
||||
private final ConcurrentMap<Class<? extends Domain>, R2dbcTableMetadata> METADATA_STORE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取元数据
|
||||
*
|
||||
* @param entityClass 实体类名
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R2dbcTableMetadata getMetadata(Class<? extends Domain> entityClass) {
|
||||
return METADATA_STORE.computeIfAbsent(entityClass, this::computeMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算元数据信息
|
||||
*
|
||||
* @param entityClass 实体类
|
||||
* @return 结果
|
||||
*/
|
||||
private R2dbcTableMetadata computeMetadata(Class<? extends Domain> entityClass) {
|
||||
PersistentEntity<?, ?> entity = r2dbcMappingContext.getRequiredPersistentEntity(entityClass);
|
||||
MetadataCreator creator = new MetadataCreator(entity);
|
||||
FieldUtils.getFieldsListWithAnnotation(entityClass, Association.class).forEach(creator);
|
||||
return creator.metadata;
|
||||
}
|
||||
|
||||
private class MetadataCreator implements Consumer<Field> {
|
||||
|
||||
private final PersistentEntity<?, ?> entity;
|
||||
|
||||
private final R2dbcTableMetadata metadata;
|
||||
|
||||
private MetadataCreator(PersistentEntity<?, ?> entity) {
|
||||
this.entity = entity;
|
||||
this.metadata = new R2dbcTableMetadata(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有关联属性
|
||||
*
|
||||
* @param field 属性永远都不为空
|
||||
*/
|
||||
@Override
|
||||
public void accept(Field field) {
|
||||
Association association = AnnotatedElementUtils.findMergedAnnotation(field, Association.class);
|
||||
if (null == association) return;
|
||||
// 获取字段访问器,只支持标准的bean
|
||||
FieldAccessor accessor = FieldAccessor.of(entity.getType(), field);
|
||||
if (!accessor.isAvailable()) return;
|
||||
// 获取具体类型
|
||||
Class<?> type = field.getType();
|
||||
if (ClassUtils.isAssignable(type, Domain.class)) {
|
||||
// 目标类型
|
||||
PersistentEntity<?, ?> targetEntity = r2dbcMappingContext.getRequiredPersistentEntity(type);
|
||||
// 多对多关联
|
||||
if (StringUtils.isNotBlank(association.relationTable())) {
|
||||
metadata.addRelation(targetEntity, association, accessor);
|
||||
return;
|
||||
}
|
||||
// 一对一关联
|
||||
if (StringUtils.isNotBlank(association.field())) {
|
||||
// 获取内部属性
|
||||
PersistentProperty<?> property = entity.getRequiredPersistentProperty(association.field());
|
||||
// 添加内部关联
|
||||
metadata.addAssociation(targetEntity, property, accessor, true);
|
||||
} else if (StringUtils.isNotBlank(association.foreignField())) {
|
||||
// 获取外部属性
|
||||
PersistentProperty<?> property = targetEntity.getRequiredPersistentProperty(association.foreignField());
|
||||
// 添加外部关联
|
||||
metadata.addAssociation(targetEntity, property, accessor, false);
|
||||
} else {
|
||||
// 都为空,从当前对象查找目标类型的reference,并自动添加关联
|
||||
Optional<PersistentProperty<?>> inner = findInnerKey(type);
|
||||
if (inner.isPresent()) {
|
||||
metadata.addAssociation(targetEntity, inner.get(), accessor, true);
|
||||
} else {
|
||||
findForeignKey(targetEntity).ifPresent(property -> metadata.addAssociation(targetEntity, property, accessor, 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.relationTable())) {
|
||||
metadata.addRelation(targetEntity, association, accessor);
|
||||
return;
|
||||
}
|
||||
// 一对多关联,仅支持外部字段
|
||||
if (StringUtils.isNotBlank(association.foreignField())) {
|
||||
// 获取外部属性
|
||||
PersistentProperty<?> property = targetEntity.getRequiredPersistentProperty(association.foreignField());
|
||||
// 添加外部关联
|
||||
metadata.addCollection(targetEntity, property, accessor);
|
||||
} else {
|
||||
// 都为空,从当前对象查找目标类型的reference,并自动添加关联
|
||||
findForeignKey(targetEntity).ifPresent(property -> metadata.addCollection(targetEntity, property, accessor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前实体查找目的实体的外键
|
||||
* 从当前实体找到关联目标实体的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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.reference;
|
||||
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
@Data
|
||||
@Property("一对一关联查询")
|
||||
@AllArgsConstructor
|
||||
public class R2dbcAssociation {
|
||||
|
||||
@Property("关联实体类")
|
||||
private PersistentEntity<?, ?> entity;
|
||||
|
||||
@Property("关联字段")
|
||||
private PersistentProperty<?> field;
|
||||
|
||||
@Property("目标值访问器")
|
||||
private FieldAccessor accessor;
|
||||
|
||||
@Property("字段是否在当前实体内部")
|
||||
boolean inner;
|
||||
|
||||
public static R2dbcAssociation of(PersistentEntity<?, ?> entity, PersistentProperty<?> field, FieldAccessor accessor, boolean inner) {
|
||||
return new R2dbcAssociation(entity, field, accessor, inner);
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.reference;
|
||||
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
@Data
|
||||
@Property("一对多关联查询")
|
||||
@AllArgsConstructor
|
||||
public class R2dbcCollection {
|
||||
|
||||
@Property("关联实体类")
|
||||
private PersistentEntity<?, ?> entity;
|
||||
|
||||
@Property("关联字段")
|
||||
private PersistentProperty<?> field;
|
||||
|
||||
@Property("目标值访问器")
|
||||
private FieldAccessor accessor;
|
||||
|
||||
public static R2dbcCollection of(PersistentEntity<?, ?> entity, PersistentProperty<?> field, FieldAccessor accessor) {
|
||||
return new R2dbcCollection(entity, field, accessor);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.reference;
|
||||
|
||||
import com.flyfish.framework.annotations.Property;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.field.FieldAccessor;
|
||||
import com.flyfish.framework.relational.mapping.Association;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
|
||||
/**
|
||||
* 多对多关系
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
@Property("多对多关系")
|
||||
@AllArgsConstructor
|
||||
public class R2dbcRelation {
|
||||
|
||||
@Property("关联实体类")
|
||||
private PersistentEntity<?, ?> entity;
|
||||
|
||||
@Property("关系表名称")
|
||||
private String tableName;
|
||||
|
||||
@Property("关系表本实体id字段")
|
||||
private String field;
|
||||
|
||||
@Property("关系表外部实体id字段")
|
||||
private String foreignField;
|
||||
|
||||
@Property("目标值访问器")
|
||||
private FieldAccessor accessor;
|
||||
|
||||
public static R2dbcRelation of(PersistentEntity<?, ?> entity, Association association, FieldAccessor accessor) {
|
||||
return new R2dbcRelation(entity, association.relationTable(), association.field(), association.foreignField(), accessor);
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.reference.field;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 默认访问器实现
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
class DefaultFieldAccessor implements FieldAccessor {
|
||||
|
||||
private final PropertyDescriptor descriptor;
|
||||
|
||||
DefaultFieldAccessor(PropertyDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
* @param obj 具体对象
|
||||
* @param value 值
|
||||
* @return 设置后的对象
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> T set(T obj, Object value) {
|
||||
if (null != descriptor.getWriteMethod()) {
|
||||
invokeSetter(descriptor.getWriteMethod(), obj, value);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得字段值
|
||||
*
|
||||
* @param obj 具体对象
|
||||
* @return 具体字段值
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain, R> R get(T obj) {
|
||||
if (null != descriptor.getReadMethod()) {
|
||||
return invokeGetter(descriptor.getReadMethod(), obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter是否可用
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSetter() {
|
||||
return null != descriptor.getWriteMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* getter是否可用
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean hasGetter() {
|
||||
return null != descriptor.getReadMethod();
|
||||
}
|
||||
|
||||
private void invokeSetter(Method method, Object obj, Object value) {
|
||||
try {
|
||||
method.invoke(obj, value);
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T invokeGetter(Method method, Object obj) {
|
||||
try {
|
||||
return (T) method.invoke(obj);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.reference.field;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 字段访问器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface FieldAccessor {
|
||||
|
||||
static FieldAccessor of(Class<?> type, Field field) {
|
||||
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, field.getName());
|
||||
return new DefaultFieldAccessor(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
* @param obj 具体对象
|
||||
* @param value 值
|
||||
* @param <T> 对象的泛型
|
||||
* @return 设置后的对象
|
||||
*/
|
||||
<T extends Domain> T set(T obj, Object value);
|
||||
|
||||
/**
|
||||
* 取得字段值
|
||||
*
|
||||
* @param obj 具体对象
|
||||
* @param <T> 对象的泛型
|
||||
* @param <R> 值的泛型
|
||||
* @return 具体字段值
|
||||
*/
|
||||
<T extends Domain, R> R get(T obj);
|
||||
|
||||
/**
|
||||
* setter是否可用
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean hasSetter();
|
||||
|
||||
/**
|
||||
* getter是否可用
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean hasGetter();
|
||||
|
||||
/**
|
||||
* 是否可用,即支持读写
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean isAvailable() {
|
||||
return hasGetter() && hasSetter();
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.visitor;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcRelation;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 元数据处理器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface MetadataHandler {
|
||||
|
||||
/**
|
||||
* 处理association
|
||||
*
|
||||
* @param association 一对一关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
Mono<Domain> handleAssociation(R2dbcAssociation association, PersistentPropertyAccessor<Domain> accessor);
|
||||
|
||||
/**
|
||||
* 处理collection
|
||||
*
|
||||
* @param collection 一对多关联
|
||||
* @param accessor 对象访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
Mono<Domain> handleCollection(R2dbcCollection collection, PersistentPropertyAccessor<Domain> accessor);
|
||||
|
||||
/**
|
||||
* 处理relation
|
||||
*
|
||||
* @param relation 多对多关联
|
||||
* @param accessor 访问器
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
Mono<Domain> handleRelation(R2dbcRelation relation, PersistentPropertyAccessor<Domain> accessor);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.visitor;
|
||||
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata;
|
||||
import org.springframework.data.relational.core.sql.Visitable;
|
||||
import org.springframework.data.relational.core.sql.Visitor;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* 元数据访问者
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface MetadataVisitor extends Visitor {
|
||||
|
||||
/**
|
||||
* 访问元数据的方法
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
void enter(R2dbcTableMetadata metadata);
|
||||
|
||||
@Override
|
||||
default void enter(@NonNull Visitable segment) {
|
||||
if (segment instanceof R2dbcTableMetadata) {
|
||||
enter((R2dbcTableMetadata) segment);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.metadata.visitor;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.r2dbc.metadata.R2dbcTableMetadata;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcAssociation;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcCollection;
|
||||
import com.flyfish.framework.r2dbc.metadata.reference.R2dbcRelation;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* r2dbc表元数据访问器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public final class R2dbcTableMetadataVisitor implements MetadataVisitor {
|
||||
|
||||
@Getter
|
||||
private Mono<Domain> result;
|
||||
|
||||
private final Domain entity;
|
||||
|
||||
private final MetadataHandler handler;
|
||||
|
||||
private PersistentPropertyAccessor<Domain> accessor;
|
||||
|
||||
private R2dbcTableMetadata metadata;
|
||||
|
||||
public R2dbcTableMetadataVisitor(Domain entity, MetadataHandler handler) {
|
||||
this.entity = entity;
|
||||
this.handler = handler;
|
||||
this.result = Mono.just(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问元数据的方法
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
@Override
|
||||
public void enter(R2dbcTableMetadata metadata) {
|
||||
// 不含有关联关系,直接跳过
|
||||
if (!metadata.hasAssociation()) {
|
||||
return;
|
||||
}
|
||||
this.metadata = metadata;
|
||||
// 获取本实体的持久化对象
|
||||
PersistentEntity<?, ?> persistentEntity = metadata.getEntity();
|
||||
// 获取属性设置器
|
||||
this.accessor = persistentEntity.getPropertyAccessor(entity);
|
||||
|
||||
// 尝试填充各种关联方式
|
||||
List<Mono<Domain>> signals = Stream.of(visitAssociations(), visitCollections(), visitRelations())
|
||||
.flatMap(Function.identity())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 尝试填充
|
||||
if (CollectionUtils.isNotEmpty(signals)) {
|
||||
this.result = Mono.zip(signals, objs -> entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 访问一对一关联
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private Stream<Mono<Domain>> visitAssociations() {
|
||||
List<R2dbcAssociation> associations = metadata.getAssociations();
|
||||
if (CollectionUtils.isNotEmpty(associations)) {
|
||||
return associations.stream().map(association -> handler.handleAssociation(association, accessor));
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取一对多关联实体
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private Stream<Mono<Domain>> visitCollections() {
|
||||
List<R2dbcCollection> collections = metadata.getCollections();
|
||||
if (CollectionUtils.isNotEmpty(collections)) {
|
||||
return collections.stream().map(collection -> handler.handleCollection(collection, accessor));
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取多对多关联数据
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private Stream<Mono<Domain>> visitRelations() {
|
||||
List<R2dbcRelation> relations = metadata.getRelations();
|
||||
if (CollectionUtils.isNotEmpty(relations)) {
|
||||
return relations.stream().map(relation -> handler.handleRelation(relation, accessor));
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.operations;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.Query;
|
||||
import com.flyfish.framework.repository.ReactiveEntityOperations;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.util.CastUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class R2dbcReactiveEntityOperations implements ReactiveEntityOperations {
|
||||
|
||||
private final R2dbcEntityOperations r2dbcEntityOperations;
|
||||
|
||||
/**
|
||||
* 查询,通过查询条件和类进行精确查询
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain, E extends EntityInformation<T, String>> Mono<T> find(Query query, E entityInformation) {
|
||||
return r2dbcEntityOperations.selectOne(toNative(query, entityInformation), cast(entityInformation).getJavaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部符合条件的实体
|
||||
*
|
||||
* @param query 查询
|
||||
* @param entityInformation 实体信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain, E extends EntityInformation<T, String>> Flux<T> findAll(Query query, E entityInformation) {
|
||||
return r2dbcEntityOperations.select(toNative(query, entityInformation), cast(entityInformation).getJavaType());
|
||||
}
|
||||
|
||||
private <T extends Domain> RelationalEntityInformation<T, String> cast(EntityInformation<T, String> entityInformation) {
|
||||
return CastUtils.cast(entityInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存实体
|
||||
*
|
||||
* @param bean 实体
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public <T extends Domain> Mono<T> save(T bean) {
|
||||
if (StringUtils.isNotBlank(bean.getId())) {
|
||||
return r2dbcEntityOperations.update(bean);
|
||||
}
|
||||
return r2dbcEntityOperations.insert(bean);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.flyfish.framework.r2dbc.query;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.query.spi.QueryFactory;
|
||||
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
|
||||
import com.flyfish.framework.query.spi.converter.QueryConverter;
|
||||
import com.flyfish.framework.r2dbc.query.adaptor.R2dbcCriteriaAdaptor;
|
||||
import com.flyfish.framework.r2dbc.query.converter.R2dbcQueryConverter;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
/**
|
||||
* r2dbc查询工厂
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class R2dbcQueryFactory implements QueryFactory<Criteria, Query> {
|
||||
|
||||
private final CriteriaAdaptor<Criteria> adaptor = new R2dbcCriteriaAdaptor();
|
||||
|
||||
private final QueryConverter<Query> converter = new R2dbcQueryConverter(adaptor);
|
||||
|
||||
@Override
|
||||
public boolean supports(EntityInformation<? extends Domain, String> entity) {
|
||||
return entity instanceof RelationalEntityInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryConverter<Query> getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CriteriaAdaptor<Criteria> getAdaptor() {
|
||||
return adaptor;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user