在上一篇文章当中,SqlSourceBuilder 当中有一步骤是通过 ParameterMappingTokenHandler 和 GenericTokenParser 来替换动态传入的参数,首先来分析 GenericTokenParser 的内容,GenericTokenParser 在判断是否是动态 sql 的时候也已经使用过了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class GenericTokenParser {

private final String openToken;
private final String closeToken;
private final TokenHandler handler;

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}

public String parse(String text) {
StringBuilder builder = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// the variable is escaped. remove the backslash.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
int end = text.indexOf(closeToken, start);
if (end == -1) {
builder.append(src, offset, src.length - offset);
offset = src.length;
} else {
builder.append(src, offset, start - offset);
offset = start + openToken.length();
String content = new String(src, offset, end - offset);
builder.append(handler.handleToken(content));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}

}

有三个参数,开始 token,结束 token,以及一个 TokenHandler,主要 TokenHandler 是 handleToken 来替换匹配的字符串,具体的 parse 方法不详细分析。

一般使用 GenericTokenParser 的方式如下:

1
2
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);

TokenHandler

看下 TokenHandler 接口的声明:

1
2
3
public interface TokenHandler {
String handleToken(String content);
}

再来看看 ParameterMappingTokenHandler 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
private Class<?> parameterType;
private MetaObject metaParameters;

public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}

@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

private ParameterMapping buildParameterMapping(String content) {
//省略代码
}

private Map<String, String> parseParameterMapping(String content) {
//省略代码
}
}

其实在 ParameterMappingTokenHandler 的 handleToken 方法当中,我们只会返回 ?来代替那些参数。虽然返回 ? 字符串很简单,但是我们需要真正传递参数的时候,这个参数的 name 以及 mybatis 的一些其他属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
//最基本的 #{id}
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>

#{property,javaType=int,jdbcType=NUMERIC}

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

因为替换了这些内容,并且在真正执行的时候我们还需要知道,所以通过 ParameterMapping 对象来表示里面的属性值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ParameterMapping {

private Configuration configuration;

private String property;//传入进来的参数 name
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;

//省略代码
}

然而我们在 ParameterMappingTokenHandler 当中可以看到有属性定义如下:

1
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

再来看 ParameterMappingTokenHandler.handleToken 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
//省略部分代码
return builder.build();
}

private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}

首先我们来分析如何把:#{property,javaType=int,jdbcType=NUMERIC} 这样的内容转换成一个 map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ParameterExpression extends HashMap<String, String> {

private static final long serialVersionUID = -2417552199605158680L;

public ParameterExpression(String expression) {
parse(expression);
}

private void parse(String expression) {
int p = skipWS(expression, 0);
if (expression.charAt(p) == '(') {
expression(expression, p + 1);
} else {
property(expression, p);
}
}

private int skipWS(String expression, int p) {
for (int i = p; i < expression.length(); i++) {
if (expression.charAt(i) > 0x20) {
return i;
}
}
return expression.length();
}

private void property(String expression, int left) {
if (left < expression.length()) {
int right = skipUntil(expression, left, ",:");
put("property", trimmedStr(expression, left, right));
jdbcTypeOpt(expression, right);
}
}

//省略了一些方法,逐步分析
}

首先是 mybatis 自定义了一个 ParameterExpression,并且继承于 HashMap。

在创建 ParameterExpression 实例的时候需要把 expression 传递进来,并且进行解析。

id,javaType=int,jdbcType=NUMERIC 传递进来的这个字符串做例子。

在 parse 方法当中第一步是跳过空白符, 执行 skipWS 方法。

然后 if 判断会执行到 property 方法。传递进来的 left 是字符串开始的 index,然后通过 skipUntil 来获取结束 index。

1
2
3
4
5
6
7
8
9
private int skipUntil(String expression, int p, final String endChars) {
for (int i = p; i < expression.length(); i++) {
char c = expression.charAt(i);
if (endChars.indexOf(c) > -1) {
return i;
}
}
return expression.length();
}

skipUntil 主要作用就是如果字符串 expression 从 p 索引开始,遇到了 endChars 当中的某个字符,就返回这个字符当前的索引。

然后在 property 方法当中继续执行:

1
put("property", trimmedStr(expression, left, right));

trimmedStr 就是去掉两边的空字符。所以根据上面传递进来的参数,在 map 当中有 key 是 property,value 是 id,这么一对键值。

然后继续执行 property 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
      jdbcTypeOpt(expression, right);

private void jdbcTypeOpt(String expression, int p) {
p = skipWS(expression, p);
if (p < expression.length()) {
if (expression.charAt(p) == ':') {
jdbcType(expression, p + 1);
} else if (expression.charAt(p) == ',') {
option(expression, p + 1);
} else {
throw new BuilderException("Parsing error in {" + new String(expression) + "} in position " + p);
}
}
}

private void option(String expression, int p) {
int left = skipWS(expression, p);
if (left < expression.length()) {
int right = skipUntil(expression, left, "=");
String name = trimmedStr(expression, left, right);
left = right + 1;
right = skipUntil(expression, left, ",");
String value = trimmedStr(expression, left, right);
put(name, value);
option(expression, right + 1);
}
}

按照上面代码执行,到 option 方法当中,会递归调用 option 把 A=B 这样的形式以 A 为 key,B 为 value 存入到 map 当中。

继续分析 ParameterMappingTokenHandler.buildParameterMapping 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);//上面已经分析
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property != null) {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
} else {
propertyType = Object.class;
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}

上面代码可以分为2个部分。

第一部分是获取 propertyType,为了创建 ParameterMapping.Builder 来构建 ParameterMapping 是实例。

第二部分是遍历刚才获取的 map,为 ParameterMapping 设置参数。

第一部分 MetaObject 相关的内容,后续分析。

—EOF—