All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ibatis.builder.xml.XMLMapperBuilder Maven / Gradle / Ivy

Go to download

The MyBatis SQL mapper framework makes it easier to use a relational database with object-oriented applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping tools.

The newest version!
/**
 *    Copyright 2009-2017 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
 *
 *       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 org.apache.ibatis.builder.xml;

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.builder.*;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.exceptions.EntityNotFoundException;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.features.jpa.builder.SqlContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.features.jpa.annotation.CustomProvider;
import org.apache.ibatis.features.jpa.generator.EntitySqlDispatcher;
import org.apache.ibatis.features.jpa.generator.SqlGenerator;
import org.apache.ibatis.features.jpa.generator.SqlGeneratorRegistry;
import org.apache.ibatis.features.jpa.mapper.Mapper;
import org.apache.ibatis.features.jpa.meta.Column;
import org.apache.ibatis.features.jpa.meta.Table;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.utils.AnnotationUtils;
import org.apache.ibatis.utils.ReflectionUtils;
import org.apache.ibatis.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author Clinton Begin
 */
public class XMLMapperBuilder extends BaseBuilder {
    private static final Logger logger = LoggerFactory.getLogger(XMLMapperBuilder.class);

    private final XPathParser parser;
    private final MapperBuilderAssistant builderAssistant;
    private final Map sqlFragments;
    private final String resource;
    private boolean enabledJpaFeatures = false;
    private Class entityClass;
    private Class idClass;

    @Deprecated
    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map sqlFragments, String namespace) {
        this(reader, configuration, resource, sqlFragments);
        this.builderAssistant.setCurrentNamespace(namespace);
    }

    @Deprecated
    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map sqlFragments) {
        this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
                configuration, resource, sqlFragments);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments, String namespace) {
        this(inputStream, configuration, resource, sqlFragments);
        this.builderAssistant.setCurrentNamespace(namespace);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
                configuration, resource, sqlFragments);
    }

    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map 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();
        }

        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.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }

            Class namespaceClass = Class.forName(namespace);
            if (Mapper.class.isAssignableFrom(namespaceClass)) {
                this.enabledJpaFeatures = true;
                findMetaClass(namespaceClass);
                EntitySqlDispatcher.getInstance().parseEntity(this.entityClass, this.idClass, configuration, namespace);
            }

            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"));
            if (this.enabledJpaFeatures) {
                embeddedEntityResultMap(this.entityClass);
                embeddedEntitySql(this.entityClass);
            }
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            if (this.enabledJpaFeatures) {
                embeddedEntityStatement(this.entityClass, namespace);
                checkMethodQueryStatement(namespace);
                configuration.addLoadedJpaMapper(namespaceClass);
            }
        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
    }

    void checkMethodQueryStatement(String namespace) {
        try {
            Class mapperClass = Class.forName(namespace);
            ReflectionUtils.doWithMethods(mapperClass, method -> {
                String id = namespace + "." + method.getName();
                if (!containsStatement(id)) {
                    if (AnnotationUtils.hasOneAnnotation(method,
                            Select.class, Insert.class, Update.class, Delete.class,
                            SelectProvider.class, InsertProvider.class, UpdateProvider.class,
                            DeleteProvider.class)) {
                        return;
                    }

                    SqlContext context = new SqlContext(mapperClass, method);
                    context.parse();
                    String sql = context.getSqlXml();
                    logger.debug("parsing embedded statement, sql: {}, mapper: {}method: {}",
                            sql, mapperClass, method);
                    XPathParser parser = new XPathParser(StringUtils.xmlDeclare(sql), false, configuration.getVariables(), new XMLMapperEntityResolver());
                    buildStatementFromContext(parser.evalNodes("select|insert|update|delete"));
                }
            });
        } catch (ClassNotFoundException e) {
            throw new BuilderException("Error loading entity sql. Cause: " + e, e);
        }
    }

    private boolean containsStatement(String id) {
        try {
            return configuration.getMappedStatement(id) != null;
        } catch (Exception e) {
            return false;
        }
    }

    void embeddedEntityStatement(final Class entity, String namespace) throws ClassNotFoundException {
        Class mapperClass = Class.forName(namespace);
        ReflectionUtils.doWithMethods(mapperClass, method -> {
            if (method.isAnnotationPresent(CustomProvider.class)) {
                try {
                    SqlGenerator generator = SqlGeneratorRegistry.getInstance().parseGenerator(method);
                    Map map = new HashMap<>();
                    map.put(SqlGenerator.PARAM_KEY_ID, method.getName());
                    String sql = generator.generatorSql(EntitySqlDispatcher.getInstance().getMetaDataParser(entity), map);
                    logger.debug("parsing embedded statement, sql: {}, generator: {}", sql, generator);
                    XPathParser parser = new XPathParser(StringUtils.xmlDeclare(sql), false, configuration.getVariables(), new XMLMapperEntityResolver());
                    buildStatementFromContext(parser.evalNodes("select|insert|update|delete"));
                } catch (InstantiationException e) {
                    throw new BuilderException("Error loading entity sql. Cause: " + e, e);
                }
            }
        });
    }


    private void findMetaClass(Class mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        if (types != null && types.length > 0) {
            for (Type type : types) {
                if (type instanceof ParameterizedType) {
                    Class rawClass = (Class) ((ParameterizedType) type).getRawType();
                    if (Mapper.class.isAssignableFrom(rawClass)) {
                        this.entityClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
                        this.idClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
                        return;
                    }
                }
            }
        }

        throw new EntityNotFoundException("No entity found for mapper : " + mapperClass.getName());
    }

    private void buildStatementFromContext(List list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }

    private void buildStatementFromContext(List 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 incompleteResultMaps = configuration.getIncompleteResultMaps();
        synchronized (incompleteResultMaps) {
            Iterator 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 incompleteCacheRefs = configuration.getIncompleteCacheRefs();
        synchronized (incompleteCacheRefs) {
            Iterator 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 incompleteStatements = configuration.getIncompleteStatements();
        synchronized (incompleteStatements) {
            Iterator 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) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type", "PERPETUAL");
            Class typeClass = typeAliasRegistry.resolveAlias(type);
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class 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 list) throws Exception {
        for (XNode parameterMapNode : list) {
            String id = parameterMapNode.getStringAttribute("id");
            String type = parameterMapNode.getStringAttribute("type");
            Class parameterClass = resolveClass(type);
            List parameterNodes = parameterMapNode.evalNodes("parameter");
            List 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);
                @SuppressWarnings("unchecked")
                Class> typeHandlerClass = (Class>) 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 list) throws Exception {
        for (XNode resultMapNode : list) {
            try {
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
                // ignore, it will be retried
            }
        }
    }

    private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
        return resultMapElement(resultMapNode, Collections.emptyList());
    }

    private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());
        String type = resultMapNode.getStringAttribute("type",
                resultMapNode.getStringAttribute("ofType",
                        resultMapNode.getStringAttribute("resultType",
                                resultMapNode.getStringAttribute("javaType"))));
        String extend = resultMapNode.getStringAttribute("extends");
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        Class typeClass = resolveClass(type);
        Discriminator discriminator = null;
        List resultMappings = new ArrayList();
        resultMappings.addAll(additionalResultMappings);
        List 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 flags = new ArrayList();
                if ("id".equals(resultChild.getName())) {
                    flags.add(ResultFlag.ID);
                }
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
            }
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
    }

    void embeddedEntityResultMap(Class entityClass) throws Exception {
        String resultMap = EntitySqlDispatcher.getInstance().getMetaDataParser(entityClass).getResultMapString();
        XPathParser parser = new XPathParser(StringUtils.xmlDeclare(resultMap), false, configuration.getVariables(),
                new XMLMapperEntityResolver());
        resultMapElements(Collections.singletonList(parser.evalNode("resultMap")));
    }

    private void processConstructorElement(XNode resultChild, Class resultType, List resultMappings) throws Exception {
        List argChildren = resultChild.getChildren();
        for (XNode argChild : argChildren) {
            List 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 resultMappings) throws Exception {
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String typeHandler = context.getStringAttribute("typeHandler");
        Class javaTypeClass = resolveClass(javaType);
        @SuppressWarnings("unchecked")
        Class> typeHandlerClass = (Class>) resolveClass(typeHandler);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Map discriminatorMap = new HashMap();
        for (XNode caseChild : context.getChildren()) {
            String value = caseChild.getStringAttribute("value");
            String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
            discriminatorMap.put(value, resultMap);
        }
        return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
    }

    private void sqlElement(List list) throws Exception {
        if (configuration.getDatabaseId() != null) {
            sqlElement(list, configuration.getDatabaseId());
        }
        sqlElement(list, null);
    }

    private void sqlElement(List list, String requiredDatabaseId) throws Exception {
        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);
            }
        }
    }

    void embeddedEntitySql(Class entityClass) throws Exception {
        Table table = EntitySqlDispatcher.getInstance()
                .getMetaDataParser(entityClass).getTable();
        List xnodes = new ArrayList<>();
        for (Table.SqlSegment sql : table.getSqlSegments()) {
            logger.debug("parsing embedded sql: {}, entity class: {} ", sql.toXml(), entityClass);
            XPathParser parser = new XPathParser(StringUtils.xmlDeclare(sql.toXml()), false, configuration.getVariables(),
                    new XMLMapperEntityResolver());
            xnodes.add(parser.evalNode("sql"));
        }
        sqlElement(xnodes);
    }

    private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
        if (requiredDatabaseId != null) {
            if (!requiredDatabaseId.equals(databaseId)) {
                return false;
            }
        } else {
            if (databaseId != null) {
                return false;
            }
            // skip this fragment if there is a previous one with a not null databaseId
            if (this.sqlFragments.containsKey(id)) {
                XNode context = this.sqlFragments.get(id);
                if (context.getStringAttribute("databaseId") != null) {
                    return false;
                }
            }
        }
        return true;
    }

    private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception {
        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()));
        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);
        @SuppressWarnings("unchecked")
        Class> typeHandlerClass = (Class>) 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 resultMappings) throws Exception {
        if ("association".equals(context.getName())
                || "collection".equals(context.getName())
                || "case".equals(context.getName())) {
            if (context.getStringAttribute("select") == null) {
                ResultMap resultMap = resultMapElement(context, resultMappings);
                return resultMap.getId();
            }
        }
        return null;
    }

    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) {
                if (!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);
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy