Skip to content

Commit 8a79493

Browse files
committed
Merge branch 'master' into type-based-handler-resolution
# Conflicts: # src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java # src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java # src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java # src/main/java/org/apache/ibatis/scripting/defaults/RawSqlSource.java # src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
2 parents c7805b4 + e202727 commit 8a79493

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1615
-1343
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright 2009-2025 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.builder;
17+
18+
import java.lang.reflect.Type;
19+
import java.sql.ResultSet;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Map.Entry;
23+
24+
import org.apache.ibatis.binding.MapperMethod.ParamMap;
25+
import org.apache.ibatis.mapping.ParameterMapping;
26+
import org.apache.ibatis.mapping.ParameterMode;
27+
import org.apache.ibatis.parsing.TokenHandler;
28+
import org.apache.ibatis.reflection.MetaClass;
29+
import org.apache.ibatis.reflection.MetaObject;
30+
import org.apache.ibatis.reflection.ParamNameResolver;
31+
import org.apache.ibatis.reflection.property.PropertyTokenizer;
32+
import org.apache.ibatis.session.Configuration;
33+
import org.apache.ibatis.type.JdbcType;
34+
import org.apache.ibatis.type.TypeHandler;
35+
36+
public class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
37+
38+
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
39+
private final List<ParameterMapping> parameterMappings;
40+
private final Class<?> parameterType;
41+
private final MetaObject metaParameters;
42+
private final Object parameterObject;
43+
private final boolean paramExists;
44+
private final ParamNameResolver paramNameResolver;
45+
46+
private Type genericType = null;
47+
private TypeHandler<?> typeHandler = null;
48+
49+
public ParameterMappingTokenHandler(List<ParameterMapping> parameterMappings, Configuration configuration,
50+
Object parameterObject, Class<?> parameterType, Map<String, Object> additionalParameters,
51+
ParamNameResolver paramNameResolver, boolean paramExists) {
52+
super(configuration);
53+
this.parameterType = parameterObject == null ? (parameterType == null ? Object.class : parameterType)
54+
: parameterObject.getClass();
55+
this.metaParameters = configuration.newMetaObject(additionalParameters);
56+
this.parameterObject = parameterObject;
57+
this.paramExists = paramExists;
58+
this.parameterMappings = parameterMappings;
59+
this.paramNameResolver = paramNameResolver;
60+
}
61+
62+
public ParameterMappingTokenHandler(List<ParameterMapping> parameterMappings, Configuration configuration,
63+
Class<?> parameterType, Map<String, Object> additionalParameters, ParamNameResolver paramNameResolver) {
64+
super(configuration);
65+
this.parameterType = parameterType;
66+
this.metaParameters = configuration.newMetaObject(additionalParameters);
67+
this.parameterObject = null;
68+
this.paramExists = false;
69+
this.parameterMappings = parameterMappings;
70+
this.paramNameResolver = paramNameResolver;
71+
}
72+
73+
public List<ParameterMapping> getParameterMappings() {
74+
return parameterMappings;
75+
}
76+
77+
@Override
78+
public String handleToken(String content) {
79+
parameterMappings.add(buildParameterMapping(content));
80+
return "?";
81+
}
82+
83+
private ParameterMapping buildParameterMapping(String content) {
84+
Map<String, String> propertiesMap = parseParameterMapping(content);
85+
86+
final String property = propertiesMap.remove("property");
87+
final JdbcType jdbcType = resolveJdbcType(propertiesMap.remove("jdbcType"));
88+
final String typeHandlerAlias = propertiesMap.remove("typeHandler");
89+
90+
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, (Class<?>) null);
91+
PropertyTokenizer propertyTokenizer = new PropertyTokenizer(property);
92+
builder.jdbcType(jdbcType);
93+
final Class<?> javaType = figureOutJavaType(propertiesMap, property, propertyTokenizer, jdbcType);
94+
builder.javaType(javaType);
95+
if (genericType == null) {
96+
genericType = javaType;
97+
}
98+
if (typeHandler == null || typeHandlerAlias != null) {
99+
typeHandler = resolveTypeHandler(parameterType, property, genericType, jdbcType, typeHandlerAlias);
100+
}
101+
builder.typeHandler(typeHandler);
102+
103+
ParameterMode mode = null;
104+
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
105+
String name = entry.getKey();
106+
String value = entry.getValue();
107+
if ("mode".equals(name)) {
108+
mode = resolveParameterMode(value);
109+
builder.mode(mode);
110+
} else if ("numericScale".equals(name)) {
111+
builder.numericScale(Integer.valueOf(value));
112+
} else if ("resultMap".equals(name)) {
113+
builder.resultMapId(value);
114+
} else if ("jdbcTypeName".equals(name)) {
115+
builder.jdbcTypeName(value);
116+
} else if ("expression".equals(name)) {
117+
throw new BuilderException("Expression based parameters are not supported yet");
118+
} else {
119+
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
120+
+ "}. Valid properties are " + PARAMETER_PROPERTIES);
121+
}
122+
}
123+
if (!ParameterMode.OUT.equals(mode) && paramExists) {
124+
if (metaParameters.hasGetter(propertyTokenizer.getName())) {
125+
builder.value(metaParameters.getValue(property));
126+
} else if (parameterObject == null) {
127+
builder.value(null);
128+
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
129+
builder.value(parameterObject);
130+
} else {
131+
MetaObject metaObject = configuration.newMetaObject(parameterObject);
132+
builder.value(metaObject.getValue(property));
133+
}
134+
}
135+
return builder.build();
136+
}
137+
138+
private Class<?> figureOutJavaType(Map<String, String> propertiesMap, String property,
139+
PropertyTokenizer propertyTokenizer, JdbcType jdbcType) {
140+
Class<?> javaType = resolveClass(propertiesMap.remove("javaType"));
141+
if (javaType != null) {
142+
return javaType;
143+
}
144+
if (metaParameters.hasGetter(propertyTokenizer.getName())) { // issue #448 get type from additional params
145+
return metaParameters.getGetterType(property);
146+
}
147+
typeHandler = resolveTypeHandler(parameterType, null, null, jdbcType, (Class<? extends TypeHandler<?>>) null);
148+
if (typeHandler != null) {
149+
return parameterType;
150+
}
151+
if (JdbcType.CURSOR.equals(jdbcType)) {
152+
return ResultSet.class;
153+
}
154+
if (paramNameResolver != null && ParamMap.class.equals(parameterType)) {
155+
Type actualParamType = paramNameResolver.getType(property);
156+
if (actualParamType instanceof Type) {
157+
MetaClass metaClass = MetaClass.forClass(actualParamType, configuration.getReflectorFactory());
158+
String multiParamsPropertyName;
159+
if (propertyTokenizer.hasNext()) {
160+
multiParamsPropertyName = propertyTokenizer.getChildren();
161+
if (metaClass.hasGetter(multiParamsPropertyName)) {
162+
Entry<Type, Class<?>> getterType = metaClass.getGenericGetterType(multiParamsPropertyName);
163+
genericType = getterType.getKey();
164+
return getterType.getValue();
165+
}
166+
} else {
167+
genericType = actualParamType;
168+
}
169+
}
170+
return Object.class;
171+
}
172+
if (Map.class.isAssignableFrom(parameterType)) {
173+
return Object.class;
174+
}
175+
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
176+
if (metaClass.hasGetter(property)) {
177+
Entry<Type, Class<?>> getterType = metaClass.getGenericGetterType(property);
178+
genericType = getterType.getKey();
179+
return getterType.getValue();
180+
}
181+
return Object.class;
182+
}
183+
184+
private Map<String, String> parseParameterMapping(String content) {
185+
try {
186+
return new ParameterExpression(content);
187+
} catch (BuilderException ex) {
188+
throw ex;
189+
} catch (Exception ex) {
190+
throw new BuilderException("Parsing error was found in mapping #{" + content
191+
+ "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
192+
}
193+
}
194+
}

src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java

Lines changed: 8 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -15,54 +15,27 @@
1515
*/
1616
package org.apache.ibatis.builder;
1717

18-
import java.lang.reflect.Type;
19-
import java.sql.ResultSet;
20-
import java.util.ArrayList;
2118
import java.util.List;
22-
import java.util.Map;
23-
import java.util.Map.Entry;
2419
import java.util.StringTokenizer;
2520

26-
import org.apache.ibatis.binding.MapperMethod.ParamMap;
2721
import org.apache.ibatis.mapping.ParameterMapping;
2822
import org.apache.ibatis.mapping.SqlSource;
29-
import org.apache.ibatis.parsing.GenericTokenParser;
30-
import org.apache.ibatis.parsing.TokenHandler;
31-
import org.apache.ibatis.reflection.MetaClass;
32-
import org.apache.ibatis.reflection.MetaObject;
33-
import org.apache.ibatis.reflection.ParamNameResolver;
34-
import org.apache.ibatis.reflection.property.PropertyTokenizer;
3523
import org.apache.ibatis.session.Configuration;
36-
import org.apache.ibatis.type.JdbcType;
37-
import org.apache.ibatis.type.TypeHandler;
3824

3925
/**
4026
* @author Clinton Begin
4127
*/
42-
public class SqlSourceBuilder extends BaseBuilder {
28+
public class SqlSourceBuilder {
4329

44-
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
45-
46-
public SqlSourceBuilder(Configuration configuration) {
47-
super(configuration);
48-
}
49-
50-
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
51-
return parse(originalSql, parameterType, additionalParameters, null);
30+
private SqlSourceBuilder() {
31+
super();
5232
}
5333

54-
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters,
55-
ParamNameResolver paramNameResolver) {
56-
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
57-
additionalParameters, paramNameResolver);
58-
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
59-
String sql;
60-
if (configuration.isShrinkWhitespacesInSql()) {
61-
sql = parser.parse(removeExtraWhitespaces(originalSql));
62-
} else {
63-
sql = parser.parse(originalSql);
64-
}
65-
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
34+
public static SqlSource buildSqlSource(Configuration configuration, String sql,
35+
List<ParameterMapping> parameterMappings) {
36+
return new StaticSqlSource(configuration,
37+
configuration.isShrinkWhitespacesInSql() ? SqlSourceBuilder.removeExtraWhitespaces(sql) : sql,
38+
parameterMappings);
6639
}
6740

6841
public static String removeExtraWhitespaces(String original) {
@@ -79,128 +52,4 @@ public static String removeExtraWhitespaces(String original) {
7952
return builder.toString();
8053
}
8154

82-
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
83-
84-
private final List<ParameterMapping> parameterMappings = new ArrayList<>();
85-
private final Class<?> parameterType;
86-
private final MetaObject metaParameters;
87-
private final ParamNameResolver paramNameResolver;
88-
89-
private Type genericType = null;
90-
private TypeHandler<?> typeHandler = null;
91-
92-
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,
93-
Map<String, Object> additionalParameters, ParamNameResolver paramNameResolver) {
94-
super(configuration);
95-
this.parameterType = parameterType;
96-
this.metaParameters = configuration.newMetaObject(additionalParameters);
97-
this.paramNameResolver = paramNameResolver;
98-
}
99-
100-
public List<ParameterMapping> getParameterMappings() {
101-
return parameterMappings;
102-
}
103-
104-
@Override
105-
public String handleToken(String content) {
106-
parameterMappings.add(buildParameterMapping(content));
107-
return "?";
108-
}
109-
110-
private ParameterMapping buildParameterMapping(String content) {
111-
Map<String, String> propertiesMap = parseParameterMapping(content);
112-
final String property = propertiesMap.remove("property");
113-
final JdbcType jdbcType = resolveJdbcType(propertiesMap.remove("jdbcType"));
114-
final String typeHandlerAlias = propertiesMap.remove("typeHandler");
115-
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, (Class<?>) null);
116-
builder.jdbcType(jdbcType);
117-
final Class<?> javaType = figureOutJavaType(propertiesMap, property, jdbcType);
118-
builder.javaType(javaType);
119-
if (genericType == null) {
120-
genericType = javaType;
121-
}
122-
if (typeHandler == null || typeHandlerAlias != null) {
123-
typeHandler = resolveTypeHandler(parameterType, property, genericType, jdbcType, typeHandlerAlias);
124-
}
125-
builder.typeHandler(typeHandler);
126-
127-
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
128-
String name = entry.getKey();
129-
String value = entry.getValue();
130-
if ("mode".equals(name)) {
131-
builder.mode(resolveParameterMode(value));
132-
} else if ("numericScale".equals(name)) {
133-
builder.numericScale(Integer.valueOf(value));
134-
} else if ("resultMap".equals(name)) {
135-
builder.resultMapId(value);
136-
} else if ("jdbcTypeName".equals(name)) {
137-
builder.jdbcTypeName(value);
138-
} else if ("expression".equals(name)) {
139-
throw new BuilderException("Expression based parameters are not supported yet");
140-
} else {
141-
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
142-
+ "}. Valid properties are " + PARAMETER_PROPERTIES);
143-
}
144-
}
145-
return builder.build();
146-
}
147-
148-
private Class<?> figureOutJavaType(Map<String, String> propertiesMap, String property, JdbcType jdbcType) {
149-
Class<?> javaType = resolveClass(propertiesMap.remove("javaType"));
150-
if (javaType != null) {
151-
return javaType;
152-
}
153-
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
154-
return metaParameters.getGetterType(property);
155-
}
156-
typeHandler = resolveTypeHandler(parameterType, null, null, jdbcType, (Class<? extends TypeHandler<?>>) null);
157-
if (typeHandler != null) {
158-
return parameterType;
159-
}
160-
if (JdbcType.CURSOR.equals(jdbcType)) {
161-
return ResultSet.class;
162-
}
163-
if (paramNameResolver != null && ParamMap.class.equals(parameterType)) {
164-
Type actualParamType = paramNameResolver.getType(property);
165-
if (actualParamType instanceof Type) {
166-
MetaClass metaClass = MetaClass.forClass(actualParamType, configuration.getReflectorFactory());
167-
PropertyTokenizer propertyTokenizer = new PropertyTokenizer(property);
168-
String multiParamsPropertyName;
169-
if (propertyTokenizer.hasNext()) {
170-
multiParamsPropertyName = propertyTokenizer.getChildren();
171-
if (metaClass.hasGetter(multiParamsPropertyName)) {
172-
Entry<Type, Class<?>> getterType = metaClass.getGenericGetterType(multiParamsPropertyName);
173-
genericType = getterType.getKey();
174-
return getterType.getValue();
175-
}
176-
} else {
177-
genericType = actualParamType;
178-
}
179-
}
180-
return Object.class;
181-
}
182-
if (Map.class.isAssignableFrom(parameterType)) {
183-
return Object.class;
184-
}
185-
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
186-
if (metaClass.hasGetter(property)) {
187-
Entry<Type, Class<?>> getterType = metaClass.getGenericGetterType(property);
188-
genericType = getterType.getKey();
189-
return getterType.getValue();
190-
}
191-
return Object.class;
192-
}
193-
194-
private Map<String, String> parseParameterMapping(String content) {
195-
try {
196-
return new ParameterExpression(content);
197-
} catch (BuilderException ex) {
198-
throw ex;
199-
} catch (Exception ex) {
200-
throw new BuilderException("Parsing error was found in mapping #{" + content
201-
+ "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
202-
}
203-
}
204-
}
205-
20655
}

0 commit comments

Comments
 (0)