可以看出,如果查询结果对象中有一个属性是需要延迟加载的,那整个结果对象就是一个代理对象,后面对这个对象的访问,都是通过代理对象去访问的,那是否有更好的方法呢?
答案是有的。
Student对象关联Teacher对象,需要对Teacher对象延迟加载。
一种方式就是mybatis当前的这种做法,对Student进行代理,当访问到getTeacher时就进行延迟加载,设置其teacher对象;
还有一种方式就是对teacher对象进行代理,当第一次加载之后,就将Student对象关联的teacher对象替换为真实的对象,而不再使用代理对象,这样后面对Student及Teacher的访问,就都是访问的实际对象,而不是代理对象了。
修改之后的代码:http://download.csdn.net/detail/u014569459/7372195
关键修改点说明:
1.修改orgapacheibatisexecutor esultsetDefaultResultSetHandler.java类,注销掉里面生成代理对象的代码
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); //comment by jerry // if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { // final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); // for (ResultMapping propertyMapping : propertyMappings) { // if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149 // return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); // } // } // } return resultObject; }
2.修改orgapacheibatisexecutorloadercglibCglibProxyFactory.java类
1)在createProxy方法增加两个参数Object parentObj,String fieldNameInParent,用于访问父对象。
public Object createProxy(Object parentObj,String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(parentObj,fieldNameInParent,target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); }
2)在EnhancedResultObjectProxyImpl类中,增加parentObj和属性,并修改默认构造函数。
private Object parentObj; private String fieldNameInParent; private EnhancedResultObjectProxyImpl(Object parentObj,String fieldNameInParent,Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { this.type = type; this.lazyLoader = lazyLoader; this.aggressive = configuration.isAggressiveLazyLoading(); this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods(); this.objectFactory = objectFactory; this.constructorArgTypes = constructorArgTypes; this.constructorArgs = constructorArgs; this.parentObj = parentObj; this.fieldNameInParent = fieldNameInParent; }
3)修改intercept函数
if(parentObj!=null) { Field field = parentObj.getClass().getDeclaredField(fieldNameInParent); Object obj = field.get(parentObj); return method.invoke(obj, args); } else { return methodProxy.invokeSuper(enhanced, args); }
修改文件列表(共4个):
1.CglibProxyFactory.java
/* * Copyright 2009-2013 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.executor.loader.cglib; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ProxyFactory; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; import org.apache.ibatis.io.Resources; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.property.PropertyCopier; import org.apache.ibatis.reflection.property.PropertyNamer; import org.apache.ibatis.session.Configuration; /** * @author Clinton Begin */ public class CglibProxyFactory implements ProxyFactory { private static final Log log = LogFactory.getLog(CglibProxyFactory.class); private static final String FINALIZE_METHOD = "finalize"; private static final String WRITE_REPLACE_METHOD = "writeReplace"; public CglibProxyFactory() { try { Resources.classForName("net.sf.cglib.proxy.Enhancer"); } catch (Throwable e) { throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e); } } public Object createProxy(Object parentObj,String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(parentObj,fieldNameInParent,target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(null,null,target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedDeserializationProxyImpl.createProxy(null,null,target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { } private static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { Enhancer enhancer = new Enhancer(); enhancer.setCallback(callback); enhancer.setSuperclass(type); try { type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } catch (NoSuchMethodException e) { enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced = null; if (constructorArgTypes.isEmpty()) { enhanced = enhancer.create(); } else { Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); enhanced = enhancer.create(typesArray, valuesArray); } return enhanced; } private static class EnhancedResultObjectProxyImpl implements MethodInterceptor { private Class<?> type; private ResultLoaderMap lazyLoader; private boolean aggressive; private Set<String> lazyLoadTriggerMethods; private ObjectFactory objectFactory; private List<Class<?>> constructorArgTypes; private List<Object> constructorArgs; private Object parentObj; private String fieldNameInParent; private EnhancedResultObjectProxyImpl(Object parentObj,String fieldNameInParent,Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { this.type = type; this.lazyLoader = lazyLoader; this.aggressive = configuration.isAggressiveLazyLoading(); this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods(); this.objectFactory = objectFactory; this.constructorArgTypes = constructorArgTypes; this.constructorArgs = constructorArgs; this.parentObj = parentObj; this.fieldNameInParent = fieldNameInParent; } public static Object createProxy(Object parentObj,String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(parentObj,fieldNameInParent,type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { Object original = null; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { lazyLoader.loadAll(); } else if (PropertyNamer.isProperty(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); } } } } } if(parentObj!=null) { Field field = parentObj.getClass().getDeclaredField(fieldNameInParent); Object obj = field.get(parentObj); return method.invoke(obj, args); } else { return methodProxy.invokeSuper(enhanced, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { private EnhancedDeserializationProxyImpl(Object parentObj,String fieldNameInParent,Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public static Object createProxy(Object parentObj,String fieldNameInParent,Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(parentObj,fieldNameInParent,type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } @Override public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { final Object o = super.invoke(enhanced, method, args); return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args); } @Override protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } } }
2.JavassistProxyFactory.java
/* * Copyright 2009-2012 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.executor.loader.javassist; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.Proxy; import javassist.util.proxy.ProxyFactory; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.executor.loader.AbstractEnhancedDeserializationProxy; import org.apache.ibatis.executor.loader.AbstractSerialStateHolder; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.loader.WriteReplaceInterface; import org.apache.ibatis.io.Resources; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.property.PropertyCopier; import org.apache.ibatis.reflection.property.PropertyNamer; import org.apache.ibatis.session.Configuration; /** * @author Eduardo Macarron */ public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { private static final Log log = LogFactory.getLog(JavassistProxyFactory.class); private static final String FINALIZE_METHOD = "finalize"; private static final String WRITE_REPLACE_METHOD = "writeReplace"; public JavassistProxyFactory() { try { Resources.classForName("javassist.util.proxy.ProxyFactory"); } catch (Throwable e) { throw new IllegalStateException("Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.", e); } } public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(null,null,target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } public Object createProxy(Object parentObj, String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(parentObj,fieldNameInParent,target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { } private static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } catch (NoSuchMethodException e) { enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced = null; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } ((Proxy) enhanced).setHandler(callback); return enhanced; } private static class EnhancedResultObjectProxyImpl implements MethodHandler { private Class<?> type; private ResultLoaderMap lazyLoader; private boolean aggressive; private Set<String> lazyLoadTriggerMethods; private ObjectFactory objectFactory; private List<Class<?>> constructorArgTypes; private List<Object> constructorArgs; private Object parentObj; private String fieldNameInParent; private EnhancedResultObjectProxyImpl(Object parentObj, String fieldNameInParent,Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { this.type = type; this.lazyLoader = lazyLoader; this.aggressive = configuration.isAggressiveLazyLoading(); this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods(); this.objectFactory = objectFactory; this.constructorArgTypes = constructorArgTypes; this.constructorArgs = constructorArgs; this.parentObj = parentObj; this.fieldNameInParent = fieldNameInParent; } public static Object createProxy(Object parentObj, String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(parentObj, fieldNameInParent,type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { Object original = null; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { lazyLoader.loadAll(); } else if (PropertyNamer.isProperty(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); } } } } } if(parentObj!=null) { Field field = parentObj.getClass().getDeclaredField(fieldNameInParent); Object obj = field.get(parentObj); return method.invoke(obj, args); } else { return methodProxy.invoke(enhanced, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodHandler { private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final Object o = super.invoke(enhanced, method, args); return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invoke(o, args); } @Override protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return new JavassistSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } } }
3.ProxyFactory.java
/* * Copyright 2012 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.executor.loader; import java.util.List; import java.util.Properties; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.session.Configuration; /** * @author Eduardo Macarron */ public interface ProxyFactory { void setProperties(Properties properties); Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); Object createProxy(Object parentObj,String fieldNameInParent,Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); }
4.DefaultResultSetHandler.java
/* * Copyright 2009-2014 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.executor.resultset; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.executor.loader.ResultLoader; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.executor.result.DefaultResultHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.Discriminator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.reflection.MetaClass; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.session.AutoMappingBehavior; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; /** * @author Clinton Begin * @author Eduardo Macarron */ public class DefaultResultSetHandler implements ResultSetHandler { private static final Object NO_VALUE = new Object(); private final Executor executor; private final Configuration configuration; private final MappedStatement mappedStatement; private final RowBounds rowBounds; private final ParameterHandler parameterHandler; private final ResultHandler resultHandler; private final BoundSql boundSql; private final TypeHandlerRegistry typeHandlerRegistry; private final ObjectFactory objectFactory; // nested resultmaps private final Map<CacheKey, Object> nestedResultObjects = new HashMap<CacheKey, Object>(); private final Map<CacheKey, Object> ancestorObjects = new HashMap<CacheKey, Object>(); private final Map<String, String> ancestorColumnPrefix = new HashMap<String, String>(); // multiple resultsets private final Map<String, ResultMapping> nextResultMaps = new HashMap<String, ResultMapping>(); private final Map<CacheKey, PendingRelation> pendingRelations = new HashMap<CacheKey, PendingRelation>(); private static class PendingRelation { public MetaObject metaObject; public ResultMapping propertyMapping; } public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) { this.executor = executor; this.configuration = mappedStatement.getConfiguration(); this.mappedStatement = mappedStatement; this.rowBounds = rowBounds; this.parameterHandler = parameterHandler; this.boundSql = boundSql; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.objectFactory = configuration.getObjectFactory(); this.resultHandler = resultHandler; } // // HANDLE OUTPUT PARAMETER // public void handleOutputParameters(CallableStatement cs) throws SQLException { final Object parameterObject = parameterHandler.getParameterObject(); final MetaObject metaParam = configuration.newMetaObject(parameterObject); final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); for (int i = 0; i < parameterMappings.size(); i++) { final ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) { if (ResultSet.class.equals(parameterMapping.getJavaType())) { handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam); } else { final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler(); metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1)); } } } } private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam) throws SQLException { try { final String resultMapId = parameterMapping.getResultMapId(); final ResultMap resultMap = configuration.getResultMap(resultMapId); final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory); final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration); handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null); metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList()); } finally { closeResultSet(rs); // issue #228 (close resultsets) } } // // HANDLE RESULT SETS // public List<Object> handleResultSets(Statement stmt) throws SQLException { final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResulSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); } private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException { ResultSet rs = stmt.getResultSet(); while (rs == null) { // move forward to get the first resultset in case the driver // doesn't return the resultset as the first result (HSQLDB 2.1) if (stmt.getMoreResults()) { rs = stmt.getResultSet(); } else { if (stmt.getUpdateCount() == -1) { // no more results. Must be no resultset break; } } } return rs != null ? new ResultSetWrapper(rs, configuration) : null; } private ResultSetWrapper getNextResultSet(Statement stmt) throws SQLException { // Making this method tolerant of bad JDBC drivers try { if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) { // Crazy Standard JDBC way of determining if there are more results if (!((!stmt.getMoreResults()) && (stmt.getUpdateCount() == -1))) { ResultSet rs = stmt.getResultSet(); return rs != null ? new ResultSetWrapper(rs, configuration) : null; } } } catch (Exception e) { // Intentionally ignored. } return null; } private void closeResultSet(ResultSet rs) { try { if (rs != null) { rs.close(); } } catch (SQLException e) { // ignore } } private void cleanUpAfterHandlingResultSet() { nestedResultObjects.clear(); ancestorColumnPrefix.clear(); } private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) { if (rsw != null && resultMapCount < 1) { throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId() + "'. It's likely that neither a Result Type nor a Result Map was specified."); } } private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets) } } private List<Object> collapseSingleResultList(List<Object> multipleResults) { if (multipleResults.size() == 1) { @SuppressWarnings("unchecked") List<Object> returned = (List<Object>) multipleResults.get(0); return returned; } return multipleResults; } // // HANDLE ROWS FOR SIMPLE RESULTMAP // private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } } private void ensureNoRowBounds() { if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) { throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. " + "Use safeRowBoundsEnabled=false setting to bypass this check."); } } protected void checkResultHandler() { if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) { throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. " + "Use safeResultHandlerEnabled=false setting to bypass this check " + "or ensure your statement returns ordered data and set resultOrdered=true on it."); } } private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(rsw.getResultSet(), resultContext, rowBounds)) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { if (parentMapping != null) { linkToParent(rs, parentMapping, rowValue); } else { callResultHandler(resultHandler, resultContext, rowValue); } } private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) { resultContext.nextResultObject(rowValue); resultHandler.handleResult(resultContext); } private boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException { return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit(); } private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) { rs.absolute(rowBounds.getOffset()); } } else { for (int i = 0; i < rowBounds.getOffset(); i++) { rs.next(); } } } // // GET VALUE FROM ROW FOR SIMPLE RESULT MAP // private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = resultMap.getConstructorResultMappings().size() > 0; if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(resultObject,rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; } return resultObject; } private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean def) { return resultMap.getAutoMapping() != null ? resultMap.getAutoMapping() : def; } // // PROPERTY MAPPINGS // private boolean applyPropertyMappings(Object resultObject,ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); //added by jerry if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { value = new dao.domain.Teacher(); try { value = propertyMapping.getJavaType().newInstance(); value = configuration.getProxyFactory().createProxy(resultObject,propertyMapping.getProperty(),value, lazyLoader, configuration, objectFactory, new ArrayList(), new ArrayList()); } catch (Exception e) { e.printStackTrace(); } } final String property = propertyMapping.getProperty(); // issue #541 make property optional if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls if (value != null || !metaObject.getSetterType(property).isPrimitive()) { metaObject.setValue(property, value); } foundValues = true; } } } return foundValues; } private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); return NO_VALUE; } else if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it return NO_VALUE; } else { final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); } } private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; for (String columnName : unmappedColumnNames) { String propertyName = columnName; if (columnPrefix != null && columnPrefix.length() > 0) { // When columnPrefix is specified, // ignore columns without the prefix. if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { propertyName = columnName.substring(columnPrefix.length()); } else { continue; } } final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); if (property != null && metaObject.hasSetter(property)) { final Class<?> propertyType = metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType)) { final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName); final Object value = typeHandler.getResult(rsw.getResultSet(), columnName); if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls if (value != null || !propertyType.isPrimitive()) { metaObject.setValue(property, value); } foundValues = true; } } } } return foundValues; } // MULTIPLE RESULT SETS private void linkToParent(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException { CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn()); PendingRelation parent = pendingRelations.get(parentKey); if (parent != null) { final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(parent.propertyMapping, parent.metaObject); if (rowValue != null) { if (collectionProperty != null) { final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); targetMetaObject.add(rowValue); } else { parent.metaObject.setValue(parent.propertyMapping.getProperty(), rowValue); } } } } private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) { final String propertyName = resultMapping.getProperty(); Object propertyValue = metaObject.getValue(propertyName); if (propertyValue == null) { Class<?> type = resultMapping.getJavaType(); if (type == null) { type = metaObject.getSetterType(propertyName); } try { if (objectFactory.isCollection(type)) { propertyValue = objectFactory.create(type); metaObject.setValue(propertyName, propertyValue); return propertyValue; } } catch (Exception e) { throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } else if (objectFactory.isCollection(propertyValue.getClass())) { return propertyValue; } return null; } private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException { CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getColumn()); PendingRelation deferLoad = new PendingRelation(); deferLoad.metaObject = metaResultObject; deferLoad.propertyMapping = parentMapping; pendingRelations.put(cacheKey, deferLoad); ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet()); if (previous == null) { nextResultMaps.put(parentMapping.getResultSet(), parentMapping); } else { if (!previous.equals(parentMapping)) { throw new ExecutorException("Two different properties are mapped to the same resultSet"); } } } private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) throws SQLException { CacheKey cacheKey = new CacheKey(); cacheKey.update(resultMapping); if (columns != null && names != null) { String[] columnsArray = columns.split(","); String[] namesArray = names.split(","); for (int i = 0 ; i < columnsArray.length ; i++) { Object value = rs.getString(columnsArray[i]); if (value != null) { cacheKey.update(namesArray[i]); cacheKey.update(value); } } } return cacheKey; } // // INSTANTIATION & CONSTRUCTOR MAPPING // private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); //comment by jerry // if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { // final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); // for (ResultMapping propertyMapping : propertyMappings) { // if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149 // return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); // } // } // } return resultObject; } private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException { final Class<?> resultType = resultMap.getType(); final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings(); if (typeHandlerRegistry.hasTypeHandler(resultType)) { return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } else if (constructorMappings.size() > 0) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else { return objectFactory.create(resultType); } } private Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException { boolean foundValues = false; for (ResultMapping constructorMapping : constructorMappings) { final Class<?> parameterType = constructorMapping.getJavaType(); final String column = constructorMapping.getColumn(); final Object value; if (constructorMapping.getNestedQueryId() != null) { value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix); } else if (constructorMapping.getNestedResultMapId() != null) { final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId()); value = getRowValue(rsw, resultMap); } else { final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler(); value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix)); } constructorArgTypes.add(parameterType); constructorArgs.add(value); foundValues = value != null || foundValues; } return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; } private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final Class<?> resultType = resultMap.getType(); final String columnName; if (resultMap.getResultMappings().size() > 0) { final List<ResultMapping> resultMappingList = resultMap.getResultMappings(); final ResultMapping mapping = resultMappingList.get(0); columnName = prependPrefix(mapping.getColumn(), columnPrefix); } else { columnName = rsw.getColumnNames().get(0); } final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName); return typeHandler.getResult(rsw.getResultSet(), columnName); } // // NESTED QUERY // private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException { final String nestedQueryId = constructorMapping.getNestedQueryId(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix); Object value = null; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class<?> targetType = constructorMapping.getJavaType(); final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); value = resultLoader.loadResult(); } return value; } private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final String nestedQueryId = propertyMapping.getNestedQueryId(); final String property = propertyMapping.getProperty(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); Object value = NO_VALUE; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class<?> targetType = propertyMapping.getJavaType(); if (executor.isCached(nestedQuery, key)) { executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); } else { final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); if (propertyMapping.isLazy()) { lazyLoader.addLoader(property, metaResultObject, resultLoader); } else { value = resultLoader.loadResult(); } } } return value; } private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException { if (resultMapping.isCompositeResult()) { return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix); } else { return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix); } } private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException { final TypeHandler<?> typeHandler; if (typeHandlerRegistry.hasTypeHandler(parameterType)) { typeHandler = typeHandlerRegistry.getTypeHandler(parameterType); } else { typeHandler = typeHandlerRegistry.getUnknownTypeHandler(); } return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); } private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException { final Object parameterObject = instantiateParameterObject(parameterType); final MetaObject metaObject = configuration.newMetaObject(parameterObject); boolean foundValues = false; for (ResultMapping innerResultMapping : resultMapping.getComposites()) { final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty()); final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType); final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix)); if (propValue != null) { // issue #353 & #560 do not execute nested query if key is null metaObject.setValue(innerResultMapping.getProperty(), propValue); foundValues = true; } } return foundValues ? parameterObject : null; } private Object instantiateParameterObject(Class<?> parameterType) { if (parameterType == null) { return new HashMap<Object, Object>(); } else { return objectFactory.create(parameterType); } } // // DISCRIMINATOR // public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException { Set<String> pastDiscriminators = new HashSet<String>(); Discriminator discriminator = resultMap.getDiscriminator(); while (discriminator != null) { final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix); final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value)); if (configuration.hasResultMap(discriminatedMapId)) { resultMap = configuration.getResultMap(discriminatedMapId); Discriminator lastDiscriminator = discriminator; discriminator = resultMap.getDiscriminator(); if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) { break; } } else { break; } } return resultMap; } private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException { final ResultMapping resultMapping = discriminator.getResultMapping(); final TypeHandler<?> typeHandler = resultMapping.getTypeHandler(); return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); } private String prependPrefix(String columnName, String prefix) { if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) { return columnName; } return prefix + columnName; } // // HANDLE NESTED RESULT MAPS // private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { final DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); Object rowValue = null; while (shouldProcessMoreRows(rsw.getResultSet(), resultContext, rowBounds)) { final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); Object partialObject = nestedResultObjects.get(rowKey); if (mappedStatement.isResultOrdered()) { // issue #577 && #542 if (partialObject == null && rowValue != null) { nestedResultObjects.clear(); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject); } else { rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject); if (partialObject == null) { storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } } if (rowValue != null && mappedStatement.isResultOrdered()) { storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } // // GET VALUE FROM ROW FOR NESTED RESULT MAP // private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, CacheKey absoluteKey, String columnPrefix, Object partialObject) throws SQLException { final String resultMapId = resultMap.getId(); Object resultObject = partialObject; if (resultObject != null) { final MetaObject metaObject = configuration.newMetaObject(resultObject); putAncestor(absoluteKey, resultObject, resultMapId, columnPrefix); applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); ancestorObjects.remove(absoluteKey); } else { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); resultObject = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = resultMap.getConstructorResultMappings().size() > 0; if (shouldApplyAutomaticMappings(resultMap, AutoMappingBehavior.FULL.equals(configuration.getAutoMappingBehavior()))) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(resultObject,rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; putAncestor(absoluteKey, resultObject, resultMapId, columnPrefix); foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; ancestorObjects.remove(absoluteKey); foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; } if (combinedKey != CacheKey.NULL_CACHE_KEY) nestedResultObjects.put(combinedKey, resultObject); } return resultObject; } private void putAncestor(CacheKey rowKey, Object resultObject, String resultMapId, String columnPrefix) { if (!ancestorColumnPrefix.containsKey(resultMapId)) { ancestorColumnPrefix.put(resultMapId, columnPrefix); } ancestorObjects.put(rowKey, resultObject); } // // NESTED RESULT MAP (JOIN MAPPING) // private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { final String nestedResultMapId = resultMapping.getNestedResultMapId(); if (nestedResultMapId != null && resultMapping.getResultSet() == null) { try { final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); CacheKey rowKey = null; Object ancestorObject = null; if (ancestorColumnPrefix.containsKey(nestedResultMapId)) { rowKey = createRowKey(nestedResultMap, rsw, ancestorColumnPrefix.get(nestedResultMapId)); ancestorObject = ancestorObjects.get(rowKey); } if (ancestorObject != null) { if (newObject) metaObject.setValue(resultMapping.getProperty(), ancestorObject); } else { rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); Object rowValue = nestedResultObjects.get(combinedKey); boolean knownValue = (rowValue != null); final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw.getResultSet())) { rowValue = getRowValue(rsw, nestedResultMap, combinedKey, rowKey, columnPrefix, rowValue); if (rowValue != null && !knownValue) { if (collectionProperty != null) { final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); targetMetaObject.add(rowValue); } else { metaObject.setValue(resultMapping.getProperty(), rowValue); } foundValues = true; } } } } catch (SQLException e) { throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } } return foundValues; } private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) { final StringBuilder columnPrefixBuilder = new StringBuilder(); if (parentPrefix != null) columnPrefixBuilder.append(parentPrefix); if (resultMapping.getColumnPrefix() != null) columnPrefixBuilder.append(resultMapping.getColumnPrefix()); final String columnPrefix = columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH); return columnPrefix; } private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSet rs) throws SQLException { Set<String> notNullColumns = resultMapping.getNotNullColumns(); boolean anyNotNullColumnHasValue = true; if (notNullColumns != null && !notNullColumns.isEmpty()) { anyNotNullColumnHasValue = false; for (String column: notNullColumns) { rs.getObject(prependPrefix(column, columnPrefix)); if (!rs.wasNull()) { anyNotNullColumnHasValue = true; break; } } } return anyNotNullColumnHasValue; } private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) throws SQLException { ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId); nestedResultMap = resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix); return nestedResultMap; } // // UNIQUE RESULT KEY // private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException { final CacheKey cacheKey = new CacheKey(); cacheKey.update(resultMap.getId()); List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap); if (resultMappings.size() == 0) { if (Map.class.isAssignableFrom(resultMap.getType())) { createRowKeyForMap(rsw, cacheKey); } else { createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix); } } else { createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix); } return cacheKey; } private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) { if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) { CacheKey combinedKey; try { combinedKey = rowKey.clone(); } catch (CloneNotSupportedException e) { throw new ExecutorException("Error cloning cache key. Cause: " + e, e); } combinedKey.update(parentRowKey); return combinedKey; } return CacheKey.NULL_CACHE_KEY; } private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) { List<ResultMapping> resultMappings = resultMap.getIdResultMappings(); if (resultMappings.size() == 0) { resultMappings = resultMap.getPropertyResultMappings(); } return resultMappings; } private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException { for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null) { // Issue #392 final ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId()); createRowKeyForMappedProperties(nestedResultMap, rsw, cacheKey, nestedResultMap.getConstructorResultMappings(), prependPrefix(resultMapping.getColumnPrefix(), columnPrefix)); } else if (resultMapping.getNestedQueryId() == null) { final String column = prependPrefix(resultMapping.getColumn(), columnPrefix); final TypeHandler<?> th = resultMapping.getTypeHandler(); List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) { // Issue #114 final Object value = th.getResult(rsw.getResultSet(), column); if (value != null) { cacheKey.update(column); cacheKey.update(value); } } } } } private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, String columnPrefix) throws SQLException { final MetaClass metaType = MetaClass.forClass(resultMap.getType()); List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); for (String column : unmappedColumnNames) { String property = column; if (columnPrefix != null && columnPrefix.length() > 0) { // When columnPrefix is specified, // ignore columns without the prefix. if (column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { property = column.substring(columnPrefix.length()); } else { continue; } } if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) { String value = rsw.getResultSet().getString(column); if (value != null) { cacheKey.update(column); cacheKey.update(value); } } } } private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException { List<String> columnNames = rsw.getColumnNames(); for (String columnName : columnNames) { final String value = rsw.getResultSet().getString(columnName); if (value != null) { cacheKey.update(columnName); cacheKey.update(value); } } } }