Woody 2 kuukautta sitten
vanhempi
commit
88b9292cff
16 muutettua tiedostoa jossa 698 lisäystä ja 431 poistoa
  1. 6 1
      framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java
  2. 1 1
      framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java
  3. 2 1
      framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java
  4. 1 1
      framework-database/src/main/java/com/chelvc/framework/database/config/MybatisConfigurer.java
  5. 2 2
      framework-database/src/main/java/com/chelvc/framework/database/config/TypeHandlerConfigurer.java
  6. 97 0
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/CustomizeMybatisInterceptor.java
  7. 1 2
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/DeletedIsolateInterceptor.java
  8. 22 13
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicInvokeInterceptor.java
  9. 1 2
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/EnvIsolateInterceptor.java
  10. 111 66
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/Expressions.java
  11. 269 0
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/Tables.java
  12. 0 192
      framework-database/src/main/java/com/chelvc/framework/database/support/MyBatisUtils.java
  13. 0 53
      framework-database/src/main/java/com/chelvc/framework/database/support/Table.java
  14. 0 89
      framework-database/src/main/java/com/chelvc/framework/database/support/TableField.java
  15. 27 8
      framework-database/src/main/java/com/chelvc/framework/database/support/Updates.java
  16. 158 0
      framework-database/src/main/java/com/chelvc/framework/database/util/SQLUtils.java

+ 6 - 1
framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java

@@ -212,7 +212,12 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      * @return true/false
      */
     public static boolean isProduction() {
-        return getProperty("environment.production", boolean.class, false);
+        Boolean bool = getProperty("environment.production", Boolean.class);
+        if (bool == null) {
+            ApplicationContext applicationContext = getApplicationContext(false);
+            return applicationContext != null && Objects.equals(getProfile(applicationContext), "prod");
+        }
+        return bool;
     }
 
     /**

+ 1 - 1
framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java

@@ -509,7 +509,7 @@ public final class ObjectUtils {
             do {
                 Field[] fields = clazz.getDeclaredFields();
                 if (notEmpty(fields)) {
-                    boolean enumerable = Enum.class.isAssignableFrom(clazz);
+                    boolean enumerable = clazz.isEnum();
                     for (Field field : fields) {
                         if (!field.isSynthetic() && ((enumerable && field.isEnumConstant())
                                 || (!enumerable && !Modifier.isStatic(field.getModifiers())))) {

+ 2 - 1
framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java

@@ -27,6 +27,7 @@ import com.chelvc.framework.database.handler.ModificationTypeHandler;
 import com.chelvc.framework.database.handler.PeriodTypeHandler;
 import com.chelvc.framework.database.handler.RangeTypeHandler;
 import com.chelvc.framework.database.handler.RegionTypeHandler;
+import com.chelvc.framework.database.interceptor.CustomizeMybatisInterceptor;
 import com.chelvc.framework.database.interceptor.DeletedIsolateInterceptor;
 import com.chelvc.framework.database.interceptor.EnvIsolateInterceptor;
 import com.chelvc.framework.database.support.EnhanceSqlInjector;
@@ -114,7 +115,7 @@ public class DatabaseConfigurer {
 
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor(ApplicationContext applicationContext) {
-        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        MybatisPlusInterceptor interceptor = new CustomizeMybatisInterceptor(applicationContext);
 
         // 设置自定义租户隔离拦截器
         ApplicationContextHolder.getSortBeans(applicationContext, InnerInterceptor.class)

+ 1 - 1
framework-database/src/main/java/com/chelvc/framework/database/config/MybatisConfigurer.java

@@ -97,7 +97,7 @@ public abstract class MybatisConfigurer {
                 || type == LocalDate.class || type == LocalTime.class || type == LocalDateTime.class
                 || type == OffsetTime.class || type == OffsetDateTime.class || type == ZonedDateTime.class
                 || type == Year.class || type == Month.class || type == YearMonth.class || type == JapaneseDate.class
-                || (type != null && Enum.class.isAssignableFrom(type)) || type == File.class || type == Period.class
+                || (type != null && type.isEnum()) || type == File.class || type == Period.class
                 || type == Region.class || type == Modification.class;
     }
 

+ 2 - 2
framework-database/src/main/java/com/chelvc/framework/database/config/TypeHandlerConfigurer.java

@@ -80,7 +80,7 @@ import com.chelvc.framework.database.handler.StringsTypeHandler;
 import com.chelvc.framework.database.sql.CallableStringDecrypter;
 import com.chelvc.framework.database.sql.ResultStringDecrypter;
 import com.chelvc.framework.database.sql.WriteStringEncryptor;
-import com.chelvc.framework.database.support.MyBatisUtils;
+import com.chelvc.framework.database.util.SQLUtils;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import javassist.CannotCompileException;
@@ -491,7 +491,7 @@ public class TypeHandlerConfigurer extends MybatisConfigurer {
         aliases.add(StringUtils.hump2underscore(property));
         aliases.add(StringUtils.underscore2hump(property));
         String column = ObjectUtils.ifNull(field.getAnnotation(TableField.class), TableField::value);
-        if (StringUtils.notEmpty(column = MyBatisUtils.unquote(column))) {
+        if (StringUtils.notEmpty(column = SQLUtils.unquote(column))) {
             aliases.add(column);
             aliases.add(StringUtils.hump2underscore(column));
             aliases.add(StringUtils.underscore2hump(column));

+ 97 - 0
framework-database/src/main/java/com/chelvc/framework/database/interceptor/CustomizeMybatisInterceptor.java

@@ -0,0 +1,97 @@
+package com.chelvc.framework.database.interceptor;
+
+import java.sql.Connection;
+import java.util.Map;
+import javax.sql.DataSource;
+
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.chelvc.framework.database.util.SQLUtils;
+import com.google.common.collect.Maps;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.NonNull;
+import org.apache.ibatis.cache.CacheKey;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * 自定义Mybatis拦截器
+ *
+ * @author Woody
+ * @date 2025/3/16
+ */
+@Intercepts({
+        @Signature(type = StatementHandler.class, method = "getBoundSql", args = {}),
+        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
+        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
+        @Signature(
+                type = Executor.class, method = "query",
+                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
+        ),
+        @Signature(
+                type = Executor.class, method = "query",
+                args = {
+                        MappedStatement.class, Object.class, RowBounds.class,
+                        ResultHandler.class, CacheKey.class, BoundSql.class
+                }
+        )
+})
+public class CustomizeMybatisInterceptor extends MybatisPlusInterceptor {
+    private final boolean dynamicDatasource;
+    private final Map<String, String> schemas;
+
+    public CustomizeMybatisInterceptor(@NonNull ApplicationContext applicationContext) {
+        DataSource dataSource = applicationContext.getBean(DataSource.class);
+        if (this.dynamicDatasource = dataSource instanceof DynamicRoutingDataSource) {
+            DynamicDataSourceProperties properties = applicationContext.getBean(DynamicDataSourceProperties.class);
+            Map<String, DataSourceProperty> configs = properties.getDatasource();
+            this.schemas = Maps.newHashMapWithExpectedSize(configs.size());
+            configs.forEach((key, config) -> this.schemas.put(key, SQLUtils.getSchema(config.getUrl())));
+        } else {
+            this.schemas = Maps.newHashMapWithExpectedSize(1);
+            if (dataSource instanceof HikariDataSource) {
+                this.schemas.put(null, SQLUtils.getSchema(((HikariDataSource) dataSource).getJdbcUrl()));
+            } else {
+                throw new RuntimeException("Unknown datasource: " + dataSource);
+            }
+        }
+    }
+
+    /**
+     * 获取方法调用上下文
+     *
+     * @param invocation 调用信息
+     * @return 上下文实例
+     */
+    private Tables.Context getContext(Invocation invocation) {
+        Object[] args = invocation.getArgs();
+        String id = ((MappedStatement) args[0]).getId();
+        String namespace = id.substring(0, id.lastIndexOf('.'));
+        String schema = this.schemas.get(this.dynamicDatasource ? DynamicDataSourceContextHolder.peek() : null);
+        return new Tables.Context(schema, namespace);
+    }
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        if (invocation.getTarget() instanceof Executor) {
+            Tables.setContext(this.getContext(invocation));
+            try {
+                return super.intercept(invocation);
+            } finally {
+                Tables.setContext(null);
+            }
+        }
+        return super.intercept(invocation);
+    }
+}

+ 1 - 2
framework-database/src/main/java/com/chelvc/framework/database/interceptor/DeletedIsolateInterceptor.java

@@ -2,7 +2,6 @@ package com.chelvc.framework.database.interceptor;
 
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import com.chelvc.framework.database.entity.Deletable;
-import com.chelvc.framework.database.support.MyBatisUtils;
 import net.sf.jsqlparser.expression.Expression;
 
 /**
@@ -24,7 +23,7 @@ public class DeletedIsolateInterceptor implements TenantLineHandler {
 
     @Override
     public boolean ignoreTable(String table) {
-        Class<?> model = MyBatisUtils.getTableModel(table);
+        Class<?> model = Tables.getTableClass(table);
         return model == null || !Deletable.class.isAssignableFrom(model);
     }
 }

+ 22 - 13
framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicInvokeInterceptor.java

@@ -13,14 +13,14 @@ import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.database.config.DatabaseProperties;
 import com.chelvc.framework.database.context.DatabaseContextHolder;
 import com.chelvc.framework.database.entity.Creatable;
 import com.chelvc.framework.database.entity.Deletable;
 import com.chelvc.framework.database.entity.Environmental;
 import com.chelvc.framework.database.entity.Updatable;
-import com.chelvc.framework.database.support.MyBatisUtils;
-import com.chelvc.framework.database.support.TableField;
+import com.chelvc.framework.database.util.SQLUtils;
 import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import net.sf.jsqlparser.JSQLParserException;
@@ -632,8 +632,8 @@ public class DynamicInvokeInterceptor implements Interceptor {
      * @param parameter 参数表达式
      */
     private void bindTypeHandler(BoundSql bound, Table table, Column column, JdbcParameter parameter) {
-        TableField field = MyBatisUtils.getTableField(table.getName(), column.getColumnName());
-        TypeHandler<?> handler = ObjectUtils.ifNull(field, TableField::getHandler);
+        Tables.Field field = Tables.getTableField(table.getName(), column.getColumnName());
+        TypeHandler<?> handler = ObjectUtils.ifNull(field, Tables.Field::getHandler);
         if (handler != null) {
             ParameterMapping mapping = bound.getParameterMappings().get(parameter.getIndex() - 1);
             SystemMetaObject.forObject(mapping).setValue("typeHandler", handler);
@@ -789,7 +789,7 @@ public class DynamicInvokeInterceptor implements Interceptor {
      */
     private boolean initializeDefaultValue(BoundSql bound, Update update, Table table, Pair<?, Expression> operator,
                                            Pair<?, Expression> datetime) {
-        Class<?> model = MyBatisUtils.getTableModel(table.getName());
+        Class<?> model = Tables.getTableClass(table.getName());
         if (model == null) {
             return false;
         }
@@ -842,7 +842,7 @@ public class DynamicInvokeInterceptor implements Interceptor {
         }
 
         // 获取字段上下文信息
-        TableField field = MyBatisUtils.getTableField(table.getName(), column.getColumnName());
+        Tables.Field field = Tables.getTableField(table.getName(), column.getColumnName());
         if (field == null || !field.isSensitive()) {
             return false;
         }
@@ -857,7 +857,7 @@ public class DynamicInvokeInterceptor implements Interceptor {
         if (!this.properties.getCrypto().isWritable()) {
             value = DatabaseContextHolder.getDatabaseCipherHandler().encrypt((String) value, true);
         }
-        StringValue extra = new StringValue(MyBatisUtils.escape((String) value));
+        StringValue extra = new StringValue(SQLUtils.escape((String) value));
         if (condition instanceof InExpression) {
             InExpression in = (InExpression) condition;
             ((ExpressionList) in.getRightItemsList()).getExpressions().add(extra);
@@ -932,7 +932,7 @@ public class DynamicInvokeInterceptor implements Interceptor {
         }
 
         // 初始化参数默认值
-        Class<?> model = MyBatisUtils.getTableModel(insert.getTable().getName());
+        Class<?> model = Tables.getTableClass(insert.getTable().getName());
         if (model == null) {
             return rebuild;
         }
@@ -1048,15 +1048,24 @@ public class DynamicInvokeInterceptor implements Interceptor {
         boolean rebuild = body != null && this.prepare(bound, body, body);
 
         // 预处理查询条件
-        Map<String, Map<String, ExpressionList>> additional = Expressions.getSelectAdditionalConditions();
-        if (ObjectUtils.notEmpty(additional)) {
+        Map<String, Expressions.Provider> providers = Expressions.getExpressionProviders();
+        if (ObjectUtils.notEmpty(providers)) {
             rebuild |= this.prepareSelectCondition(select, (table, condition, changing) -> {
-                Map<String, ExpressionList> conditions = additional.get(MyBatisUtils.unquote(table.getName()));
-                if (ObjectUtils.isEmpty(conditions)) {
+                Tables.Table tb = Tables.getTable(table.getName());
+                if (tb == null) {
                     return false;
                 }
+
+                String unquote = SQLUtils.unquote(table.getName());
+                String schema = ObjectUtils.ifNull(Tables.getContext(), Tables.Context::getSchema);
+                String key = StringUtils.isEmpty(schema) ? unquote : (schema + StringPool.DOT + unquote);
+                Map<String, ItemsList> expressions = ObjectUtils.ifNull(providers.get(key), p -> p.get(tb));
+                if (ObjectUtils.isEmpty(expressions)) {
+                    return false;
+                }
+
                 Alias alias = table.getAlias();
-                for (Map.Entry<String, ExpressionList> entry : conditions.entrySet()) {
+                for (Map.Entry<String, ItemsList> entry : expressions.entrySet()) {
                     Column column = alias == null ? new Column(entry.getKey()) : new Column(table, entry.getKey());
                     InExpression in = new InExpression(column, entry.getValue());
                     IsNullExpression isnull = new IsNullExpression().withLeftExpression(column);

+ 1 - 2
framework-database/src/main/java/com/chelvc/framework/database/interceptor/EnvIsolateInterceptor.java

@@ -2,7 +2,6 @@ package com.chelvc.framework.database.interceptor;
 
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import com.chelvc.framework.database.entity.Environmental;
-import com.chelvc.framework.database.support.MyBatisUtils;
 import net.sf.jsqlparser.expression.Expression;
 
 /**
@@ -27,7 +26,7 @@ public class EnvIsolateInterceptor implements TenantLineHandler {
         if (Expressions.env().getLeft() == null) {
             return true;
         }
-        Class<?> model = MyBatisUtils.getTableModel(table);
+        Class<?> model = Tables.getTableClass(table);
         return model == null || !Environmental.class.isAssignableFrom(model);
     }
 }

+ 111 - 66
framework-database/src/main/java/com/chelvc/framework/database/interceptor/Expressions.java

@@ -4,6 +4,7 @@ import java.lang.reflect.Field;
 import java.math.BigInteger;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
@@ -14,16 +15,16 @@ import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.DateUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.database.support.MyBatisUtils;
-import com.chelvc.framework.database.support.TableField;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.LongValue;
 import net.sf.jsqlparser.expression.StringValue;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.ItemsList;
 import net.sf.jsqlparser.schema.Column;
 import org.apache.commons.lang3.tuple.Pair;
 
@@ -36,9 +37,9 @@ import org.apache.commons.lang3.tuple.Pair;
 @Slf4j
 final class Expressions {
     /**
-     * 枚举选项限定配置属性名
+     * 枚举选项限定配置
      */
-    private static final String ENUM_OPTION_LIMIT_PROPERTY = "enum.option.limit";
+    private static final String ENUM_OPTION_LIMIT = "enum.option.limit";
 
     private Expressions() {
     }
@@ -69,24 +70,24 @@ final class Expressions {
     public static final Column CREATOR_COLUMN = new Column("creator");
 
     /**
-     * 创建时间字段
+     * 更新人字段
      */
-    public static final Column CREATE_TIME_COLUMN = new Column("create_time");
+    public static final Column UPDATER_COLUMN = new Column("updater");
 
     /**
-     * 更新人字段
+     * 逻辑删除字段
      */
-    public static final Column UPDATER_COLUMN = new Column("updater");
+    public static final Column DELETED_COLUMN = new Column("deleted");
 
     /**
-     * 更新时间字段
+     * 创建时间字段
      */
-    public static final Column UPDATE_TIME_COLUMN = new Column("update_time");
+    public static final Column CREATE_TIME_COLUMN = new Column("create_time");
 
     /**
-     * 逻辑删除字段
+     * 更新时间字段
      */
-    public static final Column DELETED_COLUMN = new Column("deleted");
+    public static final Column UPDATE_TIME_COLUMN = new Column("update_time");
 
     /**
      * 零值/表达式映射
@@ -104,70 +105,88 @@ final class Expressions {
     private static volatile Pair<String, Expression> ENV;
 
     /**
-     * 解析数据查询附加条件配置
-     *
-     * @param config 配置信息
-     * @return 表名/枚举字段名/选项值映射表
+     * 表达式提供器接口
      */
-    private static Map<String, Map<String, ExpressionList>> analyseSelectAdditionalCondition(String config) {
-        if (StringUtils.isEmpty(config)) {
-            return Collections.emptyMap();
-        }
+    interface Provider {
+        /**
+         * 获取表达式
+         *
+         * @param table 表信息
+         * @return 字段名/表达式值映射表
+         */
+        Map<String, ItemsList> get(Tables.Table table);
+    }
 
-        // 构建表名/枚举字段上下文映射表
-        String[] expressions = config.split(",");
-        if (ObjectUtils.isEmpty(expressions)) {
-            return Collections.emptyMap();
-        }
-        Map<String, Set<TableField>> tables = Maps.newHashMapWithExpectedSize(expressions.length);
-        for (String expression : expressions) {
-            if (StringUtils.isEmpty(expression = expression.trim())) {
-                continue;
-            }
+    /**
+     * 表达式提供器抽象实现
+     */
+    static abstract class AbstractProvider implements Provider {
+        protected final Set<String> columns;
+        private volatile Map<String, ItemsList> expressions;
 
-            // 解析表字段配置表达式
-            String table, column;
-            int index = expression.indexOf('.');
-            if (index < 1 || StringUtils.isEmpty(table = expression.substring(0, index))
-                    || StringUtils.isEmpty(column = expression.substring(index + 1))) {
-                log.warn("Invalid column expression: {}", expression);
-                continue;
+        public AbstractProvider(@NonNull Collection<String> columns) {
+            this.columns = Collections.unmodifiableSet(Sets.newHashSet(columns));
+            if (this.columns.isEmpty()) {
+                this.expressions = Collections.emptyMap();
             }
+        }
+
+        /**
+         * 获取字段值表达式
+         *
+         * @param table  表对象
+         * @param column 字段名
+         * @return 表达式实例
+         */
+        protected abstract ItemsList expression(Tables.Table table, String column);
 
-            // 获取表字段上下文信息
-            TableField field = MyBatisUtils.getTableField(table, column);
-            if (field != null && Enum.class.isAssignableFrom(field.getType())) {
-                tables.computeIfAbsent(MyBatisUtils.unquote(table), k -> Sets.newHashSet()).add(field);
+        @Override
+        public Map<String, ItemsList> get(@NonNull Tables.Table table) {
+            if (this.expressions == null) {
+                synchronized (this) {
+                    if (this.expressions == null) {
+                        this.expressions = Maps.newHashMapWithExpectedSize(this.columns.size());
+                        this.columns.forEach(column -> {
+                            ItemsList expression = this.expression(table, column);
+                            if (expression != null) {
+                                this.expressions.put(column, expression);
+                            }
+                        });
+                    }
+                }
             }
+            return this.expressions;
         }
-        if (ObjectUtils.isEmpty(tables)) {
-            return Collections.emptyMap();
+    }
+
+    /**
+     * 枚举字段值表达式提供器实现
+     */
+    static class EnumerateProvider extends AbstractProvider {
+        public EnumerateProvider(@NonNull Set<String> columns) {
+            super(columns);
         }
 
-        // 构建表明/枚举字段名/选项值映射表
-        Map<String, Map<String, ExpressionList>> conditions = Maps.newHashMapWithExpectedSize(tables.size());
-        tables.forEach((table, contexts) -> {
-            Map<String, ExpressionList> additional = Maps.newLinkedHashMapWithExpectedSize(contexts.size());
-            for (TableField context : contexts) {
-                Field[] fields = context.getType().getDeclaredFields();
-                if (ObjectUtils.notEmpty(fields)) {
-                    ArrayList<Expression> values = Lists.newArrayListWithCapacity(fields.length);
-                    for (Field field : fields) {
-                        if (!field.isSynthetic() && field.isEnumConstant()) {
-                            values.add(new StringValue(field.getName()));
+        @Override
+        protected ItemsList expression(@NonNull Tables.Table table, @NonNull String column) {
+            Tables.Field field = table.getField(column);
+            if (field != null && field.getType().isEnum()) {
+                Field[] options = field.getType().getDeclaredFields();
+                if (ObjectUtils.notEmpty(options)) {
+                    ArrayList<Expression> values = Lists.newArrayListWithCapacity(options.length);
+                    for (Field option : options) {
+                        if (!option.isSynthetic() && option.isEnumConstant()) {
+                            values.add(new StringValue(option.getName()));
                         }
                     }
                     if (!values.isEmpty()) {
                         values.trimToSize();
-                        additional.put(context.getColumn(), new ExpressionList(values));
+                        return new ExpressionList(values);
                     }
                 }
             }
-            if (!additional.isEmpty()) {
-                conditions.put(table, Collections.unmodifiableMap(additional));
-            }
-        });
-        return conditions.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(conditions);
+            return null;
+        }
     }
 
     /**
@@ -210,13 +229,39 @@ final class Expressions {
     }
 
     /**
-     * 获取数据查询附加条件
+     * 获取字段值表达式提供器
      *
-     * @return 表名称/字段名/值映射表
+     * @return 表名称/字段值表达式提供器映射表
      */
-    public static Map<String, Map<String, ExpressionList>> getSelectAdditionalConditions() {
-        return ApplicationContextHolder.getProperty(
-                ENUM_OPTION_LIMIT_PROPERTY, Expressions::analyseSelectAdditionalCondition
-        );
+    public static Map<String, Provider> getExpressionProviders() {
+        return ApplicationContextHolder.getProperty(ENUM_OPTION_LIMIT, property -> {
+            if (StringUtils.isEmpty(property) || (property = property.trim()).isEmpty()) {
+                return Collections.emptyMap();
+            }
+
+            Map<String, Set<String>> tables = Maps.newHashMap();
+            for (String expression : property.split(",")) {
+                if ((expression = expression.trim()).isEmpty()) {
+                    continue;
+                }
+
+                String table, column;
+                int delimiter = expression.lastIndexOf('.');
+                if (delimiter < 1 || StringUtils.isEmpty(table = expression.substring(0, delimiter).trim())
+                        || StringUtils.isEmpty(column = expression.substring(delimiter + 1).trim())) {
+                    log.warn("Invalid column expression: {}", expression);
+                    continue;
+                }
+                tables.computeIfAbsent(table, k -> Sets.newHashSet()).add(column);
+            }
+
+            if (ObjectUtils.isEmpty(tables)) {
+                return Collections.emptyMap();
+            }
+
+            Map<String, Provider> providers = Maps.newHashMapWithExpectedSize(tables.size());
+            tables.forEach((table, columns) -> providers.put(table, new EnumerateProvider(columns)));
+            return providers;
+        });
     }
 }

+ 269 - 0
framework-database/src/main/java/com/chelvc/framework/database/interceptor/Tables.java

@@ -0,0 +1,269 @@
+package com.chelvc.framework.database.interceptor;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.database.annotation.Sensitive;
+import com.chelvc.framework.database.util.SQLUtils;
+import com.google.common.collect.Maps;
+import lombok.Getter;
+import lombok.NonNull;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.UnknownTypeHandler;
+
+/**
+ * 数据表工具类
+ *
+ * @author Woody
+ * @date 2025/3/16
+ */
+final class Tables {
+    /**
+     * 执行上下文
+     */
+    private static final ThreadLocal<Context> CONTEXT = new ThreadLocal<>();
+
+    /**
+     * 表名称/对象映射表
+     */
+    private static final Map<String, Table> TABLE_NAME_MAPPING = Maps.newConcurrentMap();
+
+    /**
+     * 类对象/类型处理器实例映射表
+     */
+    private static final Map<Class<?>, TypeHandler<?>> TYPE_HANDLER_MAPPING = Maps.newConcurrentMap();
+
+    private Tables() {
+    }
+
+    /**
+     * 上下文
+     */
+    @Getter
+    static class Context implements Serializable {
+        /**
+         * 数据库Schema
+         */
+        private final String schema;
+
+        /**
+         * 命名空间
+         */
+        private final String namespace;
+
+        public Context(@NonNull String schema, @NonNull String namespace) {
+            this.schema = schema;
+            this.namespace = namespace;
+        }
+    }
+
+    /**
+     * 表信息
+     */
+    @Getter
+    static class Table implements Serializable {
+        /**
+         * 空表信息
+         */
+        public static final Table EMPTY = new Table();
+
+        private final TableInfo info;
+        private final Map<String, Field> fields;
+
+        private Table() {
+            this.info = null;
+            this.fields = null;
+        }
+
+        public Table(@NonNull TableInfo info) {
+            this.info = info;
+            if (ObjectUtils.isEmpty(info.getFieldList())) {
+                this.fields = Collections.emptyMap();
+            } else {
+                this.fields = Collections.unmodifiableMap(info.getFieldList().stream()
+                        .collect(Collectors.toMap(field -> SQLUtils.unquote(field.getColumn()), Field::new)));
+            }
+        }
+
+        /**
+         * 获取表类型
+         *
+         * @return 对象类型
+         */
+        public Class<?> getType() {
+            return this.info == null ? null : this.info.getEntityType();
+        }
+
+        /**
+         * 获取表字段
+         *
+         * @param column 字段名
+         * @return 表字段信息
+         */
+        public Field getField(@NonNull String column) {
+            return this.fields == null ? null : this.fields.get(SQLUtils.unquote(column));
+        }
+    }
+
+    /**
+     * 字段信息
+     */
+    @Getter
+    static class Field implements Serializable {
+        private final boolean sensitive;
+        private final TableFieldInfo info;
+
+        public Field(@NonNull TableFieldInfo info) {
+            this.info = info;
+            this.sensitive = info.getField().isAnnotationPresent(Sensitive.class);
+        }
+
+        /**
+         * 获取字段类型
+         *
+         * @return 对象类型
+         */
+        public Class<?> getType() {
+            return this.info.getPropertyType();
+        }
+
+        /**
+         * 获取字段对象
+         *
+         * @return 字段对象实例
+         */
+        public java.lang.reflect.Field getField() {
+            return this.info.getField();
+        }
+
+        /**
+         * 获取字段名称
+         *
+         * @return 字段名称
+         */
+        public String getColumn() {
+            return this.info.getColumn();
+        }
+
+        /**
+         * 获取属性名称
+         *
+         * @return 属性名称
+         */
+        public String getProperty() {
+            return this.info.getProperty();
+        }
+
+        /**
+         * 获取类型处理器实例
+         *
+         * @return 类型处理器实例
+         */
+        public TypeHandler<?> getHandler() {
+            Class<? extends TypeHandler<?>> type = this.info.getTypeHandler();
+            if (type == null || type == UnknownTypeHandler.class) {
+                return null;
+            }
+            TypeHandler<?> handler = TYPE_HANDLER_MAPPING.get(type);
+            if (handler == null) {
+                handler = TYPE_HANDLER_MAPPING.computeIfAbsent(type, k -> ObjectUtils.instance(type));
+            }
+            return handler;
+        }
+    }
+
+    /**
+     * 获取上下文
+     *
+     * @return 上下文实例
+     */
+    public static Context getContext() {
+        return CONTEXT.get();
+    }
+
+    /**
+     * 设置上下文
+     *
+     * @param context 上下文实例
+     */
+    public static void setContext(Context context) {
+        if (context == null) {
+            CONTEXT.remove();
+        } else {
+            CONTEXT.set(context);
+        }
+    }
+
+    /**
+     * 获取简单表名,去掉schema前缀
+     *
+     * @param name 表名
+     * @return 表名
+     */
+    public static String getSimpleName(String name) {
+        if (StringUtils.isEmpty(name)) {
+            return name;
+        }
+        int delimiter = name.lastIndexOf('.');
+        return delimiter > 0 ? name.substring(delimiter + 1) : name;
+    }
+
+    /**
+     * 获取表信息
+     *
+     * @param name 表名称
+     * @return 表信息
+     */
+    public static Table getTable(@NonNull String name) {
+        Context context = getContext();
+        String unquote = SQLUtils.unquote(name);
+        String schema = ObjectUtils.ifNull(context, Context::getSchema);
+        String key = StringUtils.isEmpty(schema) ? unquote : (schema + StringPool.DOT + unquote);
+        Table table = TABLE_NAME_MAPPING.get(key);
+        if (table == null) {
+            table = TABLE_NAME_MAPPING.computeIfAbsent(key, k -> {
+                boolean quoted = SQLUtils.isQuoted(name);
+                String namespace = ObjectUtils.ifNull(context, Context::getNamespace);
+                for (TableInfo info : TableInfoHelper.getTableInfos()) {
+                    String s = info.getCurrentNamespace(), n = getSimpleName(info.getTableName());
+                    if ((Objects.equals(s, namespace) || StringUtils.isEmpty(namespace))
+                            && (Objects.equals(n, name) || (quoted && Objects.equals(n, unquote)))) {
+                        return new Table(info);
+                    }
+                }
+                return Table.EMPTY;
+            });
+        }
+        return table == Table.EMPTY ? null : table;
+    }
+
+    /**
+     * 获取表类型
+     *
+     * @param table 表名
+     * @return 对象类型
+     */
+    public static Class<?> getTableClass(@NonNull String table) {
+        return ObjectUtils.ifNull(getTable(table), Table::getType);
+    }
+
+    /**
+     * 获取表字段信息
+     *
+     * @param table  表名称
+     * @param column 字段名
+     * @return 表字段信息
+     */
+    public static Field getTableField(@NonNull String table, @NonNull String column) {
+        return ObjectUtils.ifNull(getTable(table), tb -> tb.getField(column));
+    }
+}

+ 0 - 192
framework-database/src/main/java/com/chelvc/framework/database/support/MyBatisUtils.java

@@ -1,192 +0,0 @@
-package com.chelvc.framework.database.support;
-
-import java.util.Map;
-
-import com.baomidou.mybatisplus.core.metadata.TableInfo;
-import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
-import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
-import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
-import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
-import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
-import com.chelvc.framework.common.util.AssertUtils;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.common.util.StringUtils;
-import com.google.common.collect.Maps;
-import lombok.NonNull;
-import org.apache.ibatis.reflection.property.PropertyNamer;
-
-/**
- * MyBatis工具类
- *
- * @author Woody
- * @date 2025/3/14
- */
-public final class MyBatisUtils {
-    /**
-     * 表名称/对象映射表
-     */
-    private static final Map<String, Table> TABLE_NAME_MAPPING = Maps.newConcurrentMap();
-
-    private MyBatisUtils() {
-    }
-
-    /**
-     * 字符串参数特殊字符转义
-     *
-     * @param value 参数值
-     * @return 参数值
-     */
-    public static String escape(String value) {
-        if (StringUtils.isEmpty(value)) {
-            return value;
-        }
-
-        // 判断参数是否需要转义
-        boolean needs = false;
-        int size = value.length();
-        for (int i = 0; i < size; i++) {
-            char c = value.charAt(i);
-            if (c == '\u0000' || c == '\n' || c == '\r' || c == '\u001a' || c == '"' || c == '\'' || c == '\\') {
-                needs = true;
-                break;
-            }
-        }
-        if (!needs) {
-            return value;
-        }
-
-        // 特殊字符转义
-        StringBuilder buffer = new StringBuilder((int) ((double) size * 1.1D));
-        for (int i = 0; i < size; i++) {
-            char c = value.charAt(i);
-            switch (c) {
-                case '\u0000':
-                    buffer.append('\\');
-                    buffer.append('0');
-                    break;
-                case '\n':
-                    buffer.append('\\');
-                    buffer.append('n');
-                    break;
-                case '\r':
-                    buffer.append('\\');
-                    buffer.append('r');
-                    break;
-                case '\u001a':
-                    buffer.append('\\');
-                    buffer.append('Z');
-                    break;
-                case '\'':
-                    buffer.append('\'');
-                    buffer.append('\'');
-                    break;
-                case '\\':
-                    buffer.append('\\');
-                    buffer.append('\\');
-                    break;
-                default:
-                    buffer.append(c);
-            }
-        }
-        return buffer.toString();
-    }
-
-    /**
-     * 去掉表、字段名称反引号
-     *
-     * @param name 表、字段名称
-     * @return 表、字段名称
-     */
-    public static String unquote(String name) {
-        return isQuoted(name) ? name.substring(1, name.length() - 1) : name;
-    }
-
-    /**
-     * 判断表、字段名称是否包含反引号
-     *
-     * @param name 表、字段名称
-     * @return true/false
-     */
-    public static boolean isQuoted(String name) {
-        return StringUtils.notEmpty(name) && name.charAt(0) == '`' && name.charAt(name.length() - 1) == '`';
-    }
-
-    /**
-     * 将Getter方法转换成字段名称
-     *
-     * @param getter Getter方法
-     * @return 字段名称
-     */
-    public static String getter2column(@NonNull SFunction<?, ?> getter) {
-        SerializedLambda lambda = LambdaUtils.resolve(getter);
-        String property = PropertyNamer.methodToProperty(lambda.getImplMethodName());
-        return property2column(lambda.getInstantiatedType(), property);
-    }
-
-    /**
-     * 将Getter方法转换成属性名称
-     *
-     * @param getter Getter方法
-     * @return 属性名称
-     */
-    public static String getter2property(@NonNull SFunction<?, ?> getter) {
-        SerializedLambda lambda = LambdaUtils.resolve(getter);
-        return PropertyNamer.methodToProperty(lambda.getImplMethodName());
-    }
-
-    /**
-     * 将属性名转换成字段名称
-     *
-     * @param clazz    实体对象
-     * @param property 属性名称
-     * @return 字段名称
-     */
-    public static String property2column(@NonNull Class<?> clazz, @NonNull String property) {
-        Map<String, ColumnCache> columns = LambdaUtils.getColumnMap(clazz);
-        ColumnCache column = ObjectUtils.ifNull(columns, cs -> cs.get(LambdaUtils.formatKey(property)));
-        AssertUtils.nonnull(column, () -> "Column not found for property: " + clazz.getName() + "." + property);
-        return column.getColumn();
-    }
-
-    /**
-     * 获取表上下文
-     *
-     * @param name 表名称
-     * @return 表上下文
-     */
-    public static Table getTable(@NonNull String name) {
-        String unquote = MyBatisUtils.unquote(name);
-        Table table = TABLE_NAME_MAPPING.get(unquote);
-        if (table == null) {
-            table = TABLE_NAME_MAPPING.computeIfAbsent(unquote, k -> {
-                TableInfo info = TableInfoHelper.getTableInfo(name);
-                if (info == null && MyBatisUtils.isQuoted(name)) {
-                    info = TableInfoHelper.getTableInfo(unquote);
-                }
-                return ObjectUtils.ifNull(info, Table::new);
-            });
-        }
-        return table;
-    }
-
-    /**
-     * 获取表数据模型
-     *
-     * @param table 表名
-     * @return 数据模型对象
-     */
-    public static Class<?> getTableModel(@NonNull String table) {
-        return ObjectUtils.ifNull(getTable(table), Table::getModel);
-    }
-
-    /**
-     * 获取表字段上下文
-     *
-     * @param table 表名称
-     * @param field 字段名称
-     * @return 表字段上下文
-     */
-    public static TableField getTableField(@NonNull String table, @NonNull String field) {
-        return ObjectUtils.ifNull(getTable(table), context -> context.getField(field));
-    }
-}

+ 0 - 53
framework-database/src/main/java/com/chelvc/framework/database/support/Table.java

@@ -1,53 +0,0 @@
-package com.chelvc.framework.database.support;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import com.baomidou.mybatisplus.core.metadata.TableInfo;
-import com.chelvc.framework.common.util.ObjectUtils;
-import lombok.Getter;
-import lombok.NonNull;
-
-/**
- * 表上下文对象
- *
- * @author Woody
- * @date 2025/1/5
- */
-@Getter
-public class Table implements Serializable {
-    private final TableInfo info;
-    private final Map<String, TableField> fields;
-
-    public Table(@NonNull TableInfo info) {
-        this.info = info;
-        if (ObjectUtils.isEmpty(info.getFieldList())) {
-            this.fields = Collections.emptyMap();
-        } else {
-            this.fields = Collections.unmodifiableMap(info.getFieldList().stream().collect(Collectors.toMap(
-                    field -> MyBatisUtils.unquote(field.getColumn()), TableField::new
-            )));
-        }
-    }
-
-    /**
-     * 获取数据模型
-     *
-     * @return 模型对象
-     */
-    public Class<?> getModel() {
-        return this.info.getEntityType();
-    }
-
-    /**
-     * 获取表字段
-     *
-     * @param field 字段名称
-     * @return 表字段信息
-     */
-    public TableField getField(@NonNull String field) {
-        return this.fields.get(MyBatisUtils.unquote(field));
-    }
-}

+ 0 - 89
framework-database/src/main/java/com/chelvc/framework/database/support/TableField.java

@@ -1,89 +0,0 @@
-package com.chelvc.framework.database.support;
-
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.util.Map;
-
-import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.database.annotation.Sensitive;
-import com.google.common.collect.Maps;
-import lombok.Getter;
-import lombok.NonNull;
-import org.apache.ibatis.type.TypeHandler;
-import org.apache.ibatis.type.UnknownTypeHandler;
-
-/**
- * 字段上下文对象
- *
- * @author Woody
- * @date 2024/6/10
- */
-@Getter
-public class TableField implements Serializable {
-    /**
-     * 类对象/类型处理器实例映射表
-     */
-    private static final Map<Class<?>, TypeHandler<?>> TYPE_HANDLER_MAPPING = Maps.newConcurrentMap();
-
-    private final boolean sensitive;
-    private final TableFieldInfo info;
-
-    public TableField(@NonNull TableFieldInfo info) {
-        this.info = info;
-        this.sensitive = info.getField().isAnnotationPresent(Sensitive.class);
-    }
-
-    /**
-     * 获取属性类型
-     *
-     * @return 对象类型
-     */
-    public Class<?> getType() {
-        return this.info.getPropertyType();
-    }
-
-    /**
-     * 获取字段对象
-     *
-     * @return 字段对象实例
-     */
-    public Field getField() {
-        return this.info.getField();
-    }
-
-    /**
-     * 获取字段名称
-     *
-     * @return 字段名称
-     */
-    public String getColumn() {
-        return this.info.getColumn();
-    }
-
-    /**
-     * 获取属性名称
-     *
-     * @return 属性名称
-     */
-    public String getProperty() {
-        return this.info.getProperty();
-    }
-
-    /**
-     * 获取类型处理器实例
-     *
-     * @return 类型处理器实例
-     */
-    public TypeHandler<?> getHandler() {
-        Class<? extends TypeHandler<?>> type = this.info.getTypeHandler();
-        if (type == null || type == UnknownTypeHandler.class) {
-            return null;
-        }
-        TypeHandler<?> handler = TYPE_HANDLER_MAPPING.get(type);
-        if (handler == null) {
-            handler = TYPE_HANDLER_MAPPING.computeIfAbsent(type, k -> ObjectUtils.instance(type));
-        }
-        return handler;
-    }
-}

+ 27 - 8
framework-database/src/main/java/com/chelvc/framework/database/support/Updates.java

@@ -11,10 +11,13 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
 import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
+import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.google.common.collect.Maps;
 import lombok.NonNull;
+import org.apache.ibatis.reflection.property.PropertyNamer;
 
 /**
  * 自定义字段更新工具类
@@ -361,6 +364,22 @@ public final class Updates {
         return update.set(column, value);
     }
 
+    /**
+     * 将Getter方法转换成字段名称
+     *
+     * @param getter Getter方法
+     * @return 字段名称
+     */
+    public static String getter2column(@NonNull SFunction<?, ?> getter) {
+        SerializedLambda lambda = LambdaUtils.resolve(getter);
+        Class<?> clazz = lambda.getInstantiatedType();
+        Map<String, ColumnCache> columns = LambdaUtils.getColumnMap(clazz);
+        String property = PropertyNamer.methodToProperty(lambda.getImplMethodName());
+        ColumnCache column = ObjectUtils.ifNull(columns, cs -> cs.get(LambdaUtils.formatKey(property)));
+        AssertUtils.nonnull(column, () -> "Column not found for property: " + clazz.getName() + "." + property);
+        return column.getColumn();
+    }
+
     /**
      * 字段更新对象
      *
@@ -425,7 +444,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> use(Condition condition, @NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.use(condition, MyBatisUtils.getter2column(getter));
+                this.use(condition, getter2column(getter));
             }
             return this;
         }
@@ -452,7 +471,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> add(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.add(MyBatisUtils.getter2column(getter));
+                this.add(getter2column(getter));
             }
             return this;
         }
@@ -479,7 +498,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> clear(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.clear(MyBatisUtils.getter2column(getter));
+                this.clear(getter2column(getter));
             }
             return this;
         }
@@ -506,7 +525,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> subtract(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.subtract(MyBatisUtils.getter2column(getter));
+                this.subtract(getter2column(getter));
             }
             return this;
         }
@@ -533,7 +552,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> multiply(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.multiply(MyBatisUtils.getter2column(getter));
+                this.multiply(getter2column(getter));
             }
             return this;
         }
@@ -560,7 +579,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> divide(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.divide(MyBatisUtils.getter2column(getter));
+                this.divide(getter2column(getter));
             }
             return this;
         }
@@ -587,7 +606,7 @@ public final class Updates {
         @SafeVarargs
         public final Update<T> jsonMerge(@NonNull SFunction<T, ?>... getters) {
             for (SFunction<T, ?> getter : getters) {
-                this.jsonMerge(MyBatisUtils.getter2column(getter));
+                this.jsonMerge(getter2column(getter));
             }
             return this;
         }
@@ -611,7 +630,7 @@ public final class Updates {
          * @return 字段更新对象实例
          */
         public final Update<T> jsonLength(@NonNull SFunction<T, ?> getter, @NonNull SFunction<T, ?> target) {
-            return this.jsonLength(MyBatisUtils.getter2column(getter), MyBatisUtils.getter2column(target));
+            return this.jsonLength(getter2column(getter), getter2column(target));
         }
 
         /**

+ 158 - 0
framework-database/src/main/java/com/chelvc/framework/database/util/SQLUtils.java

@@ -0,0 +1,158 @@
+package com.chelvc.framework.database.util;
+
+import com.chelvc.framework.common.util.StringUtils;
+
+/**
+ * SQL工具类
+ *
+ * @author Woody
+ * @date 2025/3/16
+ */
+public final class SQLUtils {
+    /**
+     * DB2 JDBC连接前缀
+     */
+    private static final String JDBC_DB2_PREFIX = "jdbc:db2://";
+
+    /**
+     * MySQL JDBC连接前缀
+     */
+    private static final String JDBC_MYSQL_PREFIX = "jdbc:mysql://";
+
+    /**
+     * Oracle JDBC连接前缀
+     */
+    private static final String JDBC_ORACLE_PREFIX = "jdbc:oracle:thin:@";
+
+    /**
+     * SqlServer JDBC连接前缀
+     */
+    private static final String JDBC_SQLSERVER_PREFIX = "jdbc:sqlserver://";
+
+    /**
+     * Postgresql JDBC连接前缀
+     */
+    private static final String JDBC_POSTGRESQL_PREFIX = "jdbc:postgresql://";
+
+
+    private SQLUtils() {
+    }
+
+    /**
+     * 字符串参数特殊字符转义
+     *
+     * @param value 参数值
+     * @return 参数值
+     */
+    public static String escape(String value) {
+        if (StringUtils.isEmpty(value)) {
+            return value;
+        }
+
+        // 判断参数是否需要转义
+        boolean needs = false;
+        int size = value.length();
+        for (int i = 0; i < size; i++) {
+            char c = value.charAt(i);
+            if (c == '\u0000' || c == '\n' || c == '\r' || c == '\u001a' || c == '"' || c == '\'' || c == '\\') {
+                needs = true;
+                break;
+            }
+        }
+        if (!needs) {
+            return value;
+        }
+
+        // 特殊字符转义
+        StringBuilder buffer = new StringBuilder((int) ((double) size * 1.1D));
+        for (int i = 0; i < size; i++) {
+            char c = value.charAt(i);
+            switch (c) {
+                case '\u0000':
+                    buffer.append('\\');
+                    buffer.append('0');
+                    break;
+                case '\n':
+                    buffer.append('\\');
+                    buffer.append('n');
+                    break;
+                case '\r':
+                    buffer.append('\\');
+                    buffer.append('r');
+                    break;
+                case '\u001a':
+                    buffer.append('\\');
+                    buffer.append('Z');
+                    break;
+                case '\'':
+                    buffer.append('\'');
+                    buffer.append('\'');
+                    break;
+                case '\\':
+                    buffer.append('\\');
+                    buffer.append('\\');
+                    break;
+                default:
+                    buffer.append(c);
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * 去掉表、字段名称反引号
+     *
+     * @param name 表、字段名称
+     * @return 表、字段名称
+     */
+    public static String unquote(String name) {
+        return isQuoted(name) ? name.substring(1, name.length() - 1) : name;
+    }
+
+    /**
+     * 判断表、字段名称是否包含反引号
+     *
+     * @param name 表、字段名称
+     * @return true/false
+     */
+    public static boolean isQuoted(String name) {
+        return StringUtils.notEmpty(name) && name.charAt(0) == '`' && name.charAt(name.length() - 1) == '`';
+    }
+
+    /**
+     * 获取JDBC连接schema
+     *
+     * @param url JDBC连接
+     * @return schema
+     */
+    public static String getSchema(String url) {
+        if (StringUtils.isEmpty(url) || (url = url.trim()).isEmpty()) {
+            return null;
+        } else if (url.startsWith(JDBC_DB2_PREFIX)) {
+            int begin = url.indexOf('/', JDBC_DB2_PREFIX.length()), end = url.indexOf('?', begin);
+            return end < 0 ? url.substring(begin + 1).trim() : url.substring(begin + 1, end).trim();
+        } else if (url.startsWith(JDBC_MYSQL_PREFIX)) {
+            int begin = url.indexOf('/', JDBC_MYSQL_PREFIX.length()), end = url.indexOf('?', begin);
+            return end < 0 ? url.substring(begin + 1).trim() : url.substring(begin + 1, end).trim();
+        } else if (url.startsWith(JDBC_ORACLE_PREFIX)) {
+            if (url.charAt(JDBC_ORACLE_PREFIX.length()) == ':') {
+                int begin = url.lastIndexOf(':'), end = url.indexOf(';', begin);
+                return end < 0 ? url.substring(begin + 1).trim() : url.substring(begin + 1, end).trim();
+            }
+            int begin = url.indexOf('/', JDBC_ORACLE_PREFIX.length() + 2), end = url.indexOf(';', begin);
+            return end < 0 ? url.substring(begin + 1).trim() : url.substring(begin + 1, end).trim();
+        } else if (url.startsWith(JDBC_SQLSERVER_PREFIX)) {
+            String parameters = url.substring(url.indexOf(';', JDBC_SQLSERVER_PREFIX.length()) + 1);
+            for (String parameter : parameters.split(";")) {
+                int delimiter = parameter.indexOf('=');
+                if (delimiter > 0 && parameter.substring(0, delimiter).trim().equalsIgnoreCase("databaseName")) {
+                    return parameter.substring(delimiter + 1).trim();
+                }
+            }
+        } else if (url.startsWith(JDBC_POSTGRESQL_PREFIX)) {
+            int begin = url.indexOf('/', JDBC_POSTGRESQL_PREFIX.length()), end = url.indexOf('?', begin);
+            return end < 0 ? url.substring(begin + 1).trim() : url.substring(begin + 1, end).trim();
+        }
+        throw new IllegalArgumentException("Unknown jdbc url: " + url);
+    }
+}