com.ibatis.sqlmap.engine.mapping.result.ResultMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mybatis2 Show documentation
Show all versions of mybatis2 Show documentation
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;
}
}
}