一、xml中的select查询节点
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.submitted.maptypehandler.Mapper">
<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
select * from users
<where>
id = #{id} and name = #{name}
</where>
</select>
</mapper>
首先我们需要了解关于解析的一些类及一些基本内容,我们看下这个[select]节点,可以看到其包含属性attribute(例如id、resultType)、还可以包含子节点[where]、以及文本节点等。
我们现在来了解这个[select]节点是怎样被Mybatis处理封装的。
二、BaseBuilder类
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
......
protected Boolean booleanValueOf(String value, Boolean defaultValue) {
return value == null ? defaultValue : Boolean.valueOf(value);
}
protected Integer integerValueOf(String value, Integer defaultValue) {
return value == null ? defaultValue : Integer.valueOf(value);
}
......
protected JdbcType resolveJdbcType(String alias) {
if (alias == null) {
return null;
}
try {
return JdbcType.valueOf(alias);
} catch (IllegalArgumentException e) {
throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
}
}
......
protected Object createInstance(String alias) {
Class<?> clazz = resolveClass(alias);
if (clazz == null) {
return null;
}
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new BuilderException("Error creating instance. Cause: " + e, e);
}
}
......
protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
if (typeHandlerType == null) {
return null;
}
// javaType ignored for injected handlers see issue #746 for full detail
TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
if (handler == null) {
// not in registry, create a new one
handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
}
return handler;
}
protected <T> Class<? extends T> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
}
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
......
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
.....
registerAlias("_integer", int.class);
......
registerAlias("_long[]", long[].class);
......
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
......
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
......
}
public enum JdbcType {
......
FLOAT(Types.FLOAT),
......
CHAR(Types.CHAR),
VARCHAR(Types.VARCHAR),
LONGVARCHAR(Types.LONGVARCHAR),
......
CLOB(Types.CLOB),
BOOLEAN(Types.BOOLEAN),
......
public final int TYPE_CODE;
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
......
通过这些方法我们可以看到,这个类是提供了一些公共方法:
1、例如简单的booleanValueOf - 将string类型的布尔值转化为其原本的包装类型。
2、createInstance方法根据java类型的别名创建对应的对象,用的TypeAliasRegistry,其初始化的时候是会默认注入对应关系,例如[result column=“name” property=“name” javaType=“string”],这个映射,这里的javaType=“string"就可以通过别名string获取到String.class。这里我们可以注意到的一点是对于基本类型,例如float,如果我们写的是"float"其映射的基本类型的包装类Float.class,如果是”_float"则映射的是原生类型float.clas。
3、resolveJdbcType这个是根据jdbcType类型(字符类型的别名)获取对应的JdbcType,例如[result column=“name” property=“name” jdbcType=“INTEGER”]。
4、resolveTypeHandler通过typeHandlerType来从typeHandlerRegistry中获取对应类型的类型处理器对象。
三、XMLScriptBuilder(BaseBuilder的子类)
1、XMLScriptBuilder类结构
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
......
我们可以看到这个类是继承的BaseBuilder,这个类主要是来引导处理xml中的标签内容的(也可以处理注解中写的[scripte]这种),例如:
@Select({
"<script>",
" select count(*) from users u where u.id in",
" <foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>",
" #{item}",
" </foreach>",
"</script>"
})
Long getUserCountUsingList(List<Integer> ids);
2、其的成员变量:
1)、isDynamic
这个是相对于sql来说的,对于mybatis来说是存在两种替换赋值的即"$()"、"#{}"这两种。
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
int end = text.indexOf(closeToken, offset);
......
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
其是用这个GenericTokenParser去处理发现找文本中的内容是哪种填充方法(本文下面梳理对应调用的时候在具体分析)。
2)、parameterType
即对应的初始类型。
3)、nodeHandlerMap
这个我们看XMLScriptBuilder的构造函数通过initNodeHandlerMap方法对其的初始化,就明白其是构建对应子节点的节点处理器与节点名称的映射关系的,例如[foreach]就是用的ForEachHandler类处理的。
3、NodeHandler及SqlNode的遍历
这个就是用来处理对应标签的,首先我们来看下xml的sql语句的节点
<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
select * from users
<where>
id = #{id} and name = #{name}
</where>
</select>
我们来看下在这个[select]之间中有直接的文本节点内容,以及[where]节点(例如还看有[if]、[choose]这些),而这些在mybatis中都有一个接口用来描叙它
public interface SqlNode {
boolean apply(DynamicContext context);
}
可以看到对应内容有对应节点不同的SqlNode实现,在这些SqlNode实现中我们需要关注其中一个MixedSqlNode
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
可以看到该实现中主要是一个成员变量contents,这个其实就是用来放其他的SqlNode的,因为我们知道在一个例如[select]中可能会用到多种SqlNode,还有一个就是StaticTextSqlNode这个就是用来放静态的文本内容的.
private class WhereHandler implements NodeHandler {
......
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
private class SetHandler implements NodeHandler {
......
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
}
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
可以看到这里会有一个for循环遍历解析的过程,通过
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
......
contents.add(textSqlNode);
......
return new MixedSqlNode(contents);
}
这个这种循环构建赋值,将其他SqlNode都会添加到对应MixedSqlNode组中。例如上面的[select id=“getUserXML”]节点对应的SqlNode集就是
四、DynamicContext
这个类就是用来存放各个SqlNode最终解析产生的sql文本参数内容
public class StaticTextSqlNode implements SqlNode {
private final String text;
......
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
public class DynamicContext {
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
......
private final ContextMap bindings;
private final StringJoiner sqlBuilder = new StringJoiner(" ");
private int uniqueNumber = 0;
......
public void appendSql(String sql) {
sqlBuilder.add(sql);
}
例如这里就拼接StaticTextSqlNode中的sql文本内容,然后还有传入的参数
@Test
void shouldGetAUserFromXML() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
Map<String, Object> params = new HashMap<>();
params.put("id", 1);
params.put("name", "User1");
Assertions.assertThrows(PersistenceException.class, () -> mapper.getUserXML(params));
}
}
我们看下一个WhereSqlNode(其继承TrimSqlNode)
这里又会再次出发MixedSqlNode的变量,例如这里又会将中的静态内容再拼接到sqlBuilder中。
最后就通过各个SqlNode的遍历处理拼接成了一条sql的文本内容。
五、LanguageDriver
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
这个接口就是用于sql节点[select]的整个解析过程的,其有两个子类:XMLLanguageDriver、RawLanguageDriver(继承XMLLanguageDriver)。
1、XMLLanguageDriver
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
可以看到这里与前面的XMLScriptBuilder联系起来了(new XMLScriptBuilder(configuration, script, parameterType)),同时这里有两个createSqlSource方法上面表示的是被xml解析[select]节点使用的,下面的是被注解解析的时候使用的。同时在下面的createSqlSource方法会判断(script.startsWith(“script”)),这个是用来判断注解的写法是不是用xml中的节点的写法,例如前面的getUserCountUsingList方法@Select(…)中写的那样,如果是这种直接调用第一个createSqlSource方法使用xml中的写法遍历,如果不是,则直接使用TextSqlNode这种文本节点的方式去解析。
这个script就是整个[select节点的内容([select id=“getUserXML”])表示了,然后这个parseScriptNode方法对parseDynamicTags方法调用,也承接了上面分析的parseDynamicTags对SqlNode的循环解析。
同时根据是否是动态的来对应创建DynamicSqlSource/RawSqlSource。例如:
<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
select * from users where id = ${id} and name = ${name}
</select>
<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
select * from users where id = #{id} and name = #{name}
</select>
2、RawLanguageDriver
public class RawLanguageDriver extends XMLLanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
SqlSource source = super.createSqlSource(configuration, script, parameterType);
checkIsNotDynamic(source);
return source;
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
SqlSource source = super.createSqlSource(configuration, script, parameterType);
checkIsNotDynamic(source);
return source;
}
private void checkIsNotDynamic(SqlSource source) {
if (!RawSqlSource.class.equals(source.getClass())) {
throw new BuilderException("Dynamic content is not allowed when using RAW language");
}
}
}
我们可以看到这个RawLanguageDriver其都是直接调用父类(XMLLanguageDriver)的方法,再判断产生的SqlSource是否是RawSqlSource类型(checkIsNotDynamic(…)),这个一般是处理使用"${}"的写法。
六、SqlSource
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
这个接口是用来初始BoundSql的,这个BoundSql是用存放待数据库引擎执行的一些参数。
1、BoundSql(以DynamicSqlSource的getBoundSql为例)
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;、
......
可以看到这个类的成员变量有直接的sql语句,以及用来填充参数。
2、SqlSource的子类及它们之间的关系
然后其有四个子类:StaticSqlSource、RawSqlSource、DynamicSqlSource、ProviderSqlSource。这四个子类通过前面我们知道RawSqlSource与DynamicSqlSource的区别,然后其实在getBoundSql方法调用的内部这两个都会使用到StaticSqlSource来创建BoundSql。ProviderSqlSource这个是用于注解的如SelectProvider、InsertProvider等,对应于直接的@Select、@Delete这些注解。
private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
Method method) {
if (annotation instanceof Select) {
return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Update) {
return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Insert) {
return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Delete) {
return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof SelectKey) {
return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
}
return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
}
这种Provider注解就是用来给你自己去产生Sql语句的内容的,其参数的内容对应的是我们前面讲过的XMLLanguageDriver createSqlSource方法的参数script的内容。
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType)
例如其的使用:
@SelectProvider(type = StatementProvider.class, method = "provideSelect")
List<S> selectList(S param);
class StatementProvider {
public String provideSelect(Object param) {
StringBuilder sql = new StringBuilder("select * from ");
if (param == null || param instanceof Person) {
sql.append(" person ");
if (param != null && ((Person) param).getId() != null) {
sql.append(" where id = #{id}");
}
} else if (param instanceof Country) {
sql.append(" country ");
if (((Country) param).getId() != null) {
sql.append(" where id = #{id}");
}
}
sql.append(" order by id");
return sql.toString();
}
所以其实这个ProviderSqlSource最后也是在使用RawSqlSource或DynamicSqlSource。现在我们来具体看下
3、StaticSqlSource
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
......
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
我们可以看到其的getBoundSql方法很简单,就是完成BoundSql参数的初始化。
4、DynamicSqlSource
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
......
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
这个入参就是用来传到sql中的内容的。然后这个context(DynamicContext)、rootSqlNode.apply()(MixedSqlNode)、我们在前面有分析就不再具体分析了。然后再构建了一个SqlSerceBuilderd对象,后面调用sqlSourceParser.parse就会产生一个SqlSource(StaticSqlSource)对象(parse方法的具体解析到下面的SqlSourceBuilder类分析),然后再通过StaticSqlSource初始化构建一个BoundSql对象返回。下面我们先来简单分析下SqlSourceBuilder。
5、SqlSourceBuilder
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
......
我们可以看到其是继承我们前面梳理的BaseBuilder类,然后PARAMETER_PROPERTIES就包含了我们可能写在节点中的一些属性的key。例如我们这样写一个sql(id = #{id,jdbcType=INTEGER,javaType=int}),带上其的jdbcType、javaType。然后我们来看SqlSourceBuilder中的方法。
<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
select * from users
<where>
<if test="id != null">
id = #{id,jdbcType=INTEGER,javaType=int}
</if>
and name = #{name}
</where>
</select>
1)、parse(SqlSourceBuilder)
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这个方法就是上面调用的sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()),其入参
然后这里创建GenericTokenParser的时候传入的处理器是ParameterMappingTokenHandler。然后这里先判断isShrinkWhitespacesInSql,是否去掉一些多余的空格等,其就会移除这些(" \t\n\r\f"),默认为false。然后调用parser.parse解析为一个sql语句,这样就完成了DynamicSqlSource的getBoundSql方法中对StaticSqlSource的创建,然后就通过调用。
2)、parse(GenericTokenParser)
这个就是用来解析sql文本中的参数的,并用?作为对应的占位符。这里关键会调用ParameterMappingTokenHandler的handleToken方法。
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
@Override
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();
}
这个buildParameterMapping方法主要可以分为3个部分。
第一部分:对parseParameterMapping方法的调用,这个方法主要就是对context中的内容进行提取,然后以key-value键值对的形式放到Map中。
第二部分:设置对应的propertyType(javaType)
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 || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
第三部分:构建ParameterMapping.Builder
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 ("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 " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
这里就是遍历propertiesMap,将对应内容设置到到Builder中,再返回为ParameterMapping。
3)、ParameterExpression(对content内容的解析)
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);
}
}
}
public class ParameterExpression extends HashMap<String, String> {
.......
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();
}
解析就是从这里开始,这个skipWS就是找到开始的位置,如果expression中有"("这种就是用表达式去解析,这里我们跳过,按一般使用进入property:
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);
}
}
这里就是提取对应内容实现是提取属性,例如以前面的content字符串为例,这里trimmedStr提取的就是"id",这里就会put一个property-id的键值对。这里之所以会以",:“形式,多了一个”:",是因为有一个用法能直接设置jdbcType的内容
@Test
void simplePropertyWithOldStyleJdbcTypeAndAttributes() {
Map<String, String> result = new ParameterExpression("id:VARCHAR, attr1=val1, attr2=val2");
Assertions.assertEquals(4, result.size());
Assertions.assertEquals("id", result.get("property"));
Assertions.assertEquals("VARCHAR", result.get("jdbcType"));
Assertions.assertEquals("val1", result.get("attr1"));
Assertions.assertEquals("val2", result.get("attr2"));
}
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 {" + expression + "} in position " + p);
}
}
}
private void jdbcType(String expression, int p) {
int left = skipWS(expression, p);
int right = skipUntil(expression, left, ",");
if (right > left) {
put("jdbcType", trimmedStr(expression, left, right));
} else {
throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
}
option(expression, right + 1);
}
例如这里首先看是不是’:’,是就调用jdbcType,可以看到其设置的key是"jdbcType",其他的就通过option方法去提取key-value
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);
}
}
这里是有一个自调用一直解析到最后。
6、RawSqlSource
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
我们看到RawSqlSource其与前面的DynaticSqlSource大体差别不大,但在通过SqlSourceBuilder构建StaticSqlSource的时候是在其的初始化方法的时候。
7、ProviderSqlSource
public class ProviderSqlSource implements SqlSource {
......
@Override
public BoundSql getBoundSql(Object parameterObject) {
SqlSource sqlSource = createSqlSource(parameterObject);
return sqlSource.getBoundSql(parameterObject);
}
private SqlSource createSqlSource(Object parameterObject) {
try {
String sql;
......
sql = invokeProviderMethod(parameterObject);
......
} else {
......
}
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
return languageDriver.createSqlSource(configuration, sql, parameterType);
} catch (BuilderException e) {
......
}
}
......
private String invokeProviderMethod(Object... args) throws Exception {
Object targetObject = null;
if (!Modifier.isStatic(providerMethod.getModifiers())) {
targetObject = providerType.getDeclaredConstructor().newInstance();
}
CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
return sql != null ? sql.toString() : null;
}
........
}
这个类其实比较复杂,例如由于其面向注解,所以涉及从注解中获取执行类及方法并进行对象创建,但我们都省略这些。可以看到其主要逻辑是通过执行对应方法来获取sql,再调用languageDriver.createSqlSource(configuration, sql, parameterType),来产生StaticSqlSource,这里就加入到本文最开始的梳理的XMLLanguageDriver的createSqlSource方法了。
七、TypeHandlerRegistry
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
......
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
......
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalTime.class, new LocalTimeTypeHandler());
......
}
......
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
......
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
}
map.put(jdbcType, handler);
typeHandlerMap.put(javaType, map);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
这个类是用来处理类型的,例如我们再Mapper的xml文件中可以设置列字段的java类型,数据库类型。例如这里的jdbcTypeHandlerMap:key就是数据库类型,然后TypeHandler就是用来处理对应类型的。
这里有一个unknownTypeHandler的类型处理器,其参数化的时候传入的是Configuration类的typeHandlerRegistry变量,也就是直接用的是当前这个TypeHandlerRegistry。
然后可以看到TypeHandlerRegistry会注册默认的处理类型及对应的类型处理器。然后在register方法中还有一个分支,会判断在类型处理器上面有没有@MappedJdbcTypes注解。如果有注解,则最终注册是在typeHandlerMap变量里,也就是处理为一种可以通过java类型获取对应的数据库类型&类型处理器。
@MappedTypes(String.class)
@MappedJdbcTypes(value = { JdbcType.CHAR, JdbcType.VARCHAR }, includeNullJdbcType = true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {
我们可以看到,这个类型处理器其实就是用java的String类型去处理数据库的CHAR及VARCHAR类型。
dler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
}
map.put(jdbcType, handler);
typeHandlerMap.put(javaType, map);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
这个类是用来处理类型的,例如我们再Mapper的xml文件中可以设置列字段的java类型,数据库类型。例如这里的jdbcTypeHandlerMap:key就是数据库类型,然后TypeHandler就是用来处理对应类型的。
这里有一个unknownTypeHandler的类型处理器,其参数化的时候传入的是Configuration类的typeHandlerRegistry变量,也就是直接用的是当前这个TypeHandlerRegistry。
然后可以看到TypeHandlerRegistry会注册默认的处理类型及对应的类型处理器。然后在register方法中还有一个分支,会判断在类型处理器上面有没有@MappedJdbcTypes注解。如果有注解,则最终注册是在typeHandlerMap变量里,也就是处理为一种可以通过java类型获取对应的数据库类型&类型处理器。
```java
@MappedTypes(String.class)
@MappedJdbcTypes(value = { JdbcType.CHAR, JdbcType.VARCHAR }, includeNullJdbcType = true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {
我们可以看到,这个类型处理器其实就是用java的String类型去处理数据库的CHAR及VARCHAR类型。