ソースを参照

Json处理器支持自定义XML.

nieqiurong 1 年間 前
コミット
2381495af9
15 ファイル変更700 行追加24 行削除
  1. 3 5
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java
  2. 131 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperBuilderAssistant.java
  3. 2 3
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLConfigBuilder.java
  4. 451 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLMapperBuilder.java
  5. 5 1
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/IJsonTypeHandler.java
  6. 2 6
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableFieldInfo.java
  7. 52 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/MybatisUtils.java
  8. 2 2
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/spring/MybatisSqlSessionFactoryBean.java
  9. 2 2
      mybatis-plus-extension/src/test/kotlin/com/baomidou/mybatisplus/test/kotlin/BaseDbTest.kt
  10. 2 2
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/BaseDbTest.java
  11. 22 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/resultmap/Entity.java
  12. 21 1
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/resultmap/ResultMapTest.java
  13. 3 0
      mybatis-plus/src/test/resources/com/baomidou/mybatisplus/test/resultmap/EntityMapper.xml
  14. 1 1
      spring-boot-starter/mybatis-plus-boot-starter/src/test/java/com/baomidou/mybatisplus/test/pom/GeneratePomTest.java
  15. 1 1
      spring-boot-starter/mybatis-plus-spring-boot3-starter/src/test/java/com/baomidou/mybatisplus/test/pom/GeneratePomTest.java

+ 3 - 5
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java

@@ -28,11 +28,9 @@ import org.apache.ibatis.binding.MapperMethod;
 import org.apache.ibatis.builder.BuilderException;
 import org.apache.ibatis.builder.CacheRefResolver;
 import org.apache.ibatis.builder.IncompleteElementException;
-import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
 import org.apache.ibatis.builder.annotation.MethodResolver;
 import org.apache.ibatis.builder.annotation.ProviderSqlSource;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.cursor.Cursor;
 import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
 import org.apache.ibatis.executor.keygen.KeyGenerator;
@@ -77,13 +75,13 @@ public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
         .collect(Collectors.toSet());
 
     private final Configuration configuration;
-    private final MapperBuilderAssistant assistant;
+    private final MybatisMapperBuilderAssistant assistant;
     private final Class<?> type;
 
     public MybatisMapperAnnotationBuilder(Configuration configuration, Class<?> type) {
         super(configuration, type);
         String resource = type.getName().replace(StringPool.DOT, StringPool.SLASH) + ".java (best guess)";
-        this.assistant = new MapperBuilderAssistant(configuration, resource);
+        this.assistant = new MybatisMapperBuilderAssistant(configuration, resource);
         this.configuration = configuration;
         this.type = type;
     }
@@ -167,7 +165,7 @@ public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
                 }
             }
             if (inputStream != null) {
-                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
+                MybatisXMLMapperBuilder xmlParser = new MybatisXMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
                 xmlParser.parse();
             }
         }

+ 131 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperBuilderAssistant.java

@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2011-2023, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.core;
+
+import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
+import com.baomidou.mybatisplus.core.toolkit.MybatisUtils;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.apache.ibatis.mapping.ResultFlag;
+import org.apache.ibatis.mapping.ResultMapping;
+import org.apache.ibatis.reflection.MetaClass;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.UnknownTypeHandler;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * 重写了原生方法.
+ *
+ * @author nieqiurong
+ * @see MapperBuilderAssistant
+ * @since 3.5.6
+ */
+public class MybatisMapperBuilderAssistant extends MapperBuilderAssistant {
+
+    public MybatisMapperBuilderAssistant(Configuration configuration, String resource) {
+        super(configuration, resource);
+    }
+
+    public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
+                                            JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
+                                            Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn,
+                                            boolean lazy) {
+        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
+        TypeHandler<?> typeHandlerInstance = null;
+        if (typeHandler != null && typeHandler != UnknownTypeHandler.class) {
+            if (IJsonTypeHandler.class.isAssignableFrom(typeHandler)) {
+                try {
+                    Field field = resultType.getDeclaredField(property);
+                    typeHandlerInstance = MybatisUtils.newJsonTypeHandler(typeHandler, javaTypeClass, field);
+                } catch (NoSuchFieldException e) {
+                    //ignore 降级兼容处理
+                    typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
+                }
+            } else {
+                typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
+            }
+        }
+        List<ResultMapping> composites;
+        if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
+            composites = Collections.emptyList();
+        } else {
+            composites = parseCompositeColumnName(column);
+        }
+        return new ResultMapping.Builder(configuration, property, column, javaTypeClass).jdbcType(jdbcType)
+            .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
+            .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)).resultSet(resultSet)
+            .typeHandler(typeHandlerInstance).flags(flags == null ? new ArrayList<>() : flags).composites(composites)
+            .notNullColumns(parseMultipleColumnNames(notNullColumn)).columnPrefix(columnPrefix).foreignColumn(foreignColumn)
+            .lazy(lazy).build();
+    }
+
+
+    private Set<String> parseMultipleColumnNames(String columnName) {
+        Set<String> columns = new HashSet<>();
+        if (columnName != null) {
+            if (columnName.indexOf(',') > -1) {
+                StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
+                while (parser.hasMoreTokens()) {
+                    String column = parser.nextToken();
+                    columns.add(column);
+                }
+            } else {
+                columns.add(columnName);
+            }
+        }
+        return columns;
+    }
+
+
+    private List<ResultMapping> parseCompositeColumnName(String columnName) {
+        List<ResultMapping> composites = new ArrayList<>();
+        if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
+            StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
+            while (parser.hasMoreTokens()) {
+                String property = parser.nextToken();
+                String column = parser.nextToken();
+                ResultMapping complexResultMapping = new ResultMapping.Builder(configuration, property, column,
+                    configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
+                composites.add(complexResultMapping);
+            }
+        }
+        return composites;
+    }
+
+    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
+        if (javaType == null && property != null) {
+            try {
+                MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
+                javaType = metaResultType.getSetterType(property);
+            } catch (Exception e) {
+                // ignore, following null check statement will deal with the situation
+            }
+        }
+        if (javaType == null) {
+            javaType = Object.class;
+        }
+        return javaType;
+    }
+
+}

+ 2 - 3
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLConfigBuilder.java

@@ -18,7 +18,6 @@ package com.baomidou.mybatisplus.core;
 import org.apache.ibatis.builder.BaseBuilder;
 import org.apache.ibatis.builder.BuilderException;
 import org.apache.ibatis.builder.xml.XMLConfigBuilder;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
 import org.apache.ibatis.datasource.DataSourceFactory;
 import org.apache.ibatis.executor.ErrorContext;
@@ -377,13 +376,13 @@ public class MybatisXMLConfigBuilder extends BaseBuilder {
                     if (resource != null && url == null && mapperClass == null) {
                         ErrorContext.instance().resource(resource);
                         try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
-                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
+                            MybatisXMLMapperBuilder mapperParser = new MybatisXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                             mapperParser.parse();
                         }
                     } else if (resource == null && url != null && mapperClass == null) {
                         ErrorContext.instance().resource(url);
                         try (InputStream inputStream = Resources.getUrlAsStream(url)) {
-                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
+                            MybatisXMLMapperBuilder mapperParser = new MybatisXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                             mapperParser.parse();
                         }
                     } else if (resource == null && url == null && mapperClass != null) {

+ 451 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisXMLMapperBuilder.java

@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2011-2023, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.core;
+
+import org.apache.ibatis.builder.BaseBuilder;
+import org.apache.ibatis.builder.BuilderException;
+import org.apache.ibatis.builder.CacheRefResolver;
+import org.apache.ibatis.builder.IncompleteElementException;
+import org.apache.ibatis.builder.ResultMapResolver;
+import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
+import org.apache.ibatis.builder.xml.XMLStatementBuilder;
+import org.apache.ibatis.cache.Cache;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.mapping.Discriminator;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.ParameterMode;
+import org.apache.ibatis.mapping.ResultFlag;
+import org.apache.ibatis.mapping.ResultMap;
+import org.apache.ibatis.mapping.ResultMapping;
+import org.apache.ibatis.parsing.XNode;
+import org.apache.ibatis.parsing.XPathParser;
+import org.apache.ibatis.reflection.MetaClass;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * 重写了{@link org.apache.ibatis.builder.xml.XMLMapperBuilder} 替换了 {@link org.apache.ibatis.builder.MapperBuilderAssistant}
+ *
+ * @author nieqiurong
+ * @since 3.5.6
+ */
+public class MybatisXMLMapperBuilder extends BaseBuilder {
+
+    private final XPathParser parser;
+    private final MybatisMapperBuilderAssistant builderAssistant;
+    private final Map<String, XNode> sqlFragments;
+    private final String resource;
+
+    @Deprecated
+    public MybatisXMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
+                                   String namespace) {
+        this(reader, configuration, resource, sqlFragments);
+        this.builderAssistant.setCurrentNamespace(namespace);
+    }
+
+    @Deprecated
+    public MybatisXMLMapperBuilder(Reader reader, Configuration configuration, String resource,
+                                   Map<String, XNode> sqlFragments) {
+        this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
+            resource, sqlFragments);
+    }
+
+    public MybatisXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
+                                   Map<String, XNode> sqlFragments, String namespace) {
+        this(inputStream, configuration, resource, sqlFragments);
+        this.builderAssistant.setCurrentNamespace(namespace);
+    }
+
+    public MybatisXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
+                                   Map<String, XNode> sqlFragments) {
+        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
+            resource, sqlFragments);
+    }
+
+    private MybatisXMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
+                                    Map<String, XNode> sqlFragments) {
+        super(configuration);
+        this.builderAssistant = new MybatisMapperBuilderAssistant(configuration, resource);
+        this.parser = parser;
+        this.sqlFragments = sqlFragments;
+        this.resource = resource;
+    }
+
+    public void parse() {
+        if (!configuration.isResourceLoaded(resource)) {
+            configurationElement(parser.evalNode("/mapper"));
+            configuration.addLoadedResource(resource);
+            bindMapperForNamespace();
+        }
+        parsePendingResultMaps();
+        parsePendingCacheRefs();
+        parsePendingStatements();
+    }
+
+    public XNode getSqlFragment(String refid) {
+        return sqlFragments.get(refid);
+    }
+
+    private void configurationElement(XNode context) {
+        try {
+            String namespace = context.getStringAttribute("namespace");
+            if (namespace == null || namespace.isEmpty()) {
+                throw new BuilderException("Mapper's namespace cannot be empty");
+            }
+            builderAssistant.setCurrentNamespace(namespace);
+            cacheRefElement(context.evalNode("cache-ref"));
+            cacheElement(context.evalNode("cache"));
+            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
+            resultMapElements(context.evalNodes("/mapper/resultMap"));
+            sqlElement(context.evalNodes("/mapper/sql"));
+            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
+        } catch (Exception e) {
+            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
+        }
+    }
+
+    private void buildStatementFromContext(List<XNode> list) {
+        if (configuration.getDatabaseId() != null) {
+            buildStatementFromContext(list, configuration.getDatabaseId());
+        }
+        buildStatementFromContext(list, null);
+    }
+
+    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
+        for (XNode context : list) {
+            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
+                requiredDatabaseId);
+            try {
+                statementParser.parseStatementNode();
+            } catch (IncompleteElementException e) {
+                configuration.addIncompleteStatement(statementParser);
+            }
+        }
+    }
+
+    private void parsePendingResultMaps() {
+        Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
+        synchronized (incompleteResultMaps) {
+            Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
+            while (iter.hasNext()) {
+                try {
+                    iter.next().resolve();
+                    iter.remove();
+                } catch (IncompleteElementException e) {
+                    // ResultMap is still missing a resource...
+                }
+            }
+        }
+    }
+
+    private void parsePendingCacheRefs() {
+        Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
+        synchronized (incompleteCacheRefs) {
+            Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
+            while (iter.hasNext()) {
+                try {
+                    iter.next().resolveCacheRef();
+                    iter.remove();
+                } catch (IncompleteElementException e) {
+                    // Cache ref is still missing a resource...
+                }
+            }
+        }
+    }
+
+    private void parsePendingStatements() {
+        Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
+        synchronized (incompleteStatements) {
+            Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
+            while (iter.hasNext()) {
+                try {
+                    iter.next().parseStatementNode();
+                    iter.remove();
+                } catch (IncompleteElementException e) {
+                    // Statement is still missing a resource...
+                }
+            }
+        }
+    }
+
+    private void cacheRefElement(XNode context) {
+        if (context != null) {
+            configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
+            CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant,
+                context.getStringAttribute("namespace"));
+            try {
+                cacheRefResolver.resolveCacheRef();
+            } catch (IncompleteElementException e) {
+                configuration.addIncompleteCacheRef(cacheRefResolver);
+            }
+        }
+    }
+
+    private void cacheElement(XNode context) {
+        if (context != null) {
+            String type = context.getStringAttribute("type", "PERPETUAL");
+            Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
+            String eviction = context.getStringAttribute("eviction", "LRU");
+            Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
+            Long flushInterval = context.getLongAttribute("flushInterval");
+            Integer size = context.getIntAttribute("size");
+            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
+            boolean blocking = context.getBooleanAttribute("blocking", false);
+            Properties props = context.getChildrenAsProperties();
+            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
+        }
+    }
+
+    private void parameterMapElement(List<XNode> list) {
+        for (XNode parameterMapNode : list) {
+            String id = parameterMapNode.getStringAttribute("id");
+            String type = parameterMapNode.getStringAttribute("type");
+            Class<?> parameterClass = resolveClass(type);
+            List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
+            List<ParameterMapping> parameterMappings = new ArrayList<>();
+            for (XNode parameterNode : parameterNodes) {
+                String property = parameterNode.getStringAttribute("property");
+                String javaType = parameterNode.getStringAttribute("javaType");
+                String jdbcType = parameterNode.getStringAttribute("jdbcType");
+                String resultMap = parameterNode.getStringAttribute("resultMap");
+                String mode = parameterNode.getStringAttribute("mode");
+                String typeHandler = parameterNode.getStringAttribute("typeHandler");
+                Integer numericScale = parameterNode.getIntAttribute("numericScale");
+                ParameterMode modeEnum = resolveParameterMode(mode);
+                Class<?> javaTypeClass = resolveClass(javaType);
+                JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+                Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
+                ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
+                    javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
+                parameterMappings.add(parameterMapping);
+            }
+            builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
+        }
+    }
+
+    private void resultMapElements(List<XNode> list) {
+        for (XNode resultMapNode : list) {
+            try {
+                resultMapElement(resultMapNode);
+            } catch (IncompleteElementException e) {
+                // ignore, it will be retried
+            }
+        }
+    }
+
+    private ResultMap resultMapElement(XNode resultMapNode) {
+        return resultMapElement(resultMapNode, Collections.emptyList(), null);
+    }
+
+    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
+                                       Class<?> enclosingType) {
+        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
+        String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
+            resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
+        Class<?> typeClass = resolveClass(type);
+        if (typeClass == null) {
+            typeClass = inheritEnclosingType(resultMapNode, enclosingType);
+        }
+        Discriminator discriminator = null;
+        List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
+        List<XNode> resultChildren = resultMapNode.getChildren();
+        for (XNode resultChild : resultChildren) {
+            if ("constructor".equals(resultChild.getName())) {
+                processConstructorElement(resultChild, typeClass, resultMappings);
+            } else if ("discriminator".equals(resultChild.getName())) {
+                discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
+            } else {
+                List<ResultFlag> flags = new ArrayList<>();
+                if ("id".equals(resultChild.getName())) {
+                    flags.add(ResultFlag.ID);
+                }
+                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
+            }
+        }
+        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
+        String extend = resultMapNode.getStringAttribute("extends");
+        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
+        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
+            resultMappings, autoMapping);
+        try {
+            return resultMapResolver.resolve();
+        } catch (IncompleteElementException e) {
+            configuration.addIncompleteResultMap(resultMapResolver);
+            throw e;
+        }
+    }
+
+    protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
+        if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
+            String property = resultMapNode.getStringAttribute("property");
+            if (property != null && enclosingType != null) {
+                MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
+                return metaResultType.getSetterType(property);
+            }
+        } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
+            return enclosingType;
+        }
+        return null;
+    }
+
+    private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
+        List<XNode> argChildren = resultChild.getChildren();
+        for (XNode argChild : argChildren) {
+            List<ResultFlag> flags = new ArrayList<>();
+            flags.add(ResultFlag.CONSTRUCTOR);
+            if ("idArg".equals(argChild.getName())) {
+                flags.add(ResultFlag.ID);
+            }
+            resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
+        }
+    }
+
+    private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
+                                                      List<ResultMapping> resultMappings) {
+        String column = context.getStringAttribute("column");
+        String javaType = context.getStringAttribute("javaType");
+        String jdbcType = context.getStringAttribute("jdbcType");
+        String typeHandler = context.getStringAttribute("typeHandler");
+        Class<?> javaTypeClass = resolveClass(javaType);
+        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
+        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+        Map<String, String> discriminatorMap = new HashMap<>();
+        for (XNode caseChild : context.getChildren()) {
+            String value = caseChild.getStringAttribute("value");
+            String resultMap = caseChild.getStringAttribute("resultMap",
+                processNestedResultMappings(caseChild, resultMappings, resultType));
+            discriminatorMap.put(value, resultMap);
+        }
+        return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
+            discriminatorMap);
+    }
+
+    private void sqlElement(List<XNode> list) {
+        if (configuration.getDatabaseId() != null) {
+            sqlElement(list, configuration.getDatabaseId());
+        }
+        sqlElement(list, null);
+    }
+
+    private void sqlElement(List<XNode> list, String requiredDatabaseId) {
+        for (XNode context : list) {
+            String databaseId = context.getStringAttribute("databaseId");
+            String id = context.getStringAttribute("id");
+            id = builderAssistant.applyCurrentNamespace(id, false);
+            if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
+                sqlFragments.put(id, context);
+            }
+        }
+    }
+
+    private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
+        if (requiredDatabaseId != null) {
+            return requiredDatabaseId.equals(databaseId);
+        }
+        if (databaseId != null) {
+            return false;
+        }
+        if (!this.sqlFragments.containsKey(id)) {
+            return true;
+        }
+        // skip this fragment if there is a previous one with a not null databaseId
+        XNode context = this.sqlFragments.get(id);
+        return context.getStringAttribute("databaseId") == null;
+    }
+
+    private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
+        String property;
+        if (flags.contains(ResultFlag.CONSTRUCTOR)) {
+            property = context.getStringAttribute("name");
+        } else {
+            property = context.getStringAttribute("property");
+        }
+        String column = context.getStringAttribute("column");
+        String javaType = context.getStringAttribute("javaType");
+        String jdbcType = context.getStringAttribute("jdbcType");
+        String nestedSelect = context.getStringAttribute("select");
+        String nestedResultMap = context.getStringAttribute("resultMap",
+            () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
+        String notNullColumn = context.getStringAttribute("notNullColumn");
+        String columnPrefix = context.getStringAttribute("columnPrefix");
+        String typeHandler = context.getStringAttribute("typeHandler");
+        String resultSet = context.getStringAttribute("resultSet");
+        String foreignColumn = context.getStringAttribute("foreignColumn");
+        boolean lazy = "lazy"
+            .equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
+        Class<?> javaTypeClass = resolveClass(javaType);
+        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
+        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
+            nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
+    }
+
+    private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings,
+                                               Class<?> enclosingType) {
+        if (Arrays.asList("association", "collection", "case").contains(context.getName())
+            && context.getStringAttribute("select") == null) {
+            validateCollection(context, enclosingType);
+            ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
+            return resultMap.getId();
+        }
+        return null;
+    }
+
+    protected void validateCollection(XNode context, Class<?> enclosingType) {
+        if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
+            && context.getStringAttribute("javaType") == null) {
+            MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
+            String property = context.getStringAttribute("property");
+            if (!metaResultType.hasSetter(property)) {
+                throw new BuilderException(
+                    "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
+            }
+        }
+    }
+
+    private void bindMapperForNamespace() {
+        String namespace = builderAssistant.getCurrentNamespace();
+        if (namespace != null) {
+            Class<?> boundType = null;
+            try {
+                boundType = Resources.classForName(namespace);
+            } catch (ClassNotFoundException e) {
+                // ignore, bound type is not required
+            }
+            if (boundType != null && !configuration.hasMapper(boundType)) {
+                // Spring may not know the real resource name so we set a flag
+                // to prevent loading again this resource from the mapper interface
+                // look at MapperAnnotationBuilder#loadXmlResource
+                configuration.addLoadedResource("namespace:" + namespace);
+                configuration.addMapper(boundType);
+            }
+        }
+    }
+}

+ 5 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/IJsonTypeHandler.java

@@ -19,9 +19,13 @@ import com.baomidou.mybatisplus.annotation.TableName;
 
 /**
  * Json类型处理器接口(实现类确保为多例状态).
+ * <p>
+ * 注意:查询返回时需要使用resultMap
+ * </p>
  *
  * @author nieqiurong 2024年3月4日
- * @see TableName#autoResultMap()
+ * @see TableName#autoResultMap() 自动构建
+ * @see TableName#resultMap() 手动指定
  * @since 3.5.6
  */
 public interface IJsonTypeHandler<T> {

+ 2 - 6
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableFieldInfo.java

@@ -25,6 +25,7 @@ import com.baomidou.mybatisplus.annotation.Version;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.core.toolkit.MybatisUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
 import lombok.AccessLevel;
@@ -563,12 +564,7 @@ public class TableFieldInfo implements Constants {
             TypeHandler<?> typeHandler = registry.getMappingTypeHandler(this.typeHandler);
             if (IJsonTypeHandler.class.isAssignableFrom(this.typeHandler)) {
                 // 保证每次实例化
-                try {
-                    //TODO 后面想一下怎么支持原生的方式,目前只支持自动生成的情况.
-                    typeHandler = this.typeHandler.getDeclaredConstructor(Class.class, Field.class).newInstance(this.propertyType, this.field);
-                } catch (ReflectiveOperationException e) {
-                    throw new RuntimeException(e);
-                }
+                typeHandler = MybatisUtils.newJsonTypeHandler(this.typeHandler, this.propertyType, this.field);
             } else {
                 if (typeHandler == null) {
                     typeHandler = registry.getInstance(this.propertyType, this.typeHandler);

+ 52 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/MybatisUtils.java

@@ -0,0 +1,52 @@
+package com.baomidou.mybatisplus.core.toolkit;
+
+import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.type.TypeException;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.lang.reflect.Field;
+
+/**
+ * @author nieqiurong
+ * @since 3.5.6
+ */
+@Slf4j
+@UtilityClass
+public class MybatisUtils {
+
+    /**
+     * 实例化Json类型处理器
+     * <p>
+     * 1.子类需要包含构造(Class,Field)
+     * 2.如果无上述构造或者无属性字段,则使用默认构造(Class)进行实例化
+     * </p>
+     *
+     * @param typeHandler   类型处理器 {@link IJsonTypeHandler}
+     * @param javaTypeClass java类型信息
+     * @param field         属性字段
+     * @return 实例化类型处理器
+     */
+    public static TypeHandler<?> newJsonTypeHandler(Class<? extends TypeHandler<?>> typeHandler, Class<?> javaTypeClass, Field field) {
+        TypeHandler<?> result = null;
+        if (IJsonTypeHandler.class.isAssignableFrom(typeHandler)) {
+            if (field != null) {
+                try {
+                    result = typeHandler.getConstructor(Class.class, Field.class).newInstance(javaTypeClass, field);
+                } catch (ReflectiveOperationException e) {
+                    // ignore
+                }
+            }
+            if (result == null) {
+                try {
+                    result = typeHandler.getConstructor(Class.class).newInstance(javaTypeClass);
+                } catch (ReflectiveOperationException ex) {
+                    throw new TypeException("Failed invoking constructor for handler " + typeHandler, ex);
+                }
+            }
+        }
+        return result;
+    }
+
+}

+ 2 - 2
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/spring/MybatisSqlSessionFactoryBean.java

@@ -19,11 +19,11 @@ import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.MybatisPlusVersion;
 import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
 import com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder;
+import com.baomidou.mybatisplus.core.MybatisXMLMapperBuilder;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
 import lombok.Setter;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.cache.Cache;
 import org.apache.ibatis.executor.ErrorContext;
 import org.apache.ibatis.io.Resources;
@@ -557,7 +557,7 @@ public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFacto
                         continue;
                     }
                     try {
-                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
+                        MybatisXMLMapperBuilder xmlMapperBuilder = new MybatisXMLMapperBuilder(mapperLocation.getInputStream(),
                             targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                         xmlMapperBuilder.parse();
                     } catch (Exception e) {

+ 2 - 2
mybatis-plus-extension/src/test/kotlin/com/baomidou/mybatisplus/test/kotlin/BaseDbTest.kt

@@ -2,13 +2,13 @@ package com.baomidou.mybatisplus.test.kotlin
 
 import com.baomidou.mybatisplus.core.MybatisConfiguration
 import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder
+import com.baomidou.mybatisplus.core.MybatisXMLMapperBuilder
 import com.baomidou.mybatisplus.core.config.GlobalConfig
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils
 import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils
 import com.baomidou.mybatisplus.core.toolkit.StringUtils
 import com.baomidou.mybatisplus.extension.toolkit.SqlRunner
-import org.apache.ibatis.builder.xml.XMLMapperBuilder
 import org.apache.ibatis.io.Resources
 import org.apache.ibatis.logging.slf4j.Slf4jImpl
 import org.apache.ibatis.mapping.Environment
@@ -65,7 +65,7 @@ abstract class BaseDbTest<T> : TypeReference<T>() {
         if (StringUtils.isNotBlank(mapperXml)) {
             try {
                 val inputStream = Resources.getResourceAsStream(mapperXml)
-                val xmlMapperBuilder = XMLMapperBuilder(
+                val xmlMapperBuilder = MybatisXMLMapperBuilder(
                     inputStream,
                     configuration, mapperXml, configuration.sqlFragments
                 )

+ 2 - 2
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/BaseDbTest.java

@@ -2,12 +2,12 @@ package com.baomidou.mybatisplus.test;
 
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
+import com.baomidou.mybatisplus.core.MybatisXMLMapperBuilder;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.io.Resources;
 import org.apache.ibatis.logging.slf4j.Slf4jImpl;
 import org.apache.ibatis.mapping.Environment;
@@ -73,7 +73,7 @@ public abstract class BaseDbTest<T> extends TypeReference<T> {
         if (StringUtils.isNotBlank(mapperXml)) {
             try {
                 InputStream inputStream = Resources.getResourceAsStream(mapperXml);
-                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream,
+                MybatisXMLMapperBuilder xmlMapperBuilder = new MybatisXMLMapperBuilder(inputStream,
                     configuration, mapperXml, configuration.getSqlFragments());
                 xmlMapperBuilder.parse();
             } catch (IOException e) {

+ 22 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/resultmap/Entity.java

@@ -9,6 +9,8 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
 
 /**
  * @author miemie
@@ -28,10 +30,30 @@ public class Entity implements Serializable {
     @TableField(typeHandler = GsonTypeHandler.class)
     private Gg gg2;
 
+    @TableField(typeHandler = GsonTypeHandler.class)
+    private List<Gg> gg3;
+
+    @TableField(typeHandler = GsonTypeHandler.class)
+    private List<Gg4> gg4;
+
+    @TableField(typeHandler = GsonTypeHandler.class)
+    private String[] str;
+
+
     @Data
     @NoArgsConstructor
     @AllArgsConstructor
     public static class Gg {
         private String name;
     }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Gg4 {
+        private String name;
+        private Gg gg;
+        private List<Gg> ggList;
+        private Map<String,Gg> ggMap;
+    }
 }

+ 21 - 1
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/resultmap/ResultMapTest.java

@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -17,7 +18,11 @@ public class ResultMapTest extends BaseDbTest<EntityMapper> {
     @Test
     void test() {
         doTestAutoCommit(m -> m.insert(new Entity().setGg1(new Entity.Gg("老王"))
-            .setGg2(new Entity.Gg("老李"))));
+            .setStr(new String[]{"hello"})
+            .setGg2(new Entity.Gg("老李"))
+            .setGg3(List.of(new Entity.Gg("老张")))
+            .setGg4(List.of(new Entity.Gg4("秋秋", new Entity.Gg("小红"), List.of(new Entity.Gg("小猫")), Map.of("test", new Entity.Gg("小明")))))
+        ));
 
         doTest(m -> {
             Entity entity = m.selectOne(null);
@@ -27,6 +32,18 @@ public class ResultMapTest extends BaseDbTest<EntityMapper> {
 
             assertThat(entity.getGg2()).as("typeHandler正常").isNotNull();
             assertThat(entity.getGg2().getName()).as("是老李").isEqualTo("老李");
+
+            assertThat(entity.getGg3()).as("typeHandler正常").isNotNull();
+            assertThat(entity.getGg3().getFirst().getName()).as("是老张").isEqualTo("老张");
+
+            assertThat(entity.getGg4()).as("typeHandler正常").isNotNull();
+            assertThat(entity.getGg4().getFirst().getName()).as("是秋秋").isEqualTo("秋秋");
+            assertThat(entity.getGg4().getFirst().getGg().getName()).as("是小红").isEqualTo("小红");
+            assertThat(entity.getGg4().getFirst().getGgList().getFirst().getName()).as("是小猫").isEqualTo("小猫");
+            assertThat(entity.getGg4().getFirst().getGgMap().get("test").getName()).as("是小明").isEqualTo("小明");
+
+            assertThat(entity.getStr()).as("typeHandler正常").isNotNull();
+            assertThat(entity.getStr()[0]).isEqualTo("hello");
         });
     }
 
@@ -37,6 +54,9 @@ public class ResultMapTest extends BaseDbTest<EntityMapper> {
                 "id BIGINT(20) NOT NULL,\n" +
                 "gg1 VARCHAR(255) NULL DEFAULT NULL,\n" +
                 "gg2 VARCHAR(255) NULL DEFAULT NULL,\n" +
+                "gg3 VARCHAR(255) NULL DEFAULT NULL,\n" +
+                "gg4 VARCHAR(255) NULL DEFAULT NULL,\n" +
+                "str VARCHAR(255) NULL DEFAULT NULL,\n" +
                 "PRIMARY KEY (id)" +
                 ")");
     }

+ 3 - 0
mybatis-plus/src/test/resources/com/baomidou/mybatisplus/test/resultmap/EntityMapper.xml

@@ -9,5 +9,8 @@
 
     <resultMap id="resultMap" type="com.baomidou.mybatisplus.test.resultmap.Entity" extends="baseResult">
         <result column="gg2" property="gg2" typeHandler="com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler"/>
+        <result property="gg3" column="gg3" typeHandler="com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler"/>
+        <result property="gg4" column="gg4" javaType="list" typeHandler="com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler"/>
+        <result property="str" column="str" typeHandler="com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler"/>
     </resultMap>
 </mapper>

+ 1 - 1
spring-boot-starter/mybatis-plus-boot-starter/src/test/java/com/baomidou/mybatisplus/test/pom/GeneratePomTest.java

@@ -75,7 +75,7 @@ class GeneratePomTest {
             Dependency bom = dependenciesMap.get("spring-boot-dependencies");
             Assertions.assertEquals("import", bom.getScope());
             Assertions.assertFalse(bom.isOptional());
-            Assertions.assertEquals(dependenciesMap.get("spring-cloud-commons").getVersion(), "3.1.1");
+            Assertions.assertEquals(dependenciesMap.get("spring-cloud-commons").getVersion(), "3.1.8");
             Assertions.assertEquals(dependenciesMap.get("mybatis-spring").getVersion(), "2.1.2");
             Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "2.7.15");
         }

+ 1 - 1
spring-boot-starter/mybatis-plus-spring-boot3-starter/src/test/java/com/baomidou/mybatisplus/test/pom/GeneratePomTest.java

@@ -75,7 +75,7 @@ class GeneratePomTest {
             Dependency bom = dependenciesMap.get("spring-boot-dependencies");
             Assertions.assertEquals("import", bom.getScope());
             Assertions.assertFalse(bom.isOptional());
-            Assertions.assertEquals(dependenciesMap.get("spring-cloud-commons").getVersion(), "4.0.4");
+            Assertions.assertEquals(dependenciesMap.get("spring-cloud-commons").getVersion(), "4.1.1");
             Assertions.assertEquals(dependenciesMap.get("mybatis-spring").getVersion(), "3.0.3");
             Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "3.2.0");
         }