View Javadoc
1   /*
2    *    Copyright 2009-2023 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    *       https://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.mapping;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.Set;
22  
23  import org.apache.ibatis.session.Configuration;
24  import org.apache.ibatis.type.JdbcType;
25  import org.apache.ibatis.type.TypeHandler;
26  import org.apache.ibatis.type.TypeHandlerRegistry;
27  
28  /**
29   * @author Clinton Begin
30   */
31  public class ResultMapping {
32  
33    private Configuration configuration;
34    private String property;
35    private String column;
36    private Class<?> javaType;
37    private JdbcType jdbcType;
38    private TypeHandler<?> typeHandler;
39    private String nestedResultMapId;
40    private String nestedQueryId;
41    private Set<String> notNullColumns;
42    private String columnPrefix;
43    private List<ResultFlag> flags;
44    private List<ResultMapping> composites;
45    private String resultSet;
46    private String foreignColumn;
47    private boolean lazy;
48  
49    ResultMapping() {
50    }
51  
52    public static class Builder {
53      private final ResultMapping resultMapping = new ResultMapping();
54  
55      public Builder(Configuration configuration, String property, String column, TypeHandler<?> typeHandler) {
56        this(configuration, property);
57        resultMapping.column = column;
58        resultMapping.typeHandler = typeHandler;
59      }
60  
61      public Builder(Configuration configuration, String property, String column, Class<?> javaType) {
62        this(configuration, property);
63        resultMapping.column = column;
64        resultMapping.javaType = javaType;
65      }
66  
67      public Builder(Configuration configuration, String property) {
68        resultMapping.configuration = configuration;
69        resultMapping.property = property;
70        resultMapping.flags = new ArrayList<>();
71        resultMapping.composites = new ArrayList<>();
72        resultMapping.lazy = configuration.isLazyLoadingEnabled();
73      }
74  
75      public Builder javaType(Class<?> javaType) {
76        resultMapping.javaType = javaType;
77        return this;
78      }
79  
80      public Builder jdbcType(JdbcType jdbcType) {
81        resultMapping.jdbcType = jdbcType;
82        return this;
83      }
84  
85      public Builder nestedResultMapId(String nestedResultMapId) {
86        resultMapping.nestedResultMapId = nestedResultMapId;
87        return this;
88      }
89  
90      public Builder nestedQueryId(String nestedQueryId) {
91        resultMapping.nestedQueryId = nestedQueryId;
92        return this;
93      }
94  
95      public Builder resultSet(String resultSet) {
96        resultMapping.resultSet = resultSet;
97        return this;
98      }
99  
100     public Builder foreignColumn(String foreignColumn) {
101       resultMapping.foreignColumn = foreignColumn;
102       return this;
103     }
104 
105     public Builder notNullColumns(Set<String> notNullColumns) {
106       resultMapping.notNullColumns = notNullColumns;
107       return this;
108     }
109 
110     public Builder columnPrefix(String columnPrefix) {
111       resultMapping.columnPrefix = columnPrefix;
112       return this;
113     }
114 
115     public Builder flags(List<ResultFlag> flags) {
116       resultMapping.flags = flags;
117       return this;
118     }
119 
120     public Builder typeHandler(TypeHandler<?> typeHandler) {
121       resultMapping.typeHandler = typeHandler;
122       return this;
123     }
124 
125     public Builder composites(List<ResultMapping> composites) {
126       resultMapping.composites = composites;
127       return this;
128     }
129 
130     public Builder lazy(boolean lazy) {
131       resultMapping.lazy = lazy;
132       return this;
133     }
134 
135     public ResultMapping build() {
136       // lock down collections
137       resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
138       resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
139       resolveTypeHandler();
140       validate();
141       return resultMapping;
142     }
143 
144     private void validate() {
145       // Issue #697: cannot define both nestedQueryId and nestedResultMapId
146       if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) {
147         throw new IllegalStateException(
148             "Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property);
149       }
150       // Issue #5: there should be no mappings without typehandler
151       if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null
152           && resultMapping.typeHandler == null) {
153         throw new IllegalStateException("No typehandler found for property " + resultMapping.property);
154       }
155       // Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
156       if (resultMapping.nestedResultMapId == null && resultMapping.column == null
157           && resultMapping.composites.isEmpty()) {
158         throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
159       }
160       if (resultMapping.getResultSet() != null) {
161         int numColumns = 0;
162         if (resultMapping.column != null) {
163           numColumns = resultMapping.column.split(",").length;
164         }
165         int numForeignColumns = 0;
166         if (resultMapping.foreignColumn != null) {
167           numForeignColumns = resultMapping.foreignColumn.split(",").length;
168         }
169         if (numColumns != numForeignColumns) {
170           throw new IllegalStateException(
171               "There should be the same number of columns and foreignColumns in property " + resultMapping.property);
172         }
173       }
174     }
175 
176     private void resolveTypeHandler() {
177       if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
178         Configuration configuration = resultMapping.configuration;
179         TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
180         resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
181       }
182     }
183 
184     public Builder column(String column) {
185       resultMapping.column = column;
186       return this;
187     }
188   }
189 
190   public String getProperty() {
191     return property;
192   }
193 
194   public String getColumn() {
195     return column;
196   }
197 
198   public Class<?> getJavaType() {
199     return javaType;
200   }
201 
202   public JdbcType getJdbcType() {
203     return jdbcType;
204   }
205 
206   public TypeHandler<?> getTypeHandler() {
207     return typeHandler;
208   }
209 
210   public String getNestedResultMapId() {
211     return nestedResultMapId;
212   }
213 
214   public String getNestedQueryId() {
215     return nestedQueryId;
216   }
217 
218   public Set<String> getNotNullColumns() {
219     return notNullColumns;
220   }
221 
222   public String getColumnPrefix() {
223     return columnPrefix;
224   }
225 
226   public List<ResultFlag> getFlags() {
227     return flags;
228   }
229 
230   public List<ResultMapping> getComposites() {
231     return composites;
232   }
233 
234   public boolean isCompositeResult() {
235     return this.composites != null && !this.composites.isEmpty();
236   }
237 
238   public String getResultSet() {
239     return this.resultSet;
240   }
241 
242   public String getForeignColumn() {
243     return foreignColumn;
244   }
245 
246   public void setForeignColumn(String foreignColumn) {
247     this.foreignColumn = foreignColumn;
248   }
249 
250   public boolean isLazy() {
251     return lazy;
252   }
253 
254   public void setLazy(boolean lazy) {
255     this.lazy = lazy;
256   }
257 
258   public boolean isSimple() {
259     return this.nestedResultMapId == null && this.nestedQueryId == null && this.resultSet == null;
260   }
261 
262   @Override
263   public boolean equals(Object o) {
264     if (this == o) {
265       return true;
266     }
267     if (o == null || getClass() != o.getClass()) {
268       return false;
269     }
270 
271     ResultMapping that = (ResultMapping) o;
272 
273     return property != null && property.equals(that.property);
274   }
275 
276   @Override
277   public int hashCode() {
278     if (property != null) {
279       return property.hashCode();
280     }
281     if (column != null) {
282       return column.hashCode();
283     } else {
284       return 0;
285     }
286   }
287 
288   @Override
289   public String toString() {
290     final StringBuilder sb = new StringBuilder("ResultMapping{");
291     // sb.append("configuration=").append(configuration); // configuration doesn't have a useful .toString()
292     sb.append("property='").append(property).append('\'');
293     sb.append(", column='").append(column).append('\'');
294     sb.append(", javaType=").append(javaType);
295     sb.append(", jdbcType=").append(jdbcType);
296     // sb.append(", typeHandler=").append(typeHandler); // typeHandler also doesn't have a useful .toString()
297     sb.append(", nestedResultMapId='").append(nestedResultMapId).append('\'');
298     sb.append(", nestedQueryId='").append(nestedQueryId).append('\'');
299     sb.append(", notNullColumns=").append(notNullColumns);
300     sb.append(", columnPrefix='").append(columnPrefix).append('\'');
301     sb.append(", flags=").append(flags);
302     sb.append(", composites=").append(composites);
303     sb.append(", resultSet='").append(resultSet).append('\'');
304     sb.append(", foreignColumn='").append(foreignColumn).append('\'');
305     sb.append(", lazy=").append(lazy);
306     sb.append('}');
307     return sb.toString();
308   }
309 
310 }