Json处理器支持自定义XML.

This commit is contained in:
nieqiurong 2024-03-05 14:53:00 +08:00
parent a1a273df92
commit 2381495af9
15 changed files with 700 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,8 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import java.util.Map;
/** /**
* @author miemie * @author miemie
@ -28,10 +30,30 @@ public class Entity implements Serializable {
@TableField(typeHandler = GsonTypeHandler.class) @TableField(typeHandler = GsonTypeHandler.class)
private Gg gg2; 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 @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Gg { public static class Gg {
private String name; 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;
}
} }

View File

@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -17,7 +18,11 @@ public class ResultMapTest extends BaseDbTest<EntityMapper> {
@Test @Test
void test() { void test() {
doTestAutoCommit(m -> m.insert(new Entity().setGg1(new Entity.Gg("老王")) 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 -> { doTest(m -> {
Entity entity = m.selectOne(null); Entity entity = m.selectOne(null);
@ -27,6 +32,18 @@ public class ResultMapTest extends BaseDbTest<EntityMapper> {
assertThat(entity.getGg2()).as("typeHandler正常").isNotNull(); assertThat(entity.getGg2()).as("typeHandler正常").isNotNull();
assertThat(entity.getGg2().getName()).as("是老李").isEqualTo("老李"); 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" + "id BIGINT(20) NOT NULL,\n" +
"gg1 VARCHAR(255) NULL DEFAULT NULL,\n" + "gg1 VARCHAR(255) NULL DEFAULT NULL,\n" +
"gg2 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)" + "PRIMARY KEY (id)" +
")"); ")");
} }

View File

@ -9,5 +9,8 @@
<resultMap id="resultMap" type="com.baomidou.mybatisplus.test.resultmap.Entity" extends="baseResult"> <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 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> </resultMap>
</mapper> </mapper>

View File

@ -75,7 +75,7 @@ class GeneratePomTest {
Dependency bom = dependenciesMap.get("spring-boot-dependencies"); Dependency bom = dependenciesMap.get("spring-boot-dependencies");
Assertions.assertEquals("import", bom.getScope()); Assertions.assertEquals("import", bom.getScope());
Assertions.assertFalse(bom.isOptional()); 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("mybatis-spring").getVersion(), "2.1.2");
Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "2.7.15"); Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "2.7.15");
} }

View File

@ -75,7 +75,7 @@ class GeneratePomTest {
Dependency bom = dependenciesMap.get("spring-boot-dependencies"); Dependency bom = dependenciesMap.get("spring-boot-dependencies");
Assertions.assertEquals("import", bom.getScope()); Assertions.assertEquals("import", bom.getScope());
Assertions.assertFalse(bom.isOptional()); 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("mybatis-spring").getVersion(), "3.0.3");
Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "3.2.0"); Assertions.assertEquals(dependenciesMap.get("spring-boot-dependencies").getVersion(), "3.2.0");
} }