org.apache.ibatis.builder.xml.XMLMapperBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mybatis-jpa Show documentation
Show all versions of mybatis-jpa Show documentation
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 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 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 extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) 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 extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) 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 extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) 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