|
@@ -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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|