Parcourir la source

优化数据库json类型字段自动映射逻辑

woody il y a 1 an
Parent
commit
470c61e55a
29 fichiers modifiés avec 870 ajouts et 643 suppressions
  1. 24 0
      framework-common/src/main/java/com/chelvc/framework/common/model/Reference.java
  2. 106 0
      framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java
  3. 29 0
      framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java
  4. 41 0
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java
  5. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/BooleansTypeHandler.java
  6. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/DoublesTypeHandler.java
  7. 1 34
      framework-database/src/main/java/com/chelvc/framework/database/handler/FileTypeHandler.java
  8. 1 41
      framework-database/src/main/java/com/chelvc/framework/database/handler/FilesTypeHandler.java
  9. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/FloatsTypeHandler.java
  10. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/IntegersTypeHandler.java
  11. 170 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/JsonTypeHandler.java
  12. 12 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/ListTypeHandler.java
  13. 12 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/ListsTypeHandler.java
  14. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/LongsTypeHandler.java
  15. 12 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/MapTypeHandler.java
  16. 0 53
      framework-database/src/main/java/com/chelvc/framework/database/handler/MappingTypeHandler.java
  17. 0 54
      framework-database/src/main/java/com/chelvc/framework/database/handler/MappingsTypeHandler.java
  18. 13 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/MapsTypeHandler.java
  19. 1 34
      framework-database/src/main/java/com/chelvc/framework/database/handler/ModificationTypeHandler.java
  20. 1 41
      framework-database/src/main/java/com/chelvc/framework/database/handler/ModificationsTypeHandler.java
  21. 1 6
      framework-database/src/main/java/com/chelvc/framework/database/handler/PeriodTypeHandler.java
  22. 1 40
      framework-database/src/main/java/com/chelvc/framework/database/handler/PeriodsTypeHandler.java
  23. 1 6
      framework-database/src/main/java/com/chelvc/framework/database/handler/RegionTypeHandler.java
  24. 1 40
      framework-database/src/main/java/com/chelvc/framework/database/handler/RegionsTypeHandler.java
  25. 12 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/SetTypeHandler.java
  26. 13 0
      framework-database/src/main/java/com/chelvc/framework/database/handler/SetsTypeHandler.java
  27. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/ShortsTypeHandler.java
  28. 1 42
      framework-database/src/main/java/com/chelvc/framework/database/handler/StringsTypeHandler.java
  29. 411 0
      framework-database/src/main/java/com/chelvc/framework/database/support/JsonTypeHandlerBinder.java

+ 24 - 0
framework-common/src/main/java/com/chelvc/framework/common/model/Reference.java

@@ -1,6 +1,10 @@
 package com.chelvc.framework.common.model;
 
 import java.io.Serializable;
+import java.util.function.Supplier;
+
+import com.chelvc.framework.common.util.ObjectUtils;
+import lombok.NonNull;
 
 /**
  * 对象引用
@@ -32,6 +36,26 @@ public final class Reference<T> implements Serializable {
         return this.target;
     }
 
+    /**
+     * 获取引用目标
+     *
+     * @param defaultValue 默认值
+     * @return 目标对象实例
+     */
+    public T get(T defaultValue) {
+        return ObjectUtils.ifNull(this.target, defaultValue);
+    }
+
+    /**
+     * 获取引用目标
+     *
+     * @param supplier 默认值函数
+     * @return 目标对象实例
+     */
+    public T get(@NonNull Supplier<T> supplier) {
+        return ObjectUtils.ifNull(this.target, supplier);
+    }
+
     /**
      * 设置引用目标
      *

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

@@ -42,6 +42,7 @@ import ma.glasnost.orika.MappingContext;
 import ma.glasnost.orika.converter.BidirectionalConverter;
 import ma.glasnost.orika.impl.DefaultMapperFactory;
 import ma.glasnost.orika.metadata.Type;
+import org.apache.commons.lang3.reflect.TypeUtils;
 
 /**
  * 对象工具类
@@ -889,6 +890,111 @@ public final class ObjectUtils {
         return (Class<?>) type;
     }
 
+    /**
+     * 判断是否只是Map类型(无有效泛型类型)
+     *
+     * @param type 对象类型
+     * @return true/false
+     */
+    public static boolean isOnlyMap(java.lang.reflect.Type type) {
+        if (type == Map.class) {
+            return true;
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType parameterized = (ParameterizedType) type;
+            if (parameterized.getRawType() == Map.class) {
+                java.lang.reflect.Type[] args = parameterized.getActualTypeArguments();
+                java.lang.reflect.Type arg1 = args[0], arg2 = args[1];
+                return !(arg1 instanceof Class || arg1 instanceof ParameterizedType)
+                        && !(arg2 instanceof Class || arg2 instanceof ParameterizedType);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否只是Set类型(无有效泛型类型)
+     *
+     * @param type 对象类型
+     * @return true/false
+     */
+    public static boolean isOnlySet(java.lang.reflect.Type type) {
+        if (type == Set.class) {
+            return true;
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType parameterized = (ParameterizedType) type;
+            if (parameterized.getRawType() == Set.class) {
+                java.lang.reflect.Type arg = parameterized.getActualTypeArguments()[0];
+                return !(arg instanceof Class || arg instanceof ParameterizedType);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否只是List类型(无有效泛型类型)
+     *
+     * @param type 对象类型
+     * @return true/false
+     */
+    public static boolean isOnlyList(java.lang.reflect.Type type) {
+        if (type == List.class) {
+            return true;
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType parameterized = (ParameterizedType) type;
+            if (parameterized.getRawType() == List.class) {
+                java.lang.reflect.Type arg = parameterized.getActualTypeArguments()[0];
+                return !(arg instanceof Class || arg instanceof ParameterizedType);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否只是Collection类型(无有效泛型类型)
+     *
+     * @param type 对象类型
+     * @return true/false
+     */
+    public static boolean isOnlyCollection(java.lang.reflect.Type type) {
+        if (type == Collection.class) {
+            return true;
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType parameterized = (ParameterizedType) type;
+            if (parameterized.getRawType() == Collection.class) {
+                java.lang.reflect.Type arg = parameterized.getActualTypeArguments()[0];
+                return !(arg instanceof Class || arg instanceof ParameterizedType);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 对象类型解析
+     *
+     * @param type 对象类型实例
+     * @return 对象类型解析字符串
+     */
+    public static String analyse(@NonNull java.lang.reflect.Type type) {
+        if (type instanceof ParameterizedType) {
+            ParameterizedType parameterized = (ParameterizedType) type;
+            StringBuilder buffer = new StringBuilder();
+            buffer.append(TypeUtils.class.getName()).append(".parameterize(");
+            buffer.append(analyse(parameterized.getRawType()));
+            java.lang.reflect.Type[] args = parameterized.getActualTypeArguments();
+            buffer.append(", new ").append(java.lang.reflect.Type.class.getName()).append("[]{");
+            if (ObjectUtils.notEmpty(args)) {
+                for (int i = 0; i < args.length; i++) {
+                    if (i > 0) {
+                        buffer.append(", ");
+                    }
+                    buffer.append(analyse(args[i]));
+                }
+            }
+            return buffer.append("})").toString();
+        }
+        return type.getTypeName() + ".class";
+    }
+
     /**
      * 获取对象所有父类/接口类型
      *

+ 29 - 0
framework-database/src/main/java/com/chelvc/framework/database/config/DatabaseConfigurer.java

@@ -4,6 +4,7 @@ import java.util.Collection;
 import java.util.Comparator;
 import java.util.function.Supplier;
 
+import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.injector.ISqlInjector;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -13,15 +14,26 @@ import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerIntercept
 import com.chelvc.framework.common.function.Executor;
 import com.chelvc.framework.common.function.Handler;
 import com.chelvc.framework.common.function.Provider;
+import com.chelvc.framework.common.model.File;
+import com.chelvc.framework.common.model.Modification;
+import com.chelvc.framework.common.model.Period;
+import com.chelvc.framework.common.model.Region;
 import com.chelvc.framework.common.util.IdentityUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.database.context.Transactor;
+import com.chelvc.framework.database.handler.FileTypeHandler;
+import com.chelvc.framework.database.handler.ModificationTypeHandler;
+import com.chelvc.framework.database.handler.PeriodTypeHandler;
+import com.chelvc.framework.database.handler.RegionTypeHandler;
 import com.chelvc.framework.database.interceptor.DeletedExcludeInterceptor;
 import com.chelvc.framework.database.support.EnhanceSqlInjector;
+import com.chelvc.framework.database.support.JsonTypeHandlerBinder;
 import com.chelvc.framework.redis.context.RedisContextHolder;
 import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandlerRegistry;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.ApplicationContext;
@@ -40,6 +52,11 @@ import org.springframework.transaction.annotation.Transactional;
 @Configuration
 @RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class DatabaseConfigurer {
+    static {
+        // 初始化JSON类型绑定处理工具
+        JsonTypeHandlerBinder.initialize();
+    }
+
     private final ApplicationContext applicationContext;
 
     @Bean
@@ -110,4 +127,16 @@ public class DatabaseConfigurer {
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
         return interceptor;
     }
+
+    @Bean
+    public ConfigurationCustomizer configurationCustomizer() {
+        return configuration -> {
+            // 注册自定义字段类型处理器
+            TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry();
+            handlerRegistry.register(File.class, JdbcType.VARCHAR, new FileTypeHandler());
+            handlerRegistry.register(Period.class, JdbcType.VARCHAR, new PeriodTypeHandler());
+            handlerRegistry.register(Region.class, JdbcType.VARCHAR, new RegionTypeHandler());
+            handlerRegistry.register(Modification.class, JdbcType.VARCHAR, new ModificationTypeHandler());
+        };
+    }
 }

+ 41 - 0
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java

@@ -1,9 +1,24 @@
 package com.chelvc.framework.database.context;
 
+import java.io.InputStream;
+import java.io.Reader;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.sql.Connection;
 import java.sql.Statement;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.time.chrono.JapaneseDate;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedList;
@@ -34,8 +49,12 @@ import com.chelvc.framework.base.util.SpringUtils;
 import com.chelvc.framework.common.function.Executor;
 import com.chelvc.framework.common.function.Handler;
 import com.chelvc.framework.common.function.Provider;
+import com.chelvc.framework.common.model.File;
+import com.chelvc.framework.common.model.Modification;
 import com.chelvc.framework.common.model.Pagination;
 import com.chelvc.framework.common.model.Paging;
+import com.chelvc.framework.common.model.Period;
+import com.chelvc.framework.common.model.Region;
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -262,6 +281,28 @@ public final class DatabaseContextHolder {
         return table;
     }
 
+    /**
+     * 判断对象类型处理器是否已注册
+     *
+     * @param type 对象类型
+     * @return true/false
+     */
+    public static boolean isTypeHandlerRegistered(Class<?> type) {
+        return type == byte.class || type == Byte.class || type == byte[].class || type == Byte[].class
+                || type == char.class || type == Character.class || type == short.class || type == Short.class
+                || type == int.class || type == Integer.class || type == long.class || type == Long.class
+                || type == float.class || type == Float.class || type == double.class || type == Double.class
+                || type == boolean.class || type == Boolean.class || type == String.class || type == Object.class
+                || type == BigInteger.class || type == BigDecimal.class || type == Reader.class
+                || type == InputStream.class || type == Date.class || type == java.sql.Date.class
+                || type == java.sql.Time.class || type == java.sql.Timestamp.class || type == Instant.class
+                || 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 == Region.class || type == Modification.class;
+    }
+
     /**
      * 获取实体主键值
      *

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/BooleansTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 真假值列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class BooleansTypeHandler extends BaseTypeHandler<List<Boolean>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Boolean>> REFERENCE = new TypeReference<List<Boolean>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Boolean> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Boolean> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Boolean> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Boolean> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class BooleansTypeHandler extends JsonTypeHandler.Default<List<Boolean>> {
 }

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/DoublesTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 双精度浮点数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class DoublesTypeHandler extends BaseTypeHandler<List<Double>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Double>> REFERENCE = new TypeReference<List<Double>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Double> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Double> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Double> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Double> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class DoublesTypeHandler extends JsonTypeHandler.Default<List<Double>> {
 }

+ 1 - 34
framework-database/src/main/java/com/chelvc/framework/database/handler/FileTypeHandler.java

@@ -1,15 +1,6 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.File;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
 
 /**
  * 文件信息字段类型处理实现
@@ -17,29 +8,5 @@ import org.apache.ibatis.type.JdbcType;
  * @author Woody
  * @date 2024/1/30
  */
-public class FileTypeHandler extends BaseTypeHandler<File> {
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, File parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public File getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), File.class);
-    }
-
-    @Override
-    public File getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), File.class);
-    }
-
-    @Override
-    public File getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), File.class);
-    }
+public class FileTypeHandler extends JsonTypeHandler.Default<File> {
 }

+ 1 - 41
framework-database/src/main/java/com/chelvc/framework/database/handler/FilesTypeHandler.java

@@ -1,18 +1,8 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.File;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
 
 /**
  * 文件信息列表字段类型处理实现
@@ -20,35 +10,5 @@ import org.springframework.util.CollectionUtils;
  * @author Woody
  * @date 2024/1/30
  */
-public class FilesTypeHandler extends BaseTypeHandler<List<File>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<File>> REFERENCE = new TypeReference<List<File>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<File> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<File> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<File> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<File> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class FilesTypeHandler extends JsonTypeHandler.Default<List<File>> {
 }

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/FloatsTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 单精度浮点数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class FloatsTypeHandler extends BaseTypeHandler<List<Float>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Float>> REFERENCE = new TypeReference<List<Float>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Float> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Float> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Float> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Float> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class FloatsTypeHandler extends JsonTypeHandler.Default<List<Float>> {
 }

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/IntegersTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 整数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class IntegersTypeHandler extends BaseTypeHandler<List<Integer>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Integer>> REFERENCE = new TypeReference<List<Integer>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Integer> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Integer> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Integer> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class IntegersTypeHandler extends JsonTypeHandler.Default<List<Integer>> {
 }

+ 170 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/JsonTypeHandler.java

@@ -0,0 +1,170 @@
+package com.chelvc.framework.database.handler;
+
+import java.lang.reflect.Type;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import com.chelvc.framework.base.context.JacksonContextHolder;
+import com.chelvc.framework.common.util.ObjectUtils;
+import lombok.NonNull;
+import org.apache.ibatis.executor.result.ResultMapException;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeException;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.TypeReference;
+
+/**
+ * JSON类型处理器接口
+ *
+ * @param <T> 对象类型
+ * @author Woody
+ * @date 2024/1/30
+ */
+public interface JsonTypeHandler<T> extends TypeHandler<T> {
+    /**
+     * 获取对象类型
+     *
+     * @return 对象类型
+     */
+    Type getRawType();
+
+    @Override
+    default void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
+        if (parameter == null) {
+            if (jdbcType == null) {
+                throw new TypeException("JDBC requires that the JdbcType must be specified " +
+                        "for all nullable parameters.");
+            }
+            try {
+                ps.setNull(i, jdbcType.TYPE_CODE);
+            } catch (SQLException e) {
+                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " +
+                        jdbcType + " . Try setting a different JdbcType for this parameter " +
+                        "or a different jdbcTypeForNull configuration property. Cause: " + e, e);
+            }
+        } else {
+            try {
+                setNonNullParameter(ps, i, parameter, jdbcType);
+            } catch (Exception e) {
+                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " +
+                        jdbcType + " . Try setting a different JdbcType for this parameter " +
+                        "or a different configuration property. Cause: " + e, e);
+            }
+        }
+    }
+
+    @Override
+    default T getResult(ResultSet rs, String columnName) throws SQLException {
+        try {
+            return getNullableResult(rs, columnName);
+        } catch (Exception e) {
+            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  " +
+                    "Cause: " + e, e);
+        }
+    }
+
+    @Override
+    default T getResult(ResultSet rs, int columnIndex) throws SQLException {
+        try {
+            return getNullableResult(rs, columnIndex);
+        } catch (Exception e) {
+            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  " +
+                    "Cause: " + e, e);
+        }
+    }
+
+    @Override
+    default T getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        try {
+            return getNullableResult(cs, columnIndex);
+        } catch (Exception e) {
+            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement" +
+                    ".  Cause: " + e, e);
+        }
+    }
+
+    /**
+     * 设置参数值
+     *
+     * @param ps        PreparedStatement对象实例
+     * @param i         参数下标
+     * @param parameter 参数值
+     * @param jdbcType  数据库字段类型
+     * @throws SQLException SQL异常
+     */
+    default void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
+        if (ObjectUtils.isBlank(parameter)) {
+            ps.setNull(i, Types.VARCHAR);
+        } else {
+            ps.setString(i, JacksonContextHolder.serialize(parameter));
+        }
+    }
+
+    /**
+     * 获取字段结果
+     *
+     * @param rs         结果集合对象实例
+     * @param columnName 字段名称
+     * @return 字段值
+     * @throws SQLException SQL异常
+     */
+    @SuppressWarnings("unchecked")
+    default T getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        return (T) JacksonContextHolder.deserialize(rs.getString(columnName), this.getRawType());
+    }
+
+    /**
+     * 获取字段结果
+     *
+     * @param rs          结果集合对象实例
+     * @param columnIndex 字段下标
+     * @return 字段值
+     * @throws SQLException SQL异常
+     */
+    @SuppressWarnings("unchecked")
+    default T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        return (T) JacksonContextHolder.deserialize(rs.getString(columnIndex), this.getRawType());
+    }
+
+    /**
+     * 获取字段结果
+     *
+     * @param cs          CallableStatement对象实例
+     * @param columnIndex 字段下标
+     * @return 字段值
+     * @throws SQLException SQL异常
+     */
+    @SuppressWarnings("unchecked")
+    default T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        return (T) JacksonContextHolder.deserialize(cs.getString(columnIndex), this.getRawType());
+    }
+
+    /**
+     * JSON类型处理器默认实现
+     *
+     * @param <T> 对象类型
+     */
+    class Default<T> extends TypeReference<T> implements JsonTypeHandler<T> {
+    }
+
+    /**
+     * JSON类型处理器简单实现
+     *
+     * @param <T> 对象类型
+     */
+    class Simple<T> implements JsonTypeHandler<T> {
+        private final Type type;
+
+        public Simple(@NonNull Type type) {
+            this.type = type;
+        }
+
+        @Override
+        public Type getRawType() {
+            return this.type;
+        }
+    }
+}

+ 12 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/ListTypeHandler.java

@@ -0,0 +1,12 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.List;
+
+/**
+ * 列表字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class ListTypeHandler extends JsonTypeHandler.Default<List<?>> {
+}

+ 12 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/ListsTypeHandler.java

@@ -0,0 +1,12 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.List;
+
+/**
+ * 列表字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class ListsTypeHandler extends JsonTypeHandler.Default<List<List<?>>> {
+}

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/LongsTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 长整数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class LongsTypeHandler extends BaseTypeHandler<List<Long>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Long>> REFERENCE = new TypeReference<List<Long>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Long> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Long> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Long> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Long> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class LongsTypeHandler extends JsonTypeHandler.Default<List<Long>> {
 }

+ 12 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/MapTypeHandler.java

@@ -0,0 +1,12 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.Map;
+
+/**
+ * 字典字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class MapTypeHandler extends JsonTypeHandler.Default<Map<?, ?>> {
+}

+ 0 - 53
framework-database/src/main/java/com/chelvc/framework/database/handler/MappingTypeHandler.java

@@ -1,53 +0,0 @@
-package com.chelvc.framework.database.handler;
-
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Map;
-
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
-/**
- * 字典字段处理实现
- *
- * @author Woody
- * @date 2024/1/30
- */
-public class MappingTypeHandler extends BaseTypeHandler<Map<?, ?>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<Map<?, ?>> REFERENCE = new TypeReference<Map<?, ?>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, Map<?, ?> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public Map<?, ?> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public Map<?, ?> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public Map<?, ?> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
-}

+ 0 - 54
framework-database/src/main/java/com/chelvc/framework/database/handler/MappingsTypeHandler.java

@@ -1,54 +0,0 @@
-package com.chelvc.framework.database.handler;
-
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.List;
-import java.util.Map;
-
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
-/**
- * 字典列表字段处理实现
- *
- * @author Woody
- * @date 2024/1/30
- */
-public class MappingsTypeHandler extends BaseTypeHandler<List<Map<?, ?>>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Map<?, ?>>> REFERENCE = new TypeReference<List<Map<?, ?>>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Map<?, ?>> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Map<?, ?>> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Map<?, ?>> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Map<?, ?>> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
-}

+ 13 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/MapsTypeHandler.java

@@ -0,0 +1,13 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字典列表字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class MapsTypeHandler extends JsonTypeHandler.Default<List<Map<?, ?>>> {
+}

+ 1 - 34
framework-database/src/main/java/com/chelvc/framework/database/handler/ModificationTypeHandler.java

@@ -1,15 +1,6 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.Modification;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
 
 /**
  * 对象更新信息字段类型处理实现
@@ -17,29 +8,5 @@ import org.apache.ibatis.type.JdbcType;
  * @author Woody
  * @date 2024/1/30
  */
-public class ModificationTypeHandler extends BaseTypeHandler<Modification> {
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, Modification parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public Modification getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), Modification.class);
-    }
-
-    @Override
-    public Modification getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), Modification.class);
-    }
-
-    @Override
-    public Modification getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), Modification.class);
-    }
+public class ModificationTypeHandler extends JsonTypeHandler.Default<Modification> {
 }

+ 1 - 41
framework-database/src/main/java/com/chelvc/framework/database/handler/ModificationsTypeHandler.java

@@ -1,18 +1,8 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.Modification;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
 
 /**
  * 对象更新信息列表字段类型处理实现
@@ -20,35 +10,5 @@ import org.springframework.util.CollectionUtils;
  * @author Woody
  * @date 2024/1/30
  */
-public class ModificationsTypeHandler extends BaseTypeHandler<List<Modification>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Modification>> REFERENCE = new TypeReference<List<Modification>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Modification> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Modification> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Modification> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Modification> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class ModificationsTypeHandler extends JsonTypeHandler.Default<List<Modification>> {
 }

+ 1 - 6
framework-database/src/main/java/com/chelvc/framework/database/handler/PeriodTypeHandler.java

@@ -4,7 +4,6 @@ import java.sql.CallableStatement;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Types;
 
 import com.chelvc.framework.common.model.Period;
 import org.apache.ibatis.type.BaseTypeHandler;
@@ -20,11 +19,7 @@ public class PeriodTypeHandler extends BaseTypeHandler<Period> {
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, Period parameter, JdbcType jdbcType)
             throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, parameter.toString());
-        }
+        ps.setString(i, parameter.toString());
     }
 
     @Override

+ 1 - 40
framework-database/src/main/java/com/chelvc/framework/database/handler/PeriodsTypeHandler.java

@@ -1,17 +1,8 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.Period;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
 
 /**
  * 时间周期列表字段处理实现
@@ -19,35 +10,5 @@ import org.apache.ibatis.type.JdbcType;
  * @author Woody
  * @date 2024/1/30
  */
-public class PeriodsTypeHandler extends BaseTypeHandler<List<Period>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Period>> REFERENCE = new TypeReference<List<Period>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Period> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Period> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Period> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Period> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class PeriodsTypeHandler extends JsonTypeHandler.Default<List<Period>> {
 }

+ 1 - 6
framework-database/src/main/java/com/chelvc/framework/database/handler/RegionTypeHandler.java

@@ -4,7 +4,6 @@ import java.sql.CallableStatement;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Types;
 
 import com.chelvc.framework.common.model.Region;
 import org.apache.ibatis.type.BaseTypeHandler;
@@ -20,11 +19,7 @@ public class RegionTypeHandler extends BaseTypeHandler<Region> {
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, Region parameter, JdbcType jdbcType)
             throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.INTEGER);
-        } else {
-            ps.setLong(i, parameter.getCode());
-        }
+        ps.setLong(i, parameter.getCode());
     }
 
     @Override

+ 1 - 40
framework-database/src/main/java/com/chelvc/framework/database/handler/RegionsTypeHandler.java

@@ -1,17 +1,8 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.Region;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
 
 /**
  * 地区字段处理实现
@@ -19,35 +10,5 @@ import org.apache.ibatis.type.JdbcType;
  * @author Woody
  * @date 2024/1/30
  */
-public class RegionsTypeHandler extends BaseTypeHandler<List<Region>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Region>> REFERENCE = new TypeReference<List<Region>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Region> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (parameter == null) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Region> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Region> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Region> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class RegionsTypeHandler extends JsonTypeHandler.Default<List<Region>> {
 }

+ 12 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/SetTypeHandler.java

@@ -0,0 +1,12 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.Set;
+
+/**
+ * 集合字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class SetTypeHandler extends JsonTypeHandler.Default<Set<?>> {
+}

+ 13 - 0
framework-database/src/main/java/com/chelvc/framework/database/handler/SetsTypeHandler.java

@@ -0,0 +1,13 @@
+package com.chelvc.framework.database.handler;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 集合列表字段处理实现
+ *
+ * @author Woody
+ * @date 2024/1/30
+ */
+public class SetsTypeHandler extends JsonTypeHandler.Default<List<Set<?>>> {
+}

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/ShortsTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 短整数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class ShortsTypeHandler extends BaseTypeHandler<List<Short>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<Short>> REFERENCE = new TypeReference<List<Short>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<Short> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<Short> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<Short> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<Short> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class ShortsTypeHandler extends JsonTypeHandler.Default<List<Short>> {
 }

+ 1 - 42
framework-database/src/main/java/com/chelvc/framework/database/handler/StringsTypeHandler.java

@@ -1,53 +1,12 @@
 package com.chelvc.framework.database.handler;
 
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
 import java.util.List;
 
-import com.chelvc.framework.base.context.JacksonContextHolder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.type.BaseTypeHandler;
-import org.apache.ibatis.type.JdbcType;
-import org.springframework.util.CollectionUtils;
-
 /**
  * 长整数列表字段处理实现
  *
  * @author Woody
  * @date 2024/1/30
  */
-public class StringsTypeHandler extends BaseTypeHandler<List<String>> {
-    /**
-     * 类型引用实例
-     */
-    private static final TypeReference<List<String>> REFERENCE = new TypeReference<List<String>>() {
-    };
-
-    @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType)
-            throws SQLException {
-        if (CollectionUtils.isEmpty(parameter)) {
-            ps.setNull(i, Types.VARCHAR);
-        } else {
-            ps.setString(i, JacksonContextHolder.serialize(parameter));
-        }
-    }
-
-    @Override
-    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnName), REFERENCE);
-    }
-
-    @Override
-    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(rs.getString(columnIndex), REFERENCE);
-    }
-
-    @Override
-    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
-        return JacksonContextHolder.deserialize(cs.getString(columnIndex), REFERENCE);
-    }
+public class StringsTypeHandler extends JsonTypeHandler.Default<List<String>> {
 }

+ 411 - 0
framework-database/src/main/java/com/chelvc/framework/database/support/JsonTypeHandlerBinder.java

@@ -0,0 +1,411 @@
+package com.chelvc.framework.database.support;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.chelvc.framework.common.model.File;
+import com.chelvc.framework.common.model.Modification;
+import com.chelvc.framework.common.model.Period;
+import com.chelvc.framework.common.model.Reference;
+import com.chelvc.framework.common.model.Region;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.database.context.DatabaseContextHolder;
+import com.chelvc.framework.database.handler.BooleansTypeHandler;
+import com.chelvc.framework.database.handler.DoublesTypeHandler;
+import com.chelvc.framework.database.handler.FilesTypeHandler;
+import com.chelvc.framework.database.handler.FloatsTypeHandler;
+import com.chelvc.framework.database.handler.IntegersTypeHandler;
+import com.chelvc.framework.database.handler.JsonTypeHandler;
+import com.chelvc.framework.database.handler.ListTypeHandler;
+import com.chelvc.framework.database.handler.ListsTypeHandler;
+import com.chelvc.framework.database.handler.LongsTypeHandler;
+import com.chelvc.framework.database.handler.MapTypeHandler;
+import com.chelvc.framework.database.handler.MapsTypeHandler;
+import com.chelvc.framework.database.handler.ModificationsTypeHandler;
+import com.chelvc.framework.database.handler.PeriodsTypeHandler;
+import com.chelvc.framework.database.handler.RegionsTypeHandler;
+import com.chelvc.framework.database.handler.SetTypeHandler;
+import com.chelvc.framework.database.handler.SetsTypeHandler;
+import com.chelvc.framework.database.handler.ShortsTypeHandler;
+import com.chelvc.framework.database.handler.StringsTypeHandler;
+import com.google.common.collect.Maps;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtConstructor;
+import javassist.CtMethod;
+import javassist.CtNewMethod;
+import javassist.NotFoundException;
+import lombok.NonNull;
+import org.apache.ibatis.parsing.XNode;
+import org.apache.ibatis.type.UnknownTypeHandler;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * JSON类型绑定处理工具类
+ *
+ * @author Woody
+ * @date 2024/4/5
+ */
+public final class JsonTypeHandlerBinder {
+    /**
+     * JSON类型处理器计数器
+     */
+    private static final AtomicLong JSON_HANDLER_COUNTER = new AtomicLong(0);
+
+    /**
+     * JSON类型/处理器映射表
+     */
+    private static final Map<Type, Class<?>> JSON_HANDLER_CLASSES = Maps.newConcurrentMap();
+
+    private JsonTypeHandlerBinder() {
+    }
+
+    /**
+     * 初始化JSON类型处理器绑定工具
+     */
+    public static void initialize() {
+        try {
+            initializeMapperListener();
+            initializeTableInfoListener();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 初始化Mapper解析监听器
+     *
+     * @throws Exception 初始化异常
+     */
+    private static void initializeMapperListener() throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+
+        // 替换XMLMapperBuilder.parse方法逻辑,加入Mapper解析监听回调逻辑
+        CtClass clazz = pool.get("org.apache.ibatis.builder.xml.XMLMapperBuilder");
+        CtMethod method = clazz.getDeclaredMethod("parse");
+        method.setBody(String.format("{\n" +
+                "if (!this.configuration.isResourceLoaded(this.resource)) {\n" +
+                "   %s node = this.parser.evalNode(\"/mapper\");\n" +
+                "   %s.configure(node);\n" +
+                "   this.configurationElement(node);\n" +
+                "   this.configuration.addLoadedResource(this.resource);\n" +
+                "   this.bindMapperForNamespace();\n" +
+                "}\n" +
+                "this.parsePendingResultMaps();\n" +
+                "this.parsePendingCacheRefs();\n" +
+                "this.parsePendingStatements();\n" +
+                "}", XNode.class.getName(), JsonTypeHandlerBinder.class.getName()));
+        clazz.toClass();
+    }
+
+    /**
+     * 初始化数据模型表信息监听器
+     *
+     * @throws Exception 初始化异常
+     */
+    private static void initializeTableInfoListener() throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+
+        // 获取原始initTableFields方法
+        CtMethod initTableFields = null;
+        CtClass clazz = pool.get("com.baomidou.mybatisplus.core.metadata.TableInfoHelper");
+        for (CtMethod method : clazz.getDeclaredMethods()) {
+            if (method.getName().equals("initTableFields")) {
+                initTableFields = method;
+            }
+        }
+        if (initTableFields == null) {
+            throw new NotFoundException("initTableFields");
+        }
+
+        // 重命名initTableFields方法名
+        CtMethod copy = CtNewMethod.copy(initTableFields, clazz, null);
+        copy.setName("doInitTableFields");
+        clazz.removeMethod(initTableFields);
+        clazz.addMethod(copy);
+
+        // 替换initTableFields方法,加入表信息初始化回调逻辑
+        CtMethod replace = CtNewMethod.make(String.format("private static void initTableFields(" +
+                        "Class clazz, %s globalConfig, %s tableInfo, java.util.List excludeProperty) {\n" +
+                        "doInitTableFields(clazz, globalConfig, tableInfo, excludeProperty);\n" +
+                        "%s.configure(tableInfo);\n" +
+                        "}",
+                GlobalConfig.class.getName(), TableInfo.class.getName(), JsonTypeHandlerBinder.class.getName()), clazz
+        );
+        clazz.addMethod(replace);
+        clazz.toClass();
+    }
+
+    /**
+     * 绑定字段JSON类型处理器
+     *
+     * @param field 表字段信息
+     */
+    private static void bindJsonHandler(TableFieldInfo field) {
+        Class<?> handler = lookupJsonHandlerClass(field.getField());
+        ObjectUtils.setValue(field, "typeHandler", handler);
+        String el = ObjectUtils.getValue(field, "el") + ",typeHandler=" + handler.getName();
+        ObjectUtils.setValue(field, "el", el);
+    }
+
+    /**
+     * 查找JSON类型处理器对象
+     *
+     * @param field JSON字段实例
+     * @return JSON类型处理器对象类型
+     */
+    private static Class<?> lookupJsonHandlerClass(Field field) {
+        Type type = field.getGenericType();
+        if (ObjectUtils.isOnlyMap(type)) {
+            return MapTypeHandler.class;
+        } else if (ObjectUtils.isOnlySet(type)) {
+            return SetTypeHandler.class;
+        } else if (ObjectUtils.isOnlyList(type)) {
+            return ListTypeHandler.class;
+        } else if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() == List.class) {
+            Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
+            if (arg == int.class || arg == Integer.class) {
+                return IntegersTypeHandler.class;
+            } else if (arg == short.class || arg == Short.class) {
+                return ShortsTypeHandler.class;
+            } else if (arg == long.class || arg == Long.class) {
+                return LongsTypeHandler.class;
+            } else if (arg == float.class || arg == Float.class) {
+                return FloatsTypeHandler.class;
+            } else if (arg == double.class || arg == Double.class) {
+                return DoublesTypeHandler.class;
+            } else if (arg == boolean.class || arg == Boolean.class) {
+                return BooleansTypeHandler.class;
+            } else if (arg == String.class) {
+                return StringsTypeHandler.class;
+            } else if (arg == File.class) {
+                return FilesTypeHandler.class;
+            } else if (arg == Period.class) {
+                return PeriodsTypeHandler.class;
+            } else if (arg == Region.class) {
+                return RegionsTypeHandler.class;
+            } else if (arg == Modification.class) {
+                return ModificationsTypeHandler.class;
+            } else if (ObjectUtils.isOnlyMap(arg)) {
+                return MapsTypeHandler.class;
+            } else if (ObjectUtils.isOnlySet(arg)) {
+                return SetsTypeHandler.class;
+            } else if (ObjectUtils.isOnlyList(arg)) {
+                return ListsTypeHandler.class;
+            }
+        }
+        return JSON_HANDLER_CLASSES.computeIfAbsent(type, t -> {
+            try {
+                return initializeJsonHandlerClass(field);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+
+    /**
+     * 初始化JSON类型处理器对象
+     *
+     * @param field JSON字段实例
+     * @return JSON类型处理器对象类型
+     * @throws Exception 对象初始化异常
+     */
+    private static Class<?> initializeJsonHandlerClass(Field field) throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+        CtClass handler = pool.makeClass(String.format(
+                "%s.JsonTypeHandler_%d",
+                field.getDeclaringClass().getPackage().getName(),
+                JSON_HANDLER_COUNTER.incrementAndGet()
+        ));
+        handler.setSuperclass(pool.get(JsonTypeHandler.Simple.class.getName()));
+        CtConstructor constructor = new CtConstructor(new CtClass[]{}, handler);
+        constructor.setBody(String.format("{super(%s);}", ObjectUtils.analyse(field.getGenericType())));
+        handler.addConstructor(constructor);
+        return handler.toClass();
+    }
+
+    /**
+     * 判断字段是否可以配置JSON类型处理器
+     *
+     * @param field 数据模型字段
+     * @return true/false
+     */
+    private static boolean isJsonHandlerConfigurable(Field field) {
+        TableField annotation = field.getAnnotation(TableField.class);
+        if (annotation == null || annotation.exist()) {
+            Class<?> handler = ObjectUtils.ifNull(annotation, TableField::typeHandler);
+            return (handler == null || handler == UnknownTypeHandler.class)
+                    && !DatabaseContextHolder.isTypeHandlerRegistered(field.getType());
+        }
+        return false;
+    }
+
+    /**
+     * 判断数据模型是否可以配置JSON类型处理器
+     *
+     * @param clazz 数据模型对象
+     * @return true/false
+     */
+    private static boolean isJsonHandlerConfigurable(Class<?> clazz) {
+        Reference<Boolean> configurable = new Reference<>();
+        ObjectUtils.iterateFields(clazz, (i, field) -> {
+            if (isJsonHandlerConfigurable(field)) {
+                configurable.set(true);
+                return false;
+            }
+            return true;
+        });
+        return configurable.get(false);
+    }
+
+    /**
+     * 判断表字段是否可以配置JSON类型处理器
+     *
+     * @param field 表字段信息
+     * @return true/false
+     */
+    private static boolean isJsonHandlerConfigurable(TableFieldInfo field) {
+        return field.getTypeHandler() == null
+                && !DatabaseContextHolder.isTypeHandlerRegistered(field.getPropertyType());
+    }
+
+    /**
+     * 获取查询结果类型
+     *
+     * @param node 查询节点
+     * @return 对象类型
+     */
+    private static Class<?> getSelectResultType(XNode node) {
+        String type = node.getStringAttribute("resultType");
+        if (StringUtils.isEmpty(type)) {
+            return null;
+        }
+        try {
+            return Class.forName(type);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取查询结果类型
+     *
+     * @param node 查询节点
+     * @return 对象类型
+     */
+    private static Class<?> getResultMappingType(XNode node) {
+        try {
+            return Class.forName(node.getStringAttribute("type"));
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 初始化resultMap
+     *
+     * @param document xml文档对象
+     * @param clazz    对象类型
+     * @return 节点元素
+     */
+    private static Element initializeResultMapping(Document document, Class<?> clazz) {
+        // 构建resultMap节点元素
+        Element element = document.createElement("resultMap");
+        String id = "__" + StringUtils.hump2underscore(clazz.getSimpleName()).toUpperCase() + "_RESULT_MAP";
+        element.setAttribute("id", id);
+        element.setAttribute("type", clazz.getName());
+
+        // 添加resultMap字段映射
+        ObjectUtils.iterateFields(clazz, (i, field) -> {
+            String tag = field.isAnnotationPresent(TableId.class) ? "id" : "result";
+            String column = StringUtils.ifEmpty(
+                    ObjectUtils.ifNull(field.getAnnotation(TableField.class), TableField::value),
+                    () -> StringUtils.hump2underscore(field.getName())
+            );
+            Element child = document.createElement(tag);
+            child.setAttribute("column", column);
+            child.setAttribute("property", field.getName());
+
+            // 设置json类型处理器
+            if (isJsonHandlerConfigurable(field)) {
+                Class<?> handler = lookupJsonHandlerClass(field);
+                child.setAttribute("typeHandler", handler.getName());
+            }
+            element.appendChild(child);
+        });
+        return element;
+    }
+
+    /**
+     * 配置Mapper XML节点
+     *
+     * @param mapper Mapper XML节点
+     */
+    public static void configure(@NonNull XNode mapper) {
+        // 设置已有resultMap的json类型处理器
+        List<XNode> maps = mapper.evalNodes("resultMap");
+        if (ObjectUtils.notEmpty(maps)) {
+            maps.forEach(map -> {
+                Class<?> clazz = getResultMappingType(map);
+                for (XNode child : map.getChildren()) {
+                    // 判断当前是有存在typeHandler,如果不存在且字段满足配置json类型处理器条件,则自动绑定json类型处理器
+                    if (StringUtils.isEmpty(child.getStringAttribute("typeHandler"))) {
+                        String property = child.getStringAttribute("property");
+                        Field field = ObjectUtils.getField(clazz, property);
+                        if (isJsonHandlerConfigurable(field)) {
+                            Class<?> handler = lookupJsonHandlerClass(field);
+                            ((Element) child.getNode()).setAttribute("typeHandler", handler.getName());
+                        }
+                    }
+                }
+            });
+        }
+
+        // 设置resultType对应的resultMap,并将原来的resultType替换成resultMap
+        Document document = mapper.getNode().getOwnerDocument();
+        List<XNode> selects = mapper.evalNodes("select");
+        if (ObjectUtils.notEmpty(selects)) {
+            Map<Class<?>, Element> cache = Maps.newHashMap();
+            selects.forEach(select -> {
+                // 判断resultType是否可配置json类型处理器
+                Class<?> clazz = getSelectResultType(select);
+                if (clazz != null && isJsonHandlerConfigurable(clazz)) {
+                    // 构建并注册resultMap
+                    Element map = cache.computeIfAbsent(clazz, c -> initializeResultMapping(document, clazz));
+                    mapper.getNode().appendChild(map);
+
+                    // 用户新生成的resultMap替换resultType
+                    Element element = (Element) select.getNode();
+                    element.removeAttribute("resultType");
+                    element.setAttribute("resultMap", map.getAttribute("id"));
+                }
+            });
+        }
+    }
+
+    /**
+     * 配置数据模型表信息
+     *
+     * @param table 数据模型表信息
+     */
+    public static void configure(@NonNull TableInfo table) {
+        // 自动绑定JSON类型字段处理器
+        List<TableFieldInfo> fields = table.getFieldList();
+        if (ObjectUtils.notEmpty(fields)) {
+            fields.stream().filter(JsonTypeHandlerBinder::isJsonHandlerConfigurable)
+                    .forEach(JsonTypeHandlerBinder::bindJsonHandler);
+        }
+    }
+}