View Javadoc
1   /*
2    *    Copyright 2009-2021 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.executor.resultset;
17  
18  import java.sql.ResultSet;
19  import java.sql.ResultSetMetaData;
20  import java.sql.SQLException;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.ibatis.io.Resources;
31  import org.apache.ibatis.mapping.ResultMap;
32  import org.apache.ibatis.session.Configuration;
33  import org.apache.ibatis.type.JdbcType;
34  import org.apache.ibatis.type.ObjectTypeHandler;
35  import org.apache.ibatis.type.TypeHandler;
36  import org.apache.ibatis.type.TypeHandlerRegistry;
37  import org.apache.ibatis.type.UnknownTypeHandler;
38  
39  /**
40   * @author Iwao AVE!
41   */
42  public class ResultSetWrapper {
43  
44    private final ResultSet resultSet;
45    private final TypeHandlerRegistry typeHandlerRegistry;
46    private final List<String> columnNames = new ArrayList<>();
47    private final List<String> classNames = new ArrayList<>();
48    private final List<JdbcType> jdbcTypes = new ArrayList<>();
49    private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
50    private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
51    private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
52  
53    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
54      super();
55      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
56      this.resultSet = rs;
57      final ResultSetMetaData metaData = rs.getMetaData();
58      final int columnCount = metaData.getColumnCount();
59      for (int i = 1; i <= columnCount; i++) {
60        columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
61        jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
62        classNames.add(metaData.getColumnClassName(i));
63      }
64    }
65  
66    public ResultSet getResultSet() {
67      return resultSet;
68    }
69  
70    public List<String> getColumnNames() {
71      return this.columnNames;
72    }
73  
74    public List<String> getClassNames() {
75      return Collections.unmodifiableList(classNames);
76    }
77  
78    public List<JdbcType> getJdbcTypes() {
79      return jdbcTypes;
80    }
81  
82    public JdbcType getJdbcType(String columnName) {
83      for (int i = 0; i < columnNames.size(); i++) {
84        if (columnNames.get(i).equalsIgnoreCase(columnName)) {
85          return jdbcTypes.get(i);
86        }
87      }
88      return null;
89    }
90  
91    /**
92     * Gets the type handler to use when reading the result set.
93     * Tries to get from the TypeHandlerRegistry by searching for the property type.
94     * If not found it gets the column JDBC type and tries to get a handler for it.
95     *
96     * @param propertyType
97     *          the property type
98     * @param columnName
99     *          the column name
100    * @return the type handler
101    */
102   public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
103     TypeHandler<?> handler = null;
104     Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
105     if (columnHandlers == null) {
106       columnHandlers = new HashMap<>();
107       typeHandlerMap.put(columnName, columnHandlers);
108     } else {
109       handler = columnHandlers.get(propertyType);
110     }
111     if (handler == null) {
112       JdbcType jdbcType = getJdbcType(columnName);
113       handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
114       // Replicate logic of UnknownTypeHandler#resolveTypeHandler
115       // See issue #59 comment 10
116       if (handler == null || handler instanceof UnknownTypeHandler) {
117         final int index = columnNames.indexOf(columnName);
118         final Class<?> javaType = resolveClass(classNames.get(index));
119         if (javaType != null && jdbcType != null) {
120           handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
121         } else if (javaType != null) {
122           handler = typeHandlerRegistry.getTypeHandler(javaType);
123         } else if (jdbcType != null) {
124           handler = typeHandlerRegistry.getTypeHandler(jdbcType);
125         }
126       }
127       if (handler == null || handler instanceof UnknownTypeHandler) {
128         handler = new ObjectTypeHandler();
129       }
130       columnHandlers.put(propertyType, handler);
131     }
132     return handler;
133   }
134 
135   private Class<?> resolveClass(String className) {
136     try {
137       // #699 className could be null
138       if (className != null) {
139         return Resources.classForName(className);
140       }
141     } catch (ClassNotFoundException e) {
142       // ignore
143     }
144     return null;
145   }
146 
147   private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
148     List<String> mappedColumnNames = new ArrayList<>();
149     List<String> unmappedColumnNames = new ArrayList<>();
150     final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
151     final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
152     for (String columnName : columnNames) {
153       final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
154       if (mappedColumns.contains(upperColumnName)) {
155         mappedColumnNames.add(upperColumnName);
156       } else {
157         unmappedColumnNames.add(columnName);
158       }
159     }
160     mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
161     unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
162   }
163 
164   public List<String> getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
165     List<String> mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
166     if (mappedColumnNames == null) {
167       loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
168       mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
169     }
170     return mappedColumnNames;
171   }
172 
173   public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
174     List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
175     if (unMappedColumnNames == null) {
176       loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
177       unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
178     }
179     return unMappedColumnNames;
180   }
181 
182   private String getMapKey(ResultMap resultMap, String columnPrefix) {
183     return resultMap.getId() + ":" + columnPrefix;
184   }
185 
186   private Set<String> prependPrefixes(Set<String> columnNames, String prefix) {
187     if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) {
188       return columnNames;
189     }
190     final Set<String> prefixed = new HashSet<>();
191     for (String columnName : columnNames) {
192       prefixed.add(prefix + columnName);
193     }
194     return prefixed;
195   }
196 
197 }