ResultLoaderMap.java
- /*
- * Copyright 2009-2024 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 org.apache.ibatis.executor.loader;
- import java.io.Serializable;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.security.AccessController;
- import java.security.PrivilegedActionException;
- import java.security.PrivilegedExceptionAction;
- import java.sql.SQLException;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Set;
- import org.apache.ibatis.cursor.Cursor;
- import org.apache.ibatis.executor.BaseExecutor;
- import org.apache.ibatis.executor.BatchResult;
- import org.apache.ibatis.executor.ExecutorException;
- import org.apache.ibatis.logging.Log;
- import org.apache.ibatis.logging.LogFactory;
- import org.apache.ibatis.mapping.BoundSql;
- import org.apache.ibatis.mapping.MappedStatement;
- import org.apache.ibatis.reflection.MetaObject;
- import org.apache.ibatis.session.Configuration;
- import org.apache.ibatis.session.ResultHandler;
- import org.apache.ibatis.session.RowBounds;
- /**
- * @author Clinton Begin
- * @author Franta Mejta
- */
- public class ResultLoaderMap {
- private final Map<String, LoadPair> loaderMap = new HashMap<>();
- public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
- String upperFirst = getUppercaseFirstProperty(property);
- if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
- throw new ExecutorException("Nested lazy loaded result property '" + property + "' for query id '"
- + resultLoader.mappedStatement.getId()
- + " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
- }
- loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
- }
- public final Map<String, LoadPair> getProperties() {
- return new HashMap<>(this.loaderMap);
- }
- public Set<String> getPropertyNames() {
- return loaderMap.keySet();
- }
- public int size() {
- return loaderMap.size();
- }
- public boolean isEmpty() {
- return loaderMap.isEmpty();
- }
- public boolean hasLoader(String property) {
- return loaderMap.containsKey(property.toUpperCase(Locale.ENGLISH));
- }
- public boolean load(String property) throws SQLException {
- LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
- if (pair != null) {
- pair.load();
- return true;
- }
- return false;
- }
- public void remove(String property) {
- loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
- }
- public void loadAll() throws SQLException {
- final Set<String> methodNameSet = loaderMap.keySet();
- String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
- for (String methodName : methodNames) {
- load(methodName);
- }
- }
- private static String getUppercaseFirstProperty(String property) {
- String[] parts = property.split("\\.");
- return parts[0].toUpperCase(Locale.ENGLISH);
- }
- /**
- * Property which was not loaded yet.
- */
- public static class LoadPair implements Serializable {
- private static final long serialVersionUID = 20130412;
- /**
- * Name of factory method which returns database connection.
- */
- private static final String FACTORY_METHOD = "getConfiguration";
- /**
- * Object to check whether we went through serialization..
- */
- private final transient Object serializationCheck = new Object();
- /**
- * Meta object which sets loaded properties.
- */
- private transient MetaObject metaResultObject;
- /**
- * Result loader which loads unread properties.
- */
- private transient ResultLoader resultLoader;
- /**
- * Wow, logger.
- */
- private transient Log log;
- /**
- * Factory class through which we get database connection.
- */
- private Class<?> configurationFactory;
- /**
- * Name of the unread property.
- */
- private final String property;
- /**
- * ID of SQL statement which loads the property.
- */
- private String mappedStatement;
- /**
- * Parameter of the sql statement.
- */
- private Serializable mappedParameter;
- private LoadPair(final String property, MetaObject metaResultObject, ResultLoader resultLoader) {
- this.property = property;
- this.metaResultObject = metaResultObject;
- this.resultLoader = resultLoader;
- /* Save required information only if original object can be serialized. */
- if (metaResultObject != null && metaResultObject.getOriginalObject() instanceof Serializable) {
- final Object mappedStatementParameter = resultLoader.parameterObject;
- /* @todo May the parameter be null? */
- if (mappedStatementParameter instanceof Serializable) {
- this.mappedStatement = resultLoader.mappedStatement.getId();
- this.mappedParameter = (Serializable) mappedStatementParameter;
- this.configurationFactory = resultLoader.configuration.getConfigurationFactory();
- } else {
- Log log = this.getLogger();
- if (log.isDebugEnabled()) {
- log.debug("Property [" + this.property + "] of [" + metaResultObject.getOriginalObject().getClass()
- + "] cannot be loaded " + "after deserialization. Make sure it's loaded before serializing "
- + "forenamed object.");
- }
- }
- }
- }
- public void load() throws SQLException {
- /*
- * These field should not be null unless the loadpair was serialized. Yet in that case this method should not be
- * called.
- */
- if (this.metaResultObject == null) {
- throw new IllegalArgumentException("metaResultObject is null");
- }
- if (this.resultLoader == null) {
- throw new IllegalArgumentException("resultLoader is null");
- }
- this.load(null);
- }
- public void load(final Object userObject) throws SQLException {
- if (this.metaResultObject == null || this.resultLoader == null) {
- if (this.mappedParameter == null) {
- throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
- + "required parameter of mapped statement [" + this.mappedStatement + "] is not serializable.");
- }
- final Configuration config = this.getConfiguration();
- final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
- if (ms == null) {
- throw new ExecutorException(
- "Cannot lazy load property [" + this.property + "] of deserialized object [" + userObject.getClass()
- + "] because configuration does not contain statement [" + this.mappedStatement + "]");
- }
- this.metaResultObject = config.newMetaObject(userObject);
- this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
- metaResultObject.getSetterType(this.property), null, null);
- }
- /*
- * We are using a new executor because we may be (and likely are) on a new thread and executors aren't thread
- * safe. (Is this sufficient?) A better approach would be making executors thread safe.
- */
- if (this.serializationCheck == null) {
- final ResultLoader old = this.resultLoader;
- this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
- old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
- }
- this.metaResultObject.setValue(property, this.resultLoader.loadResult());
- }
- private Configuration getConfiguration() {
- if (this.configurationFactory == null) {
- throw new ExecutorException("Cannot get Configuration as configuration factory was not set.");
- }
- Object configurationObject;
- try {
- final Method factoryMethod = this.configurationFactory.getDeclaredMethod(FACTORY_METHOD);
- if (!Modifier.isStatic(factoryMethod.getModifiers())) {
- throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
- + FACTORY_METHOD + "] is not static.");
- }
- if (!factoryMethod.isAccessible()) {
- configurationObject = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
- try {
- factoryMethod.setAccessible(true);
- return factoryMethod.invoke(null);
- } finally {
- factoryMethod.setAccessible(false);
- }
- });
- } else {
- configurationObject = factoryMethod.invoke(null);
- }
- } catch (final ExecutorException ex) {
- throw ex;
- } catch (final NoSuchMethodException ex) {
- throw new ExecutorException("Cannot get Configuration as factory class [" + this.configurationFactory
- + "] is missing factory method of name [" + FACTORY_METHOD + "].", ex);
- } catch (final PrivilegedActionException ex) {
- throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
- + FACTORY_METHOD + "] threw an exception.", ex.getCause());
- } catch (final Exception ex) {
- throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
- + FACTORY_METHOD + "] threw an exception.", ex);
- }
- if (!(configurationObject instanceof Configuration)) {
- throw new ExecutorException("Cannot get Configuration as factory method [" + this.configurationFactory + "]#["
- + FACTORY_METHOD + "] didn't return [" + Configuration.class + "] but ["
- + (configurationObject == null ? "null" : configurationObject.getClass()) + "].");
- }
- return Configuration.class.cast(configurationObject);
- }
- private Log getLogger() {
- if (this.log == null) {
- this.log = LogFactory.getLog(this.getClass());
- }
- return this.log;
- }
- }
- private static final class ClosedExecutor extends BaseExecutor {
- public ClosedExecutor() {
- super(null, null);
- }
- @Override
- public boolean isClosed() {
- return true;
- }
- @Override
- protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
- throw new UnsupportedOperationException("Not supported.");
- }
- @Override
- protected List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
- throw new UnsupportedOperationException("Not supported.");
- }
- @Override
- protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
- ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
- throw new UnsupportedOperationException("Not supported.");
- }
- @Override
- protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
- throws SQLException {
- throw new UnsupportedOperationException("Not supported.");
- }
- }
- }