XMLMapperBuilder.java
- /*
- * Copyright 2009-2024 the original author or authors.
- *
- * 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
- *
- * https://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 org.apache.ibatis.builder.xml;
- import java.io.InputStream;
- import java.io.Reader;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Properties;
- 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.MapperBuilderAssistant;
- import org.apache.ibatis.builder.ResultMapResolver;
- 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;
- /**
- * @author Clinton Begin
- * @author Kazuki Shimizu
- */
- public class XMLMapperBuilder extends BaseBuilder {
- private final XPathParser parser;
- private final MapperBuilderAssistant builderAssistant;
- private final Map<String, XNode> sqlFragments;
- private final String resource;
- @Deprecated
- public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
- String namespace) {
- this(reader, configuration, resource, sqlFragments);
- this.builderAssistant.setCurrentNamespace(namespace);
- }
- @Deprecated
- public XMLMapperBuilder(Reader reader, Configuration configuration, String resource,
- Map<String, XNode> sqlFragments) {
- this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
- resource, sqlFragments);
- }
- public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
- Map<String, XNode> sqlFragments, String namespace) {
- this(inputStream, configuration, resource, sqlFragments);
- this.builderAssistant.setCurrentNamespace(namespace);
- }
- public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
- Map<String, XNode> sqlFragments) {
- this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
- resource, sqlFragments);
- }
- private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
- Map<String, XNode> sqlFragments) {
- super(configuration);
- this.builderAssistant = new MapperBuilderAssistant(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();
- }
- configuration.parsePendingResultMaps(false);
- configuration.parsePendingCacheRefs(false);
- configuration.parsePendingStatements(false);
- }
- 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 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);
- }
- }
- }
- }