fix: 修复使用NativeSql更新列时,可能错误以及无法获取更新后的值的问题。

This commit is contained in:
zhouhao
2023-06-20 19:37:02 +08:00
parent a0ceb24867
commit cb5a2cb7c5
6 changed files with 267 additions and 8 deletions

View File

@@ -24,6 +24,7 @@ import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
import org.hswebframework.web.crud.entity.factory.EntityMappingCustomizer;
import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
import org.hswebframework.web.crud.events.*;
import org.hswebframework.web.crud.events.expr.SpelSqlExpressionInvoker;
import org.hswebframework.web.crud.generator.CurrentTimeGenerator;
import org.hswebframework.web.crud.generator.DefaultIdGenerator;
import org.hswebframework.web.crud.generator.MD5Generator;
@@ -136,10 +137,14 @@ public class EasyormConfiguration {
@Bean
public EntityEventListener entityEventListener(ApplicationEventPublisher eventPublisher,
ObjectProvider<SqlExpressionInvoker> invokers,
ObjectProvider<EntityEventListenerCustomizer> customizers) {
DefaultEntityEventListenerConfigure configure = new DefaultEntityEventListenerConfigure();
customizers.forEach(customizer -> customizer.customize(configure));
return new EntityEventListener(eventPublisher, configure);
EntityEventListener entityEventListener = new EntityEventListener(eventPublisher, configure);
entityEventListener.setExpressionInvoker(invokers.getIfAvailable(SpelSqlExpressionInvoker::new));
return entityEventListener;
}
@Bean

View File

@@ -2,20 +2,19 @@ package org.hswebframework.web.crud.events;
import lombok.AllArgsConstructor;
import org.apache.commons.beanutils.BeanUtilsBean;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.core.GlobalConfig;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.ezorm.rdb.events.*;
import org.hswebframework.ezorm.rdb.events.EventListener;
import org.hswebframework.ezorm.rdb.events.EventType;
import org.hswebframework.ezorm.rdb.events.*;
import org.hswebframework.ezorm.rdb.executor.NullValue;
import org.hswebframework.ezorm.rdb.mapping.*;
import org.hswebframework.ezorm.rdb.mapping.events.MappingContextKeys;
import org.hswebframework.ezorm.rdb.mapping.events.MappingEventTypes;
import org.hswebframework.ezorm.rdb.mapping.events.ReactiveResultHolder;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.TableOrViewMetadata;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
import org.hswebframework.web.api.crud.entity.Entity;
import org.hswebframework.web.bean.FastBeanCopier;
@@ -33,10 +32,11 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import static org.hswebframework.web.crud.events.EntityEventHelper.*;
import static org.hswebframework.web.crud.events.EntityEventHelper.isDoFireEvent;
import static org.hswebframework.web.crud.events.EntityEventHelper.publishEvent;
@SuppressWarnings("all")
@AllArgsConstructor
@RequiredArgsConstructor
public class EntityEventListener implements EventListener, Ordered {
public static final ContextKey<List<Object>> readyToDeleteContextKey = ContextKey.of("readyToDelete");
@@ -46,6 +46,9 @@ public class EntityEventListener implements EventListener, Ordered {
private final EntityEventListenerConfigure listenerConfigure;
@Setter
private SqlExpressionInvoker expressionInvoker;
@Override
public String getId() {
return "entity-listener";
@@ -147,6 +150,7 @@ public class EntityEventListener implements EventListener, Ordered {
.orElse(Collections.emptyMap());
for (Object old : olds) {
Map<String, Object> oldMap = null;
Object data = FastBeanCopier.copy(old, mapping.newInstance());
for (Map.Entry<String, Object> entry : columns.entrySet()) {
@@ -161,6 +165,16 @@ public class EntityEventListener implements EventListener, Ordered {
if (value instanceof NullValue) {
value = null;
}
//原生sql
if (value instanceof NativeSql) {
value = expressionInvoker == null ? null : expressionInvoker.invoke(
((NativeSql) value),
mapping,
oldMap == null ? oldMap = createFullMapping(old, mapping) : oldMap);
if (value == null) {
continue;
}
}
GlobalConfig
.getPropertyOperator()
@@ -172,6 +186,18 @@ public class EntityEventListener implements EventListener, Ordered {
return newValues;
}
protected Map<String, Object> createFullMapping(Object old, EntityColumnMapping mapping) {
Map<String, Object> map = FastBeanCopier.copy(old, new HashMap<>());
for (RDBColumnMetadata column : mapping.getTable().getColumns()) {
if (map.containsKey(column.getAlias())) {
map.put(column.getName(), map.get(column.getAlias()));
}
}
return map;
}
protected Mono<Void> sendUpdateEvent(List<Object> before,
List<Object> after,
Class<Object> type,
@@ -300,7 +326,7 @@ public class EntityEventListener implements EventListener, Ordered {
.setParam(dslUpdate.toQueryParam())
.fetch()
.collectList()
.doOnNext(list->{
.doOnNext(list -> {
context.set(readyToDeleteContextKey, list);
})
.filter(CollectionUtils::isNotEmpty)

View File

@@ -0,0 +1,12 @@
package org.hswebframework.web.crud.events;
import org.hswebframework.ezorm.rdb.mapping.EntityColumnMapping;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
import java.util.Map;
public interface SqlExpressionInvoker {
Object invoke(NativeSql sql, EntityColumnMapping mapping, Map<String,Object> object);
}

View File

@@ -0,0 +1,25 @@
package org.hswebframework.web.crud.events.expr;
import org.hswebframework.ezorm.rdb.mapping.EntityColumnMapping;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
import org.hswebframework.web.crud.events.SqlExpressionInvoker;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
public abstract class AbstractSqlExpressionInvoker implements SqlExpressionInvoker {
private final Map<String, BiFunction<Object[], Map<String, Object>, Object>> compiled =
new ConcurrentHashMap<>();
@Override
public Object invoke(NativeSql sql, EntityColumnMapping mapping, Map<String, Object> object) {
return compiled.computeIfAbsent(sql.getSql(), this::compile)
.apply(sql.getParameters(), object);
}
protected abstract BiFunction<Object[], Map<String, Object>, Object> compile(String sql);
}

View File

@@ -0,0 +1,153 @@
package org.hswebframework.web.crud.events.expr;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
@Slf4j
public class SpelSqlExpressionInvoker extends AbstractSqlExpressionInvoker {
static class SqlFunctions extends HashMap<String, Object> {
public SqlFunctions(Map<String, Object> map) {
super(map);
}
public String lower(Object str) {
return String.valueOf(str).toLowerCase();
}
public String upper(Object str) {
return String.valueOf(str).toUpperCase();
}
public String substring(Object str, int start, int length) {
return String.valueOf(str).substring(start, length);
}
public String trim(Object str) {
return String.valueOf(str).trim();
}
public String concat(Object... args) {
StringBuilder builder = new StringBuilder();
for (Object arg : args) {
builder.append(arg);
}
return builder.toString();
}
public Object coalesce(Object... args) {
for (Object arg : args) {
if (arg != null) {
return arg;
}
}
return null;
}
}
static final FastThreadLocal<StandardEvaluationContext> SHARED_CONTEXT = new FastThreadLocal<StandardEvaluationContext>() {
@Override
protected StandardEvaluationContext initialValue() {
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(accessor);
context.addMethodResolver(new ReflectiveMethodResolver() {
@Override
public MethodExecutor resolve(@Nonnull EvaluationContext context,
@Nonnull Object targetObject,
@Nonnull String name,
@Nonnull List<TypeDescriptor> argumentTypes) throws AccessException {
return super.resolve(context, targetObject, name.toLowerCase(), argumentTypes);
}
});
return context;
}
};
@Override
protected BiFunction<Object[], Map<String, Object>, Object> compile(String sql) {
StringBuilder builder = new StringBuilder(sql.length());
int argIndex = 0;
for (int i = 0; i < sql.length(); i++) {
char c = sql.charAt(i);
if (c == '?') {
builder.append("_arg").append(argIndex++);
} else {
builder.append(c);
}
}
try {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(builder.toString());
AtomicLong errorCount = new AtomicLong();
return (args, object) -> {
if (errorCount.get() > 1024) {
return null;
}
object = new SqlFunctions(object);
if (args != null && args.length != 0) {
int index = 0;
for (Object parameter : args) {
object.put("_arg" + index, parameter);
}
}
StandardEvaluationContext context = SHARED_CONTEXT.get();
try {
context.setRootObject(object);
Object val = expression.getValue(context);
errorCount.set(0);
return val;
} catch (Throwable err) {
log.warn("invoke native sql [{}] value error",
sql,
err);
errorCount.incrementAndGet();
} finally {
context.setRootObject(null);
}
return null;
};
} catch (Throwable error) {
log.warn("create sql expression [{}] parser error", sql, error);
return (args, data) -> null;
}
}
static ExtMapAccessor accessor = new ExtMapAccessor();
static class ExtMapAccessor extends MapAccessor {
@Override
public boolean canRead(@Nonnull EvaluationContext context, Object target,@Nonnull String name) throws AccessException {
return target instanceof Map;
}
@Override
@Nonnull
public TypedValue read(@Nonnull EvaluationContext context, Object target, @Nonnull String name) throws AccessException {
Assert.state(target instanceof Map, "Target must be of type Map");
Map<?, ?> map = (Map<?, ?>) target;
Object value = map.get(name);
return new TypedValue(value);
}
}
}

View File

@@ -0,0 +1,38 @@
package org.hswebframework.web.crud.events.expr;
import org.hswebframework.ezorm.rdb.mapping.EntityColumnMapping;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.NativeSql;
import org.junit.jupiter.api.Test;
import reactor.function.Function3;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.*;
class SpelSqlExpressionInvokerTest {
@Test
void test() {
SpelSqlExpressionInvoker invoker = new SpelSqlExpressionInvoker();
BiFunction<Object[], Map<String, Object>, Object> func = invoker.compile("name + 1 + ?");
assertEquals(13, func.apply(new Object[]{2}, Collections.singletonMap("name", 10)));
}
@Test
void testFunction() {
SpelSqlExpressionInvoker invoker = new SpelSqlExpressionInvoker();
BiFunction<Object[], Map<String, Object>, Object> func = invoker.compile("coalesce(name,?)");
assertEquals(2, func.apply(new Object[]{2}, Collections.emptyMap()));
assertEquals(3, func.apply(null, Collections.singletonMap("name",3)));
}
}