diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/DynamicClassLoader.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/DynamicClassLoader.java index d60493a..48c9e1c 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/DynamicClassLoader.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/DynamicClassLoader.java @@ -21,4 +21,11 @@ public abstract class DynamicClassLoader extends URLClassLoader { * @return 结果 */ public abstract Map getClassBytes(); + + /** + * 将指定的类二进制替换为其他类 + * + * @param path 目标类路径 + */ + public abstract void replace(String name, String path); } diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java index 7fac8e1..00aa317 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java @@ -1,7 +1,13 @@ package com.flyfish.framework.compiler.core; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.StreamUtils; +import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +36,21 @@ class MemoryClassLoader extends DynamicClassLoader { return classBytes; } + @Override + public void replace(String name, String path) { + String actualPath = "classpath:" + path; + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources(actualPath); + if (ArrayUtils.isNotEmpty(resources)) { + byte[] content = StreamUtils.copyToByteArray(resources[0].getInputStream()); + classBytes.put(name, content); + defineClass(name, content); + } + } catch (IOException ignored) { + } + } + /** * 修复获取package为null的问题,定义并验证class * diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/R2dbcDataConfig.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/R2dbcDataConfig.java index cf2b897..7102445 100644 --- a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/R2dbcDataConfig.java +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/R2dbcDataConfig.java @@ -2,9 +2,9 @@ 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.config.convert.AdditionalConverters; -import com.flyfish.framework.r2dbc.config.convert.FullMappingR2dbcConverter; -import com.flyfish.framework.r2dbc.config.convert.FullReactiveDataAccessStrategy; +import com.flyfish.framework.r2dbc.config.mapping.AdditionalConverters; +import com.flyfish.framework.r2dbc.config.mapping.FullMappingR2dbcConverter; +import com.flyfish.framework.r2dbc.config.mapping.FullReactiveDataAccessStrategy; import com.flyfish.framework.r2dbc.metadata.R2dbcMetadataManager; import com.flyfish.framework.r2dbc.metadata.impl.SimpleR2dbcMetadataManager; import com.flyfish.framework.r2dbc.operations.R2dbcReactiveEntityOperations; diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullReactiveDataAccessStrategy.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullReactiveDataAccessStrategy.java deleted file mode 100644 index 98ce3cd..0000000 --- a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullReactiveDataAccessStrategy.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.flyfish.framework.r2dbc.config.convert; - -import com.flyfish.framework.utils.JacksonUtil; -import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; -import org.springframework.data.r2dbc.dialect.R2dbcDialect; -import org.springframework.data.r2dbc.mapping.OutboundRow; -import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; -import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.NonNull; -import org.springframework.r2dbc.core.Parameter; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -import java.util.Collection; - -/** - * 全支持的数据访问策略,将数组扁平化转换,方便支持json - * - * @author wangyu - */ -@SuppressWarnings("deprecation") -public class FullReactiveDataAccessStrategy extends DefaultReactiveDataAccessStrategy { - - private final R2dbcConverter converter; - - private final R2dbcDialect dialect; - - /** - * 最大化配置的构造器,仅提供该实现 - * - * @param dialect 别名实现 - * @param converter 转换器 - */ - public FullReactiveDataAccessStrategy(R2dbcDialect dialect, R2dbcConverter converter) { - super(dialect, converter); - this.converter = converter; - this.dialect = dialect; - } - - @Override - @NonNull - public OutboundRow getOutboundRow(@NonNull Object object) { - Assert.notNull(object, "Entity object must not be null"); - - OutboundRow row = new OutboundRow(); - - converter.write(object, row); - - RelationalPersistentEntity entity = getRequiredPersistentEntity(ClassUtils.getUserClass(object)); - - for (RelationalPersistentProperty property : entity) { - Parameter value = row.get(property.getColumnName()); - if (value != null && shouldConvertArrayValue(property, value)) { - Parameter writeValue = getArrayValue(value); - row.put(property.getColumnName(), writeValue); - } - } - - return row; - } - - private Parameter getArrayValue(Parameter value) { - if (value.getType().equals(byte[].class)) { - return value; - } - return Parameter.fromOrEmpty(JacksonUtil.toJson(value.getValue()).orElse(null), String.class); - } - - /** - * 判断是否需要转换数据值 - * - * @param property 属性 - * @param value 值表示 - * @return 结果 - */ - private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) { - - if (!property.isCollectionLike()) { - return false; - } - - if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) { - return true; - } - - if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) { - return true; - } - - return false; - } - - @SuppressWarnings("unchecked") - private RelationalPersistentEntity getRequiredPersistentEntity(Class typeToRead) { - return converter.getMappingContext().getRequiredPersistentEntity(typeToRead); - } - -} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/AdditionalConverters.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/AdditionalConverters.java similarity index 97% rename from flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/AdditionalConverters.java rename to flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/AdditionalConverters.java index 1af8605..6d23535 100644 --- a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/AdditionalConverters.java +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/AdditionalConverters.java @@ -1,4 +1,4 @@ -package com.flyfish.framework.r2dbc.config.convert; +package com.flyfish.framework.r2dbc.config.mapping; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.TypeFactory; diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullMappingR2dbcConverter.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullMappingR2dbcConverter.java similarity index 98% rename from flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullMappingR2dbcConverter.java rename to flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullMappingR2dbcConverter.java index f3fd29c..9075833 100644 --- a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/convert/FullMappingR2dbcConverter.java +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullMappingR2dbcConverter.java @@ -1,4 +1,4 @@ -package com.flyfish.framework.r2dbc.config.convert; +package com.flyfish.framework.r2dbc.config.mapping; import com.flyfish.framework.utils.JacksonUtil; import org.springframework.data.convert.CustomConversions; @@ -9,7 +9,6 @@ import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; import org.springframework.data.r2dbc.mapping.OutboundRow; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.util.TypeInformation; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullReactiveDataAccessStrategy.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullReactiveDataAccessStrategy.java new file mode 100644 index 0000000..f4d13db --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/config/mapping/FullReactiveDataAccessStrategy.java @@ -0,0 +1,180 @@ +package com.flyfish.framework.r2dbc.config.mapping; + +import com.flyfish.framework.utils.JacksonUtil; +import io.r2dbc.spi.Readable; +import io.r2dbc.spi.ReadableMetadata; +import io.r2dbc.spi.Row; +import io.r2dbc.spi.RowMetadata; +import org.springframework.data.r2dbc.convert.R2dbcConverter; +import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.core.PredefinedStatementMapper; +import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.core.StatementMapper; +import org.springframework.data.r2dbc.dialect.R2dbcDialect; +import org.springframework.data.r2dbc.mapping.OutboundRow; +import org.springframework.data.r2dbc.query.FullUpdateMapper; +import org.springframework.data.r2dbc.query.UpdateMapper; +import org.springframework.data.relational.core.dialect.RenderContextFactory; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.data.relational.domain.RowDocument; +import org.springframework.lang.NonNull; +import org.springframework.r2dbc.core.Parameter; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.util.Collection; +import java.util.List; +import java.util.function.BiFunction; + +/** + * 全支持的数据访问策略,将数组扁平化转换,方便支持json + * + * @author wangyu + */ +@SuppressWarnings("deprecation") +public class FullReactiveDataAccessStrategy implements ReactiveDataAccessStrategy { + + private final DefaultReactiveDataAccessStrategy delegated; + + private final R2dbcConverter converter; + + private final UpdateMapper updateMapper; + + private final StatementMapper statementMapper; + + /** + * 最大化配置的构造器,仅提供该实现 + * + * @param dialect 别名实现 + * @param converter 转换器 + */ + public FullReactiveDataAccessStrategy(R2dbcDialect dialect, R2dbcConverter converter) { + Assert.notNull(dialect, "Dialect must not be null"); + Assert.notNull(converter, "RelationalConverter must not be null"); + this.delegated = new DefaultReactiveDataAccessStrategy(dialect, converter); + + this.converter = converter; + this.updateMapper = new FullUpdateMapper(dialect, converter); + RenderContextFactory factory = new RenderContextFactory(dialect); + this.statementMapper = new PredefinedStatementMapper(dialect, factory.createRenderContext(), this.updateMapper, + delegated.getMappingContext()); + } + + @Override + @NonNull + public List getAllColumns(@NonNull Class entityType) { + return delegated.getAllColumns(entityType); + } + + @Override + @NonNull + public List getIdentifierColumns(@NonNull Class entityType) { + return delegated.getIdentifierColumns(entityType); + } + + @Override + @NonNull + public OutboundRow getOutboundRow(@NonNull Object object) { + Assert.notNull(object, "Entity object must not be null"); + + OutboundRow row = new OutboundRow(); + + converter.write(object, row); + + RelationalPersistentEntity entity = getRequiredPersistentEntity(ClassUtils.getUserClass(object)); + + for (RelationalPersistentProperty property : entity) { + Parameter value = row.get(property.getColumnName()); + if (value != null && shouldConvertArrayValue(property, value)) { + Parameter writeValue = getArrayValue(value); + row.put(property.getColumnName(), writeValue); + } + } + + return row; + } + + @Override + @NonNull + public Parameter getBindValue(@NonNull Parameter value) { + return this.updateMapper.getBindValue(value); + } + + @Override + @NonNull + public BiFunction getRowMapper(@NonNull Class typeToRead) { + return delegated.getRowMapper(typeToRead); + } + + @Override + @NonNull + public RowDocument toRowDocument(@NonNull Class type, @NonNull Readable row, @NonNull Iterable metadata) { + return delegated.toRowDocument(type, row, metadata); + } + + @Override + @NonNull + public SqlIdentifier getTableName(@NonNull Class type) { + return delegated.getTableName(type); + } + + @Override + @NonNull + public PreparedOperation processNamedParameters(@NonNull String query, @NonNull NamedParameterProvider parameterProvider) { + return delegated.processNamedParameters(query, parameterProvider); + } + + private Parameter getArrayValue(Parameter value) { + if (value.getType().equals(byte[].class)) { + return value; + } + return Parameter.fromOrEmpty(JacksonUtil.toJson(value.getValue()).orElse(null), String.class); + } + + @Override + @NonNull + public String toSql(@NonNull SqlIdentifier identifier) { + return this.updateMapper.toSql(identifier); + } + + @Override + @NonNull + public StatementMapper getStatementMapper() { + return this.statementMapper; + } + + @Override + @NonNull + public R2dbcConverter getConverter() { + return converter; + } + + /** + * 判断是否需要转换数据值 + * + * @param property 属性 + * @param value 值表示 + * @return 结果 + */ + private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) { + if (!property.isCollectionLike()) { + return false; + } + if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) { + return true; + } + if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) { + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + private RelationalPersistentEntity getRequiredPersistentEntity(Class typeToRead) { + return converter.getMappingContext().getRequiredPersistentEntity(typeToRead); + } + +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/FunctionCriteria.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/FunctionCriteria.java new file mode 100644 index 0000000..02208cb --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/FunctionCriteria.java @@ -0,0 +1,145 @@ +package com.flyfish.framework.r2dbc.query.adaptor; + +import lombok.Getter; +import org.springframework.data.relational.core.query.Criteria; +import org.springframework.data.relational.core.query.CriteriaDefinition; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 函数查询 + * + * @author wangyu + */ +@Getter +public class FunctionCriteria implements CriteriaDefinition { + + private final CriteriaDefinition previous; + + private final Combinator combinator; + + private final SqlIdentifier column; + + private final String functionName; + + private final List values; + + private FunctionCriteria(SqlIdentifier column, String functionName, Object... values) { + this(null, null, column, functionName, values); + } + + private FunctionCriteria(@Nullable CriteriaDefinition previous, @Nullable Combinator combinator, + SqlIdentifier column, String functionName, Object... values) { + this.previous = previous; + this.combinator = combinator; + this.column = column; + this.functionName = functionName; + this.values = Arrays.asList(values); + } + + public static FunctionStep where(String column) { + return new DefaultFunctionStep(column); + } + + @Override + public boolean isGroup() { + return false; + } + + @Override + @NonNull + public List getGroup() { + return Collections.emptyList(); + } + + @Override + public Comparator getComparator() { + return Comparator.INITIAL; + } + + @Override + public Object getValue() { + return values; + } + + @Override + public boolean isIgnoreCase() { + return false; + } + + @Override + public boolean hasPrevious() { + return null != previous; + } + + @Override + public boolean isEmpty() { + return false; + } + + public Criteria and() { + return Criteria.empty().and(this); + } + + public Criteria or() { + return Criteria.empty().or(this); + } + + public FunctionStep and(String column) { + return new DefaultFunctionStep(column, this, Combinator.AND); + } + + public FunctionStep or(String column) { + return new DefaultFunctionStep(column, this, Combinator.OR); + } + + /** + * 主要操作步骤,支持若干函数 + */ + public interface FunctionStep { + + /** + * json包含,mysql特有 + * + * @param value 值 + * @return 结果 + */ + FunctionCriteria jsonContains(Object value); + } + + /** + * 默认的查询链实现 + */ + private static class DefaultFunctionStep implements FunctionStep { + + private final CriteriaDefinition previous; + + private final SqlIdentifier column; + + private final Combinator combinator; + + private DefaultFunctionStep(String column) { + this(column, Criteria.empty(), Combinator.INITIAL); + } + + private DefaultFunctionStep(String column, CriteriaDefinition previous, Combinator combinator) { + this.column = SqlIdentifier.quoted(column); + this.previous = previous; + this.combinator = combinator; + } + + @Override + public FunctionCriteria jsonContains(Object value) { + return create(column, "JSON_CONTAINS", value); + } + + private FunctionCriteria create(SqlIdentifier column, String functionName, Object... values) { + return new FunctionCriteria(previous, combinator, column, functionName, values); + } + } +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/R2dbcCriteriaAdaptor.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/R2dbcCriteriaAdaptor.java index f993439..63a95bf 100644 --- a/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/R2dbcCriteriaAdaptor.java +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/com/flyfish/framework/r2dbc/query/adaptor/R2dbcCriteriaAdaptor.java @@ -4,14 +4,8 @@ import com.flyfish.framework.query.Queries; import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.relational.core.query.Criteria; -import org.springframework.data.relational.core.query.CriteriaDefinition; -import org.springframework.data.relational.core.sql.Expression; -import org.springframework.data.relational.core.sql.Functions; -import org.springframework.data.relational.core.sql.SimpleFunction; -import org.springframework.data.relational.core.sql.SqlIdentifier; import java.util.Collection; -import java.util.Collections; import java.util.List; /** @@ -43,12 +37,16 @@ public class R2dbcCriteriaAdaptor implements CriteriaAdaptor { @Override public Criteria hasId(String column, Object value) { - return Criteria.where(String.format("JSON_CONTAINS(%s, %s)", column, value)).isTrue(); + return FunctionCriteria.where(column) + .jsonContains(value) + .and(); } @Override public Criteria has(String column, Object value) { - return Criteria.where(String.format("JSON_CONTAINS(%s, %s)", column, value)).isTrue(); + return FunctionCriteria.where(column) + .jsonContains(value) + .and(); } @Override diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/PredefinedStatementMapper.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/PredefinedStatementMapper.java new file mode 100644 index 0000000..4c4c048 --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/PredefinedStatementMapper.java @@ -0,0 +1,21 @@ +package org.springframework.data.r2dbc.core; + +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.r2dbc.dialect.R2dbcDialect; +import org.springframework.data.r2dbc.query.UpdateMapper; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.sql.render.RenderContext; + +/** + * 创建适配类,暴露构造器方法 + * + * @author wangyu + */ +public class PredefinedStatementMapper extends DefaultStatementMapper { + + public PredefinedStatementMapper(R2dbcDialect dialect, RenderContext renderContext, UpdateMapper updateMapper, + MappingContext, ? extends RelationalPersistentProperty> mappingContext) { + super(dialect, renderContext, updateMapper, mappingContext); + } +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/FullUpdateMapper.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/FullUpdateMapper.java new file mode 100644 index 0000000..0a05029 --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/FullUpdateMapper.java @@ -0,0 +1,408 @@ +package org.springframework.data.r2dbc.query; + +import com.flyfish.framework.r2dbc.query.adaptor.FunctionCriteria; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.r2dbc.convert.R2dbcConverter; +import org.springframework.data.r2dbc.dialect.R2dbcDialect; +import org.springframework.data.relational.core.dialect.Escaper; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.query.CriteriaDefinition; +import org.springframework.data.relational.core.query.ValueFunction; +import org.springframework.data.relational.core.sql.*; +import org.springframework.data.util.Pair; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.Parameter; +import org.springframework.r2dbc.core.binding.BindMarker; +import org.springframework.r2dbc.core.binding.BindMarkers; +import org.springframework.r2dbc.core.binding.MutableBindings; +import org.springframework.util.Assert; + +import java.util.*; + +/** + * 全量支持的mapper + * + * @author wangyu + * 低级重写,后续官方支持后删除 + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class FullUpdateMapper extends UpdateMapper { + + private final R2dbcDialect dialect; + private final MappingContext, RelationalPersistentProperty> mappingContext; + + + public FullUpdateMapper(R2dbcDialect dialect, R2dbcConverter converter) { + super(dialect, converter); + this.dialect = dialect; + this.mappingContext = (MappingContext) converter.getMappingContext(); + } + + /** + * 重写此方法,以允许使用自定义函数 + * + * @param markers 绑定标记对象,必须非空 + * @param criteria 需映射的条件定义,必须非空 + * @param table 必须非空 + * @param entity 相关的 {@link RelationalPersistentEntity},可以为空 + * @return 结果 + */ + @Override + @NonNull + public BoundCondition getMappedObject(@NonNull BindMarkers markers, @NonNull CriteriaDefinition criteria, @NonNull Table table, + @Nullable RelationalPersistentEntity entity) { + Assert.notNull(markers, "BindMarkers must not be null"); + Assert.notNull(criteria, "CriteriaDefinition must not be null"); + Assert.notNull(table, "Table must not be null"); + + MutableBindings bindings = new MutableBindings(markers); + if (criteria.isEmpty()) { + throw new IllegalArgumentException("Cannot map empty Criteria"); + } + Condition mapped = unroll(criteria, table, entity, bindings); + return new BoundCondition(bindings, mapped); + } + + private Condition unroll(CriteriaDefinition criteria, Table table, @Nullable RelationalPersistentEntity entity, + MutableBindings bindings) { + + CriteriaDefinition current = criteria; + + // reverse unroll criteria chain + Map forwardChain = new HashMap<>(); + + while (current.hasPrevious()) { + forwardChain.put(current.getPrevious(), current); + current = current.getPrevious(); + } + + // perform the actual mapping + Condition mapped = getCondition(current, bindings, table, entity); + while (forwardChain.containsKey(current)) { + + CriteriaDefinition criterion = forwardChain.get(current); + Condition result = null; + + Condition condition = getCondition(criterion, bindings, table, entity); + if (condition != null) { + result = combine(criterion, mapped, criterion.getCombinator(), condition); + } + + if (result != null) { + mapped = result; + } + current = criterion; + } + + if (mapped == null) { + throw new IllegalStateException("Cannot map empty Criteria"); + } + + return mapped; + } + + @Nullable + private Condition unrollGroup(List criteria, Table table, + CriteriaDefinition.Combinator combinator, @Nullable RelationalPersistentEntity entity, + MutableBindings bindings) { + + Condition mapped = null; + for (CriteriaDefinition criterion : criteria) { + + if (criterion.isEmpty()) { + continue; + } + + Condition condition = unroll(criterion, table, entity, bindings); + + mapped = combine(criterion, mapped, combinator, condition); + } + + return mapped; + } + + + private Condition combine(CriteriaDefinition criteria, @Nullable Condition currentCondition, + CriteriaDefinition.Combinator combinator, Condition nextCondition) { + + if (currentCondition == null) { + currentCondition = nextCondition; + } else if (combinator == CriteriaDefinition.Combinator.INITIAL) { + currentCondition = currentCondition.and(Conditions.nest(nextCondition)); + } else if (combinator == CriteriaDefinition.Combinator.AND) { + currentCondition = currentCondition.and(nextCondition); + } else if (combinator == CriteriaDefinition.Combinator.OR) { + currentCondition = currentCondition.or(nextCondition); + } else { + throw new IllegalStateException("Combinator " + combinator + " not supported"); + } + + return currentCondition; + } + + @Nullable + private Condition getCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table, + @Nullable RelationalPersistentEntity entity) { + + if (criteria.isEmpty()) { + return null; + } + + if (criteria.isGroup()) { + + Condition condition = unrollGroup(criteria.getGroup(), table, criteria.getCombinator(), entity, bindings); + + return condition == null ? null : Conditions.nest(condition); + } + + return mapCondition(criteria, bindings, table, entity); + } + + + @Nullable + private Object convertValue(CriteriaDefinition.Comparator comparator, @Nullable Object value, TypeInformation typeHint) { + + if ((CriteriaDefinition.Comparator.IN.equals(comparator) || CriteriaDefinition.Comparator.NOT_IN.equals(comparator)) + && value instanceof Collection collection && !collection.isEmpty()) { + + Collection mapped = new ArrayList<>(collection.size()); + + for (Object o : collection) { + mapped.add(convertValue(o, typeHint)); + } + + return mapped; + } + + return convertValue(value, typeHint); + } + + /** + * 核心改造方法,增加对方法标识的适配,剔除额外的column属性 + * + * @param criteria 通用查询 + * @param bindings 绑定参数 + * @param table 表 + * @param entity 实体信息 + * @return 结果 + */ + private Condition mapCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table, + @Nullable RelationalPersistentEntity entity) { + Field propertyField = createPropertyField(entity, criteria.getColumn(), mappingContext); + Column column = table.column(propertyField.getMappedColumnName()); + TypeInformation actualType = propertyField.getTypeHint().getRequiredActualType(); + + Object mappedValue; + Class typeHint; + + CriteriaDefinition.Comparator comparator = criteria.getComparator(); + if (criteria.getValue() instanceof Parameter parameter) { + + mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint()); + typeHint = getTypeHint(mappedValue, actualType.getType(), parameter); + } else if (criteria.getValue() instanceof ValueFunction valueFunction) { + + mappedValue = valueFunction.map(v -> convertValue(comparator, v, propertyField.getTypeHint())) + .apply(getEscaper(comparator)); + + typeHint = actualType.getType(); + } else { + + mappedValue = convertValue(comparator, criteria.getValue(), propertyField.getTypeHint()); + typeHint = actualType.getType(); + } + + // 特殊处理方法 + if (criteria instanceof FunctionCriteria functionCriteria) { + return createFunction(column, mappedValue, typeHint, bindings, functionCriteria); + } + // 原处理方法 + return createCondition(column, mappedValue, typeHint, bindings, comparator, criteria.isIgnoreCase()); + } + + /** + * 创建函数引用 + * + * @param column 引用的列 + * @param mappedValue 映射后的值 + * @param valueType 值类型 + * @param bindings 绑定上下文 + * @param criteria 查询信息 + * @return 结果 + */ + private Condition createFunction(Column column, @Nullable Object mappedValue, Class valueType, + MutableBindings bindings, FunctionCriteria criteria) { + // 处理多值情况 + if (mappedValue instanceof Iterable) { + // 大小 + 1, 容纳列名 + List expressions = new ArrayList<>( + mappedValue instanceof Collection ? ((Collection) mappedValue).size() + 1 : 10); + expressions.add(column); + + for (Object o : (Iterable) mappedValue) { + + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + expressions.add(bind(o, valueType, bindings, bindMarker)); + } + return FunctionCondition.of(criteria.getFunctionName(), expressions); + } else { + // 单值绑定 + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + + return FunctionCondition.of(criteria.getFunctionName(), column, expression); + } + } + + private Condition createCondition(Column column, @Nullable Object mappedValue, Class valueType, + MutableBindings bindings, CriteriaDefinition.Comparator comparator, boolean ignoreCase) { + + if (comparator.equals(CriteriaDefinition.Comparator.IS_NULL)) { + return column.isNull(); + } + + if (comparator.equals(CriteriaDefinition.Comparator.IS_NOT_NULL)) { + return column.isNotNull(); + } + + if (comparator == CriteriaDefinition.Comparator.IS_TRUE) { + Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase); + + return column.isEqualTo(bind); + } + + if (comparator == CriteriaDefinition.Comparator.IS_FALSE) { + Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase); + + return column.isEqualTo(bind); + } + + Expression columnExpression = column; + if (ignoreCase) { + columnExpression = Functions.upper(column); + } + + if (comparator == CriteriaDefinition.Comparator.NOT_IN || comparator == CriteriaDefinition.Comparator.IN) { + + Condition condition; + + if (mappedValue instanceof Iterable) { + + List expressions = new ArrayList<>( + mappedValue instanceof Collection ? ((Collection) mappedValue).size() : 10); + + for (Object o : (Iterable) mappedValue) { + + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + expressions.add(bind(o, valueType, bindings, bindMarker)); + } + + condition = Conditions.in(columnExpression, expressions.toArray(new Expression[0])); + + } else { + + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + + condition = Conditions.in(columnExpression, expression); + } + + if (comparator == CriteriaDefinition.Comparator.NOT_IN) { + condition = condition.not(); + } + + return condition; + } + + if (comparator == CriteriaDefinition.Comparator.BETWEEN || comparator == CriteriaDefinition.Comparator.NOT_BETWEEN) { + + Pair pair = (Pair) mappedValue; + + Expression begin = bind(pair.getFirst(), valueType, bindings, + bindings.nextMarker(column.getName().getReference()), ignoreCase); + Expression end = bind(pair.getSecond(), valueType, bindings, bindings.nextMarker(column.getName().getReference()), + ignoreCase); + + return comparator == CriteriaDefinition.Comparator.BETWEEN ? Conditions.between(columnExpression, begin, end) + : Conditions.notBetween(columnExpression, begin, end); + } + + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + + switch (comparator) { + case EQ: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); + return Conditions.isEqual(columnExpression, expression); + } + case NEQ: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); + return Conditions.isEqual(columnExpression, expression).not(); + } + case LT: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + return column.isLess(expression); + } + case LTE: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + return column.isLessOrEqualTo(expression); + } + case GT: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + return column.isGreater(expression); + } + case GTE: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker); + return column.isGreaterOrEqualTo(expression); + } + case LIKE: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); + return Conditions.like(columnExpression, expression); + } + case NOT_LIKE: { + Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); + return Conditions.notLike(columnExpression, expression); + } + default: + throw new UnsupportedOperationException("Comparator " + comparator + " not supported"); + } + } + + private Expression booleanBind(Column column, Object mappedValue, Class valueType, MutableBindings bindings, + boolean ignoreCase) { + BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); + + return bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); + } + + private Expression bind(@Nullable Object mappedValue, Class valueType, MutableBindings bindings, + BindMarker bindMarker) { + return bind(mappedValue, valueType, bindings, bindMarker, false); + } + + private Expression bind(@Nullable Object mappedValue, Class valueType, MutableBindings bindings, + BindMarker bindMarker, boolean ignoreCase) { + + if (mappedValue != null) { + bindings.bind(bindMarker, mappedValue); + } else { + bindings.bindNull(bindMarker, valueType); + } + + return ignoreCase ? Functions.upper(SQL.bindMarker(bindMarker.getPlaceholder())) + : SQL.bindMarker(bindMarker.getPlaceholder()); + } + + + private Escaper getEscaper(CriteriaDefinition.Comparator comparator) { + + if (comparator == CriteriaDefinition.Comparator.LIKE || comparator == CriteriaDefinition.Comparator.NOT_LIKE) { + return dialect.getLikeEscaper(); + } + + return Escaper.DEFAULT; + } + +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/FunctionCondition.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/FunctionCondition.java new file mode 100644 index 0000000..070bb38 --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/FunctionCondition.java @@ -0,0 +1,35 @@ +package org.springframework.data.relational.core.sql; + +import org.springframework.data.relational.core.sql.render.FunctionVisitor; + +import java.util.Arrays; +import java.util.List; + +/** + * 可以渲染函数的条件 + * + * @author wangyu + */ +public class FunctionCondition extends ConstantCondition implements Condition { + + private final SimpleFunction function; + + private FunctionCondition(SimpleFunction function) { + super(""); + this.function = function; + } + + public static Condition of(String functionName, List args) { + return new FunctionCondition(SimpleFunction.create(functionName, args)); + } + + public static Condition of(String functionName, Expression... args) { + return of(functionName, Arrays.asList(args)); + } + + @Override + public String toString() { + FunctionVisitor visitor = new FunctionVisitor(); + return function.toString(); + } +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java new file mode 100644 index 0000000..6bd35e0 --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019-2024 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 org.springframework.data.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.*; +import org.springframework.lang.Nullable; + +/** + * {@link org.springframework.data.relational.core.sql.Visitor} delegating {@link Condition} rendering to condition + * {@link org.springframework.data.relational.core.sql.Visitor}s. + * + * @author Mark Paluch + * @author Jens Schauder + * @author Daniele Canteri + * @see AndCondition + * @see OrCondition + * @see IsNull + * @see Comparison + * @see Like + * @see In + * @since 1.1 + */ +class ConditionVisitor extends TypedSubtreeVisitor implements PartRenderer { + + private final RenderContext context; + private final StringBuilder builder = new StringBuilder(); + + ConditionVisitor(RenderContext context) { + this.context = context; + } + + @Override + Delegation enterMatched(Condition segment) { + + DelegatingVisitor visitor = getDelegation(segment); + + return visitor != null ? Delegation.delegateTo(visitor) : Delegation.retain(); + } + + @Nullable + private DelegatingVisitor getDelegation(Condition segment) { + + if (segment instanceof AndCondition) { + return new MultiConcatConditionVisitor(context, (AndCondition) segment, builder::append); + } + + if (segment instanceof OrCondition) { + return new MultiConcatConditionVisitor(context, (OrCondition) segment, builder::append); + } + + if (segment instanceof IsNull) { + return new IsNullVisitor(context, builder::append); + } + + if (segment instanceof Between) { + return new BetweenVisitor((Between) segment, context, builder::append); + } + + if (segment instanceof Comparison) { + return new ComparisonVisitor(context, (Comparison) segment, builder::append); + } + + if (segment instanceof Like) { + return new LikeVisitor((Like) segment, context, builder::append); + } + + if (segment instanceof In) { + + if (((In) segment).hasExpressions()) { + return new InVisitor(context, builder::append); + } else { + return new EmptyInVisitor(context, builder::append); + } + } + + if (segment instanceof NestedCondition) { + return new NestedConditionVisitor(context, builder::append); + } + + if (segment instanceof ConstantCondition) { + return new ConstantConditionVisitor(context, builder::append); + } + + if (segment instanceof Not) { + return new NotConditionVisitor(context, builder::append); + } + + if (segment instanceof FunctionCondition functionCondition) { + return new FunctionVisitor(functionCondition, context, builder::append); + } + + return null; + } + + @Override + public CharSequence getRenderedPart() { + return builder; + } +} diff --git a/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/FunctionVisitor.java b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/FunctionVisitor.java new file mode 100644 index 0000000..cf42b37 --- /dev/null +++ b/flyfish-data/flyfish-data-r2dbc/src/main/java/org/springframework/data/relational/core/sql/render/FunctionVisitor.java @@ -0,0 +1,45 @@ +package org.springframework.data.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.FunctionCondition; +import org.springframework.data.relational.core.sql.SimpleFunction; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.lang.Nullable; + +public class FunctionVisitor extends FilteredSubtreeVisitor { + + private final RenderContext context; + private final RenderTarget target; + private @Nullable PartRenderer current; + private final StringBuilder part = new StringBuilder(); + + public FunctionVisitor(FunctionCondition condition, RenderContext context, RenderTarget target) { + super(it -> it == condition); + this.context = context; + this.target = target; + } + + @Override + Delegation enterNested(Visitable segment) { + if (segment instanceof SimpleFunction) { + SimpleFunctionVisitor visitor = new SimpleFunctionVisitor(context); + current = visitor; + return Delegation.delegateTo(visitor); + } + throw new IllegalStateException("Cannot provide visitor for " + segment); + } + + @Override + Delegation leaveNested(Visitable segment) { + if (current != null) { + part.append(current.getRenderedPart()); + current = null; + } + return super.leaveNested(segment); + } + + @Override + Delegation leaveMatched(Visitable segment) { + target.onRendered(part); + return super.leaveMatched(segment); + } +}