mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-09 17:34:50 +08:00
fix: 修复使用NativeSql更新列时,可能错误以及无法获取更新后的值的问题。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user