MapperBuilderAssistant.java

  1. /*
  2.  *    Copyright 2009-2024 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.builder;

  17. import java.sql.ResultSet;
  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Properties;
  25. import java.util.Set;
  26. import java.util.StringTokenizer;

  27. import org.apache.ibatis.cache.Cache;
  28. import org.apache.ibatis.cache.decorators.LruCache;
  29. import org.apache.ibatis.cache.impl.PerpetualCache;
  30. import org.apache.ibatis.executor.ErrorContext;
  31. import org.apache.ibatis.executor.keygen.KeyGenerator;
  32. import org.apache.ibatis.mapping.CacheBuilder;
  33. import org.apache.ibatis.mapping.Discriminator;
  34. import org.apache.ibatis.mapping.MappedStatement;
  35. import org.apache.ibatis.mapping.ParameterMap;
  36. import org.apache.ibatis.mapping.ParameterMapping;
  37. import org.apache.ibatis.mapping.ParameterMode;
  38. import org.apache.ibatis.mapping.ResultFlag;
  39. import org.apache.ibatis.mapping.ResultMap;
  40. import org.apache.ibatis.mapping.ResultMapping;
  41. import org.apache.ibatis.mapping.ResultSetType;
  42. import org.apache.ibatis.mapping.SqlCommandType;
  43. import org.apache.ibatis.mapping.SqlSource;
  44. import org.apache.ibatis.mapping.StatementType;
  45. import org.apache.ibatis.reflection.MetaClass;
  46. import org.apache.ibatis.scripting.LanguageDriver;
  47. import org.apache.ibatis.session.Configuration;
  48. import org.apache.ibatis.type.JdbcType;
  49. import org.apache.ibatis.type.TypeHandler;

  50. /**
  51.  * @author Clinton Begin
  52.  */
  53. public class MapperBuilderAssistant extends BaseBuilder {

  54.   private String currentNamespace;
  55.   private final String resource;
  56.   private Cache currentCache;
  57.   private boolean unresolvedCacheRef; // issue #676

  58.   public MapperBuilderAssistant(Configuration configuration, String resource) {
  59.     super(configuration);
  60.     ErrorContext.instance().resource(resource);
  61.     this.resource = resource;
  62.   }

  63.   public String getCurrentNamespace() {
  64.     return currentNamespace;
  65.   }

  66.   public void setCurrentNamespace(String currentNamespace) {
  67.     if (currentNamespace == null) {
  68.       throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
  69.     }

  70.     if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
  71.       throw new BuilderException(
  72.           "Wrong namespace. Expected '" + this.currentNamespace + "' but found '" + currentNamespace + "'.");
  73.     }

  74.     this.currentNamespace = currentNamespace;
  75.   }

  76.   public String applyCurrentNamespace(String base, boolean isReference) {
  77.     if (base == null) {
  78.       return null;
  79.     }
  80.     if (isReference) {
  81.       // is it qualified with any namespace yet?
  82.       if (base.contains(".")) {
  83.         return base;
  84.       }
  85.     } else {
  86.       // is it qualified with this namespace yet?
  87.       if (base.startsWith(currentNamespace + ".")) {
  88.         return base;
  89.       }
  90.       if (base.contains(".")) {
  91.         throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
  92.       }
  93.     }
  94.     return currentNamespace + "." + base;
  95.   }

  96.   public Cache useCacheRef(String namespace) {
  97.     if (namespace == null) {
  98.       throw new BuilderException("cache-ref element requires a namespace attribute.");
  99.     }
  100.     try {
  101.       unresolvedCacheRef = true;
  102.       Cache cache = configuration.getCache(namespace);
  103.       if (cache == null) {
  104.         throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
  105.       }
  106.       currentCache = cache;
  107.       unresolvedCacheRef = false;
  108.       return cache;
  109.     } catch (IllegalArgumentException e) {
  110.       throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  111.     }
  112.   }

  113.   public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval,
  114.       Integer size, boolean readWrite, boolean blocking, Properties props) {
  115.     Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
  116.         .addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
  117.         .readWrite(readWrite).blocking(blocking).properties(props).build();
  118.     configuration.addCache(cache);
  119.     currentCache = cache;
  120.     return cache;
  121.   }

  122.   public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
  123.     id = applyCurrentNamespace(id, false);
  124.     ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
  125.     configuration.addParameterMap(parameterMap);
  126.     return parameterMap;
  127.   }

  128.   public ParameterMapping buildParameterMapping(Class<?> parameterType, String property, Class<?> javaType,
  129.       JdbcType jdbcType, String resultMap, ParameterMode parameterMode, Class<? extends TypeHandler<?>> typeHandler,
  130.       Integer numericScale) {
  131.     resultMap = applyCurrentNamespace(resultMap, true);

  132.     // Class parameterType = parameterMapBuilder.type();
  133.     Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
  134.     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

  135.     return new ParameterMapping.Builder(configuration, property, javaTypeClass).jdbcType(jdbcType)
  136.         .resultMapId(resultMap).mode(parameterMode).numericScale(numericScale).typeHandler(typeHandlerInstance).build();
  137.   }

  138.   public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator,
  139.       List<ResultMapping> resultMappings, Boolean autoMapping) {
  140.     id = applyCurrentNamespace(id, false);
  141.     extend = applyCurrentNamespace(extend, true);

  142.     if (extend != null) {
  143.       if (!configuration.hasResultMap(extend)) {
  144.         throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
  145.       }
  146.       ResultMap resultMap = configuration.getResultMap(extend);
  147.       List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
  148.       extendedResultMappings.removeAll(resultMappings);
  149.       // Remove parent constructor if this resultMap declares a constructor.
  150.       boolean declaresConstructor = false;
  151.       for (ResultMapping resultMapping : resultMappings) {
  152.         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
  153.           declaresConstructor = true;
  154.           break;
  155.         }
  156.       }
  157.       if (declaresConstructor) {
  158.         extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
  159.       }
  160.       resultMappings.addAll(extendedResultMappings);
  161.     }
  162.     ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
  163.         .discriminator(discriminator).build();
  164.     configuration.addResultMap(resultMap);
  165.     return resultMap;
  166.   }

  167.   public Discriminator buildDiscriminator(Class<?> resultType, String column, Class<?> javaType, JdbcType jdbcType,
  168.       Class<? extends TypeHandler<?>> typeHandler, Map<String, String> discriminatorMap) {
  169.     ResultMapping resultMapping = buildResultMapping(resultType, null, column, javaType, jdbcType, null, null, null,
  170.         null, typeHandler, new ArrayList<>(), null, null, false);
  171.     Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
  172.     for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
  173.       String resultMap = e.getValue();
  174.       resultMap = applyCurrentNamespace(resultMap, true);
  175.       namespaceDiscriminatorMap.put(e.getKey(), resultMap);
  176.     }
  177.     return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
  178.   }

  179.   public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
  180.       SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
  181.       String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
  182.       boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
  183.       LanguageDriver lang, String resultSets, boolean dirtySelect) {

  184.     if (unresolvedCacheRef) {
  185.       throw new IncompleteElementException("Cache-ref not yet resolved");
  186.     }

  187.     id = applyCurrentNamespace(id, false);

  188.     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
  189.         .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
  190.         .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
  191.         .resultOrdered(resultOrdered).resultSets(resultSets)
  192.         .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
  193.         .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);

  194.     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  195.     if (statementParameterMap != null) {
  196.       statementBuilder.parameterMap(statementParameterMap);
  197.     }

  198.     MappedStatement statement = statementBuilder.build();
  199.     configuration.addMappedStatement(statement);
  200.     return statement;
  201.   }

  202.   /**
  203.    * Backward compatibility signature 'addMappedStatement'.
  204.    *
  205.    * @param id
  206.    *          the id
  207.    * @param sqlSource
  208.    *          the sql source
  209.    * @param statementType
  210.    *          the statement type
  211.    * @param sqlCommandType
  212.    *          the sql command type
  213.    * @param fetchSize
  214.    *          the fetch size
  215.    * @param timeout
  216.    *          the timeout
  217.    * @param parameterMap
  218.    *          the parameter map
  219.    * @param parameterType
  220.    *          the parameter type
  221.    * @param resultMap
  222.    *          the result map
  223.    * @param resultType
  224.    *          the result type
  225.    * @param resultSetType
  226.    *          the result set type
  227.    * @param flushCache
  228.    *          the flush cache
  229.    * @param useCache
  230.    *          the use cache
  231.    * @param resultOrdered
  232.    *          the result ordered
  233.    * @param keyGenerator
  234.    *          the key generator
  235.    * @param keyProperty
  236.    *          the key property
  237.    * @param keyColumn
  238.    *          the key column
  239.    * @param databaseId
  240.    *          the database id
  241.    * @param lang
  242.    *          the lang
  243.    *
  244.    * @return the mapped statement
  245.    */
  246.   public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
  247.       SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
  248.       String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
  249.       boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
  250.       LanguageDriver lang, String resultSets) {
  251.     return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
  252.         parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
  253.         keyProperty, keyColumn, databaseId, lang, null, false);
  254.   }

  255.   public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
  256.       SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
  257.       String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
  258.       boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
  259.       LanguageDriver lang) {
  260.     return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
  261.         parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
  262.         keyProperty, keyColumn, databaseId, lang, null);
  263.   }

  264.   private <T> T valueOrDefault(T value, T defaultValue) {
  265.     return value == null ? defaultValue : value;
  266.   }

  267.   private ParameterMap getStatementParameterMap(String parameterMapName, Class<?> parameterTypeClass,
  268.       String statementId) {
  269.     parameterMapName = applyCurrentNamespace(parameterMapName, true);
  270.     ParameterMap parameterMap = null;
  271.     if (parameterMapName != null) {
  272.       try {
  273.         parameterMap = configuration.getParameterMap(parameterMapName);
  274.       } catch (IllegalArgumentException e) {
  275.         throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
  276.       }
  277.     } else if (parameterTypeClass != null) {
  278.       List<ParameterMapping> parameterMappings = new ArrayList<>();
  279.       parameterMap = new ParameterMap.Builder(configuration, statementId + "-Inline", parameterTypeClass,
  280.           parameterMappings).build();
  281.     }
  282.     return parameterMap;
  283.   }

  284.   private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
  285.     resultMap = applyCurrentNamespace(resultMap, true);

  286.     List<ResultMap> resultMaps = new ArrayList<>();
  287.     if (resultMap != null) {
  288.       String[] resultMapNames = resultMap.split(",");
  289.       for (String resultMapName : resultMapNames) {
  290.         try {
  291.           resultMaps.add(configuration.getResultMap(resultMapName.trim()));
  292.         } catch (IllegalArgumentException e) {
  293.           throw new IncompleteElementException(
  294.               "Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
  295.         }
  296.       }
  297.     } else if (resultType != null) {
  298.       ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId + "-Inline", resultType,
  299.           new ArrayList<>(), null).build();
  300.       resultMaps.add(inlineResultMap);
  301.     }
  302.     return resultMaps;
  303.   }

  304.   public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
  305.       JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
  306.       Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn,
  307.       boolean lazy) {
  308.     Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  309.     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  310.     List<ResultMapping> composites;
  311.     if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
  312.       composites = Collections.emptyList();
  313.     } else {
  314.       composites = parseCompositeColumnName(column);
  315.     }
  316.     return new ResultMapping.Builder(configuration, property, column, javaTypeClass).jdbcType(jdbcType)
  317.         .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
  318.         .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)).resultSet(resultSet)
  319.         .typeHandler(typeHandlerInstance).flags(flags == null ? new ArrayList<>() : flags).composites(composites)
  320.         .notNullColumns(parseMultipleColumnNames(notNullColumn)).columnPrefix(columnPrefix).foreignColumn(foreignColumn)
  321.         .lazy(lazy).build();
  322.   }

  323.   /**
  324.    * Backward compatibility signature 'buildResultMapping'.
  325.    *
  326.    * @param resultType
  327.    *          the result type
  328.    * @param property
  329.    *          the property
  330.    * @param column
  331.    *          the column
  332.    * @param javaType
  333.    *          the java type
  334.    * @param jdbcType
  335.    *          the jdbc type
  336.    * @param nestedSelect
  337.    *          the nested select
  338.    * @param nestedResultMap
  339.    *          the nested result map
  340.    * @param notNullColumn
  341.    *          the not null column
  342.    * @param columnPrefix
  343.    *          the column prefix
  344.    * @param typeHandler
  345.    *          the type handler
  346.    * @param flags
  347.    *          the flags
  348.    *
  349.    * @return the result mapping
  350.    */
  351.   public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
  352.       JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
  353.       Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
  354.     return buildResultMapping(resultType, property, column, javaType, jdbcType, nestedSelect, nestedResultMap,
  355.         notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
  356.   }

  357.   /**
  358.    * Gets the language driver.
  359.    *
  360.    * @param langClass
  361.    *          the lang class
  362.    *
  363.    * @return the language driver
  364.    *
  365.    * @deprecated Use {@link Configuration#getLanguageDriver(Class)}
  366.    */
  367.   @Deprecated
  368.   public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
  369.     return configuration.getLanguageDriver(langClass);
  370.   }

  371.   private Set<String> parseMultipleColumnNames(String columnName) {
  372.     Set<String> columns = new HashSet<>();
  373.     if (columnName != null) {
  374.       if (columnName.indexOf(',') > -1) {
  375.         StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
  376.         while (parser.hasMoreTokens()) {
  377.           String column = parser.nextToken();
  378.           columns.add(column);
  379.         }
  380.       } else {
  381.         columns.add(columnName);
  382.       }
  383.     }
  384.     return columns;
  385.   }

  386.   private List<ResultMapping> parseCompositeColumnName(String columnName) {
  387.     List<ResultMapping> composites = new ArrayList<>();
  388.     if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
  389.       StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
  390.       while (parser.hasMoreTokens()) {
  391.         String property = parser.nextToken();
  392.         String column = parser.nextToken();
  393.         ResultMapping complexResultMapping = new ResultMapping.Builder(configuration, property, column,
  394.             configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
  395.         composites.add(complexResultMapping);
  396.       }
  397.     }
  398.     return composites;
  399.   }

  400.   private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
  401.     if (javaType == null && property != null) {
  402.       try {
  403.         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
  404.         javaType = metaResultType.getSetterType(property);
  405.       } catch (Exception e) {
  406.         // ignore, following null check statement will deal with the situation
  407.       }
  408.     }
  409.     if (javaType == null) {
  410.       javaType = Object.class;
  411.     }
  412.     return javaType;
  413.   }

  414.   private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType,
  415.       JdbcType jdbcType) {
  416.     if (javaType == null) {
  417.       if (JdbcType.CURSOR.equals(jdbcType)) {
  418.         javaType = ResultSet.class;
  419.       } else if (Map.class.isAssignableFrom(resultType)) {
  420.         javaType = Object.class;
  421.       } else {
  422.         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
  423.         javaType = metaResultType.getGetterType(property);
  424.       }
  425.     }
  426.     if (javaType == null) {
  427.       javaType = Object.class;
  428.     }
  429.     return javaType;
  430.   }

  431. }