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

com.ibatis.sqlmap.engine.mapping.result.ResultMap Maven / Gradle / Ivy

Go to download

The mybatis data 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 2004-2023 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 com.ibatis.sqlmap.engine.mapping.result;

import com.ibatis.common.beans.Probe;
import com.ibatis.common.beans.ProbeFactory;
import com.ibatis.common.jdbc.exception.NestedSQLException;
import com.ibatis.sqlmap.client.SqlMapException;
import com.ibatis.sqlmap.engine.exchange.DataExchange;
import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
import com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader;
import com.ibatis.sqlmap.engine.mapping.sql.Sql;
import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
import com.ibatis.sqlmap.engine.scope.ErrorContext;
import com.ibatis.sqlmap.engine.scope.StatementScope;
import com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker;
import com.ibatis.sqlmap.engine.type.DomTypeMarker;
import com.ibatis.sqlmap.engine.type.TypeHandler;
import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;

/**
 * Basic implementation of ResultMap interface.
 */
public class ResultMap {

  /** The Constant PROBE. */
  private static final Probe PROBE = ProbeFactory.getProbe();

  /** The Constant KEY_SEPARATOR. */
  private static final String KEY_SEPARATOR = "\002";

  /** The id. */
  private String id;

  /** The result class. */
  private Class resultClass;

  /** The result mappings. */
  // DO NOT ACCESS EITHER OF THESE OUTSIDE OF THEIR BEAN GETTER/SETTER
  private ResultMapping[] resultMappings;

  /** The remappable result mappings. */
  private ThreadLocal remappableResultMappings = new ThreadLocal();

  /** The data exchange. */
  private DataExchange dataExchange;

  /** The nested result mappings. */
  private List nestedResultMappings;

  /** The discriminator. */
  private Discriminator discriminator;

  /** The group by props. */
  private Set groupByProps;

  /** The xml name. */
  private String xmlName;

  /** The resource. */
  private String resource;

  /** The delegate. */
  protected SqlMapExecutorDelegate delegate;

  /** The allow remapping. */
  protected boolean allowRemapping = false;

  /** The Constant NO_VALUE. */
  public static final Object NO_VALUE = new Object();

  /**
   * Constructor to pass a SqlMapExecutorDelegate in.
   *
   * @param delegate
   *          - the SqlMapExecutorDelegate
   */
  public ResultMap(SqlMapExecutorDelegate delegate) {
    this.delegate = delegate;
  }

  /**
   * Getter for the SqlMapExecutorDelegate.
   *
   * @return - the delegate
   */
  public SqlMapExecutorDelegate getDelegate() {
    return delegate;
  }

  /**
   * Gets the id.
   *
   * @return the id
   */
  public String getId() {
    return id;
  }

  /**
   * Setter for the ID.
   *
   * @param id
   *          - the new ID
   */
  public void setId(String id) {
    this.id = id;
  }

  /**
   * Gets the result class.
   *
   * @return the result class
   */
  public Class getResultClass() {
    return resultClass;
  }

  /**
   * Gets the unique key.
   *
   * @param keyPrefix
   *          the key prefix
   * @param values
   *          the values
   *
   * @return the unique key
   */
  public Object getUniqueKey(String keyPrefix, Object[] values) {
    if (groupByProps != null) {
      StringBuilder keyBuffer;
      if (keyPrefix != null)
        keyBuffer = new StringBuilder(keyPrefix);
      else
        keyBuffer = new StringBuilder();
      for (int i = 0; i < getResultMappings().length; i++) {
        String propertyName = getResultMappings()[i].getPropertyName();
        if (groupByProps.contains(propertyName)) {
          keyBuffer.append(values[i]);
          keyBuffer.append('-');
        }
      }
      if (keyBuffer.length() < 1) {
        return null;
      } else {
        // seperator value not likely to appear in a database
        keyBuffer.append(KEY_SEPARATOR);
        return keyBuffer.toString();
      }
    } else {
      return null;
    }
  }

  /**
   * Gets the unique key.
   *
   * @param values
   *          the values
   *
   * @return the unique key
   */
  public Object getUniqueKey(Object[] values) {
    return getUniqueKey(null, values);
  }

  /**
   * Setter for the result class (what the results will be mapped into).
   *
   * @param resultClass
   *          - the result class
   */
  public void setResultClass(Class resultClass) {
    this.resultClass = resultClass;
  }

  /**
   * Getter for the DataExchange object to be used.
   *
   * @return - the DataExchange object
   */
  public DataExchange getDataExchange() {
    return dataExchange;
  }

  /**
   * Setter for the DataExchange object to be used.
   *
   * @param dataExchange
   *          - the new DataExchange object
   */
  public void setDataExchange(DataExchange dataExchange) {
    this.dataExchange = dataExchange;
  }

  /**
   * Getter (used by DomDataExchange) for the xml name of the results.
   *
   * @return - the name
   */
  public String getXmlName() {
    return xmlName;
  }

  /**
   * Setter (used by the SqlMapBuilder) for the xml name of the results.
   *
   * @param xmlName
   *          - the name
   */
  public void setXmlName(String xmlName) {
    this.xmlName = xmlName;
  }

  /**
   * Getter for the resource (used to report errors).
   *
   * @return - the resource
   */
  public String getResource() {
    return resource;
  }

  /**
   * Setter for the resource (used by the SqlMapBuilder).
   *
   * @param resource
   *          - the resource name
   */
  public void setResource(String resource) {
    this.resource = resource;
  }

  /**
   * Adds the group by property.
   *
   * @param name
   *          the name
   */
  public void addGroupByProperty(String name) {
    if (groupByProps == null) {
      groupByProps = new HashSet();
    }
    groupByProps.add(name);
  }

  /**
   * Checks for group by.
   *
   * @return true, if successful
   */
  public boolean hasGroupBy() {
    return groupByProps != null && groupByProps.size() > 0;
  }

  /**
   * Group by props.
   *
   * @return the iterator
   */
  public Iterator groupByProps() {
    return groupByProps.iterator();
  }

  /**
   * Adds the nested result mappings.
   *
   * @param mapping
   *          the mapping
   */
  public void addNestedResultMappings(ResultMapping mapping) {
    if (nestedResultMappings == null) {
      nestedResultMappings = new ArrayList();
    }
    nestedResultMappings.add(mapping);
  }

  /**
   * Gets the nested result mappings.
   *
   * @return the nested result mappings
   */
  public List getNestedResultMappings() {
    return nestedResultMappings;
  }

  /**
   * Gets the result mappings.
   *
   * @return the result mappings
   */
  public ResultMapping[] getResultMappings() {
    if (allowRemapping) {
      return (ResultMapping[]) remappableResultMappings.get();
    } else {
      return resultMappings;
    }
  }

  /**
   * Sets the discriminator.
   *
   * @param discriminator
   *          the new discriminator
   */
  public void setDiscriminator(Discriminator discriminator) {
    if (this.discriminator != null) {
      throw new SqlMapException("A discriminator may only be set once per result map.");
    }
    this.discriminator = discriminator;
  }

  /**
   * Gets the discriminator.
   *
   * @return the discriminator
   */
  public Discriminator getDiscriminator() {
    return discriminator;
  }

  /**
   * Resolve sub map.
   *
   * @param statementScope
   *          the statement scope
   * @param rs
   *          the rs
   *
   * @return the result map
   *
   * @throws SQLException
   *           the SQL exception
   */
  public ResultMap resolveSubMap(StatementScope statementScope, ResultSet rs) throws SQLException {
    ResultMap subMap = this;
    if (discriminator != null) {
      ResultMapping mapping = (ResultMapping) discriminator.getResultMapping();
      Object value = getPrimitiveResultMappingValue(rs, mapping);
      if (value == null) {
        value = doNullMapping(value, mapping);
      }
      subMap = discriminator.getSubMap(String.valueOf(value));
      if (subMap == null) {
        subMap = this;
      } else if (subMap != this) {
        subMap = subMap.resolveSubMap(statementScope, rs);
      }
    }
    return subMap;
  }

  /**
   * Setter for a list of the individual ResultMapping objects.
   *
   * @param resultMappingList
   *          - the list
   */
  public void setResultMappingList(List resultMappingList) {
    if (allowRemapping) {
      this.remappableResultMappings
          .set((ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]));
    } else {
      this.resultMappings = (ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]);
    }

    Map props = new HashMap();
    props.put("map", this);
    dataExchange = getDelegate().getDataExchangeFactory().getDataExchangeForClass(resultClass);
    dataExchange.initialize(props);
  }

  /**
   * Getter for the number of ResultMapping objects.
   *
   * @return - the count
   */
  public int getResultCount() {
    return this.getResultMappings().length;
  }

  /**
   * Read a row from a resultset and map results to an array.
   *
   * @param statementScope
   *          scope of the request
   * @param rs
   *          ResultSet to read from
   *
   * @return row read as an array of column values.
   *
   * @throws SQLException
   *           the SQL exception
   */
  public Object[] getResults(StatementScope statementScope, ResultSet rs) throws SQLException {
    ErrorContext errorContext = statementScope.getErrorContext();
    errorContext.setActivity("applying a result map");
    errorContext.setObjectId(this.getId());
    errorContext.setResource(this.getResource());
    errorContext.setMoreInfo("Check the result map.");

    boolean foundData = false;
    Object[] columnValues = new Object[getResultMappings().length];
    for (int i = 0; i < getResultMappings().length; i++) {
      ResultMapping mapping = (ResultMapping) getResultMappings()[i];
      errorContext.setMoreInfo(mapping.getErrorString());
      if (mapping.getStatementName() != null) {
        if (resultClass == null) {
          throw new SqlMapException(
              "The result class was null when trying to get results for ResultMap named " + getId() + ".");
        } else if (Map.class.isAssignableFrom(resultClass)) {
          Class javaType = mapping.getJavaType();
          if (javaType == null) {
            javaType = Object.class;
          }
          columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
        } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
          Class javaType = mapping.getJavaType();
          if (javaType == null) {
            javaType = DomTypeMarker.class;
          }
          columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
        } else {
          Probe p = ProbeFactory.getProbe(resultClass);
          Class type = p.getPropertyTypeForSetter(resultClass, mapping.getPropertyName());
          columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, type);
        }
        foundData = foundData || columnValues[i] != null;
      } else if (mapping.getNestedResultMapName() == null) {
        columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
        if (columnValues[i] == null) {
          columnValues[i] = doNullMapping(columnValues[i], mapping);
        } else {
          foundData = true;
        }
      }
    }

    statementScope.setRowDataFound(foundData);

    return columnValues;
  }

  /**
   * Sets the result object values.
   *
   * @param statementScope
   *          the statement scope
   * @param resultObject
   *          the result object
   * @param values
   *          the values
   *
   * @return the object
   */
  public Object setResultObjectValues(StatementScope statementScope, Object resultObject, Object[] values) {
    final String previousNestedKey = statementScope.getCurrentNestedKey();
    String ukey = (String) getUniqueKey(statementScope.getCurrentNestedKey(), values);
    Map uniqueKeys = statementScope.getUniqueKeys(this);
    statementScope.setCurrentNestedKey(ukey);
    if (uniqueKeys != null && uniqueKeys.containsKey(ukey)) {
      // Unique key is already known, so get the existing result object and process additional
      // results.
      resultObject = uniqueKeys.get(ukey);
      applyNestedResultMap(statementScope, resultObject, values);
      resultObject = NO_VALUE;
    } else if (ukey == null || uniqueKeys == null || !uniqueKeys.containsKey(ukey)) {
      // Unique key is NOT known, so create a new result object and then process additional
      // results.
      resultObject = dataExchange.setData(statementScope, this, resultObject, values);
      // Lazy init key set, only if we're grouped by something (i.e. ukey != null)
      if (ukey != null) {
        if (uniqueKeys == null) {
          uniqueKeys = new HashMap();
          statementScope.setUniqueKeys(this, uniqueKeys);
        }
        uniqueKeys.put(ukey, resultObject);
      }
      applyNestedResultMap(statementScope, resultObject, values);
    } else {
      // Otherwise, we don't care about these results.
      resultObject = NO_VALUE;
    }

    statementScope.setCurrentNestedKey(previousNestedKey);
    return resultObject;
  }

  /**
   * Apply nested result map.
   *
   * @param statementScope
   *          the statement scope
   * @param resultObject
   *          the result object
   * @param values
   *          the values
   */
  private void applyNestedResultMap(StatementScope statementScope, Object resultObject, Object[] values) {
    if (resultObject != null && resultObject != NO_VALUE) {
      if (nestedResultMappings != null) {
        for (int i = 0, n = nestedResultMappings.size(); i < n; i++) {
          ResultMapping resultMapping = (ResultMapping) nestedResultMappings.get(i);
          setNestedResultMappingValue(resultMapping, statementScope, resultObject, values);
        }
      }
    }
  }

  /**
   * Some changes in this method for IBATIS-225:
   * 
    *
  • We no longer require the nested property to be a collection. This will allow reuses of resultMaps on 1:1 * relationships
  • *
  • If the nested property is not a collection, then it will be created/replaced by the values generated from the * current row.
  • *
* * @param mapping * the mapping * @param statementScope * the statement scope * @param resultObject * the result object * @param values * the values */ protected void setNestedResultMappingValue(ResultMapping mapping, StatementScope statementScope, Object resultObject, Object[] values) { try { String resultMapName = mapping.getNestedResultMapName(); ResultMap resultMap = getDelegate().getResultMap(resultMapName); // get the discriminated submap if it exists resultMap = resultMap.resolveSubMap(statementScope, statementScope.getResultSet()); Class type = mapping.getJavaType(); String propertyName = mapping.getPropertyName(); Object obj = PROBE.getObject(resultObject, propertyName); if (obj == null) { if (type == null) { type = PROBE.getPropertyTypeForSetter(resultObject, propertyName); } try { // create the object if is it a Collection. If not a Collection // then we will just set the property to the object created // in processing the nested result map if (Collection.class.isAssignableFrom(type)) { obj = ResultObjectFactoryUtil.createObjectThroughFactory(type); PROBE.setObject(resultObject, propertyName, obj); } } catch (Exception e) { throw new SqlMapException( "Error instantiating collection property for mapping '" + mapping.getPropertyName() + "'. Cause: " + e, e); } } // JIRA 375 // "Provide a way for not creating items from nested ResultMaps when the items contain only null values" boolean subResultObjectAbsent = false; if (mapping.getNotNullColumn() != null) { if (statementScope.getResultSet().getObject(mapping.getNotNullColumn()) == null) { subResultObjectAbsent = true; } } if (!subResultObjectAbsent) { values = resultMap.getResults(statementScope, statementScope.getResultSet()); if (statementScope.isRowDataFound()) { Object o = resultMap.setResultObjectValues(statementScope, null, values); if (o != NO_VALUE) { if (obj != null && obj instanceof Collection) { ((Collection) obj).add(o); } else { PROBE.setObject(resultObject, propertyName, o); } } } } } catch (SQLException e) { throw new SqlMapException( "Error getting nested result map values for '" + mapping.getPropertyName() + "'. Cause: " + e, e); } } /** * Gets the nested select mapping value. * * @param statementScope * the statement scope * @param rs * the rs * @param mapping * the mapping * @param targetType * the target type * * @return the nested select mapping value * * @throws SQLException * the SQL exception */ protected Object getNestedSelectMappingValue(StatementScope statementScope, ResultSet rs, ResultMapping mapping, Class targetType) throws SQLException { try { TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory(); String statementName = mapping.getStatementName(); SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient(); MappedStatement mappedStatement = client.getMappedStatement(statementName); Class parameterType = mappedStatement.getParameterClass(); Object parameterObject = null; if (parameterType == null) { parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType); } else { if (typeHandlerFactory.hasTypeHandler(parameterType)) { parameterObject = preparePrimitiveParameterObject(rs, mapping, parameterType); } else if (DomTypeMarker.class.isAssignableFrom(parameterType)) { parameterObject = prepareDomParameterObject(rs, mapping); } else { parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType); } } Object result = null; if (parameterObject != null) { Sql sql = mappedStatement.getSql(); ResultMap resultMap = sql.getResultMap(statementScope, parameterObject); Class resultClass = resultMap.getResultClass(); if (resultClass != null && !DomTypeMarker.class.isAssignableFrom(targetType)) { if (DomCollectionTypeMarker.class.isAssignableFrom(resultClass)) { targetType = DomCollectionTypeMarker.class; } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) { targetType = DomTypeMarker.class; } } result = ResultLoader.loadResult(client, statementName, parameterObject, targetType); String nullValue = mapping.getNullValue(); if (result == null && nullValue != null) { TypeHandler typeHandler = typeHandlerFactory.getTypeHandler(targetType); if (typeHandler != null) { result = typeHandler.valueOf(nullValue); } } } return result; } catch (InstantiationException e) { throw new NestedSQLException("Error setting nested bean property. Cause: " + e, e); } catch (IllegalAccessException e) { throw new NestedSQLException("Error setting nested bean property. Cause: " + e, e); } } /** * Prepare primitive parameter object. * * @param rs * the rs * @param mapping * the mapping * @param parameterType * the parameter type * * @return the object * * @throws SQLException * the SQL exception */ private Object preparePrimitiveParameterObject(ResultSet rs, ResultMapping mapping, Class parameterType) throws SQLException { Object parameterObject; TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory(); TypeHandler th = typeHandlerFactory.getTypeHandler(parameterType); parameterObject = th.getResult(rs, mapping.getColumnName()); return parameterObject; } /** * New document. * * @param root * the root * * @return the document */ private Document newDocument(String root) { try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); doc.appendChild(doc.createElement(root)); return doc; } catch (ParserConfigurationException e) { throw new RuntimeException("Error creating XML document. Cause: " + e); } } /** * Prepare dom parameter object. * * @param rs * the rs * @param mapping * the mapping * * @return the object * * @throws SQLException * the SQL exception */ private Object prepareDomParameterObject(ResultSet rs, ResultMapping mapping) throws SQLException { TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory(); Document doc = newDocument("parameter"); Probe probe = ProbeFactory.getProbe(doc); String complexName = mapping.getColumnName(); TypeHandler stringTypeHandler = typeHandlerFactory.getTypeHandler(String.class); if (complexName.indexOf('=') > -1) { // old 1.x style multiple params StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false); while (parser.hasMoreTokens()) { String propName = parser.nextToken(); String colName = parser.nextToken(); Object propValue = stringTypeHandler.getResult(rs, colName); probe.setObject(doc, propName, propValue.toString()); } } else { // single param Object propValue = stringTypeHandler.getResult(rs, complexName); probe.setObject(doc, "value", propValue.toString()); } return doc; } /** * Prepare bean parameter object. * * @param statementScope * the statement scope * @param rs * the rs * @param mapping * the mapping * @param parameterType * the parameter type * * @return the object * * @throws InstantiationException * the instantiation exception * @throws IllegalAccessException * the illegal access exception * @throws SQLException * the SQL exception */ private Object prepareBeanParameterObject(StatementScope statementScope, ResultSet rs, ResultMapping mapping, Class parameterType) throws InstantiationException, IllegalAccessException, SQLException { TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory(); Object parameterObject; if (parameterType == null) { parameterObject = new HashMap(); } else { parameterObject = ResultObjectFactoryUtil.createObjectThroughFactory(parameterType); } String complexName = mapping.getColumnName(); if (complexName.indexOf('=') > -1 || complexName.indexOf(',') > -1) { StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false); while (parser.hasMoreTokens()) { String propName = parser.nextToken(); String colName = parser.nextToken(); Class propType = PROBE.getPropertyTypeForSetter(parameterObject, propName); TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(propType); Object propValue = propTypeHandler.getResult(rs, colName); PROBE.setObject(parameterObject, propName, propValue); } } else { // single param TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(parameterType); if (propTypeHandler == null) { propTypeHandler = typeHandlerFactory.getUnkownTypeHandler(); } parameterObject = propTypeHandler.getResult(rs, complexName); } return parameterObject; } /** * Gets the primitive result mapping value. * * @param rs * the rs * @param mapping * the mapping * * @return the primitive result mapping value * * @throws SQLException * the SQL exception */ protected Object getPrimitiveResultMappingValue(ResultSet rs, ResultMapping mapping) throws SQLException { Object value = null; TypeHandler typeHandler = mapping.getTypeHandler(); if (typeHandler != null) { String columnName = mapping.getColumnName(); int columnIndex = mapping.getColumnIndex(); if (columnName == null) { value = typeHandler.getResult(rs, columnIndex); } else { value = typeHandler.getResult(rs, columnName); } } else { throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName() + "' to the column '" + mapping.getColumnName() + "'. One or both of the types, or the combination of types is not supported."); } return value; } /** * Do null mapping. * * @param value * the value * @param mapping * the mapping * * @return the object * * @throws SqlMapException * the sql map exception */ protected Object doNullMapping(Object value, ResultMapping mapping) throws SqlMapException { if (value == null) { TypeHandler typeHandler = mapping.getTypeHandler(); if (typeHandler != null) { String nullValue = mapping.getNullValue(); if (nullValue != null) value = typeHandler.valueOf(nullValue); return value; } else { throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName() + "' to the column '" + mapping.getColumnName() + "'. One or both of the types, or the combination of types is not supported."); } } else { return value; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy