mybatis 参数赋值及类型解析
基本类型处理器
configuration对象初始化的时候会创建TypeHandlerRegistry,构造方法里指定了默认类型处理。基本类型常见的数据库类型都又对应的解析器。
TypeHandlerRegistry类typeHandlerMap属性存储了javaType和类型TypeHandler之间的映射关系。这里的mapkey值就是javaType对应的Class,然后value呢?我们知道一个类型可以对应多个类型,如varchar和char都可以解析成String类型。所以这里的value又是一个map,key是jdbcType,value是TypeHandler。所以这个结构就是{
javaTypeClass1->map{
jdbcType1->TypeHandler1,
jdbcType1->TypeHandler2...
},
javaTypeClass2->map{
jdbcType1->TypeHandler1,
jdbcType1->TypeHandler2...
}
...
},
来看下String类型对应的TypeHandler具体结构:
注意看这里内层map有个null的key。这里先不说,后面会说到。
类型处理器注册过程
在构造函数里会注册基本处理类型,然后从类型解析器的MappedJdbcTypes注解,拿到jdbcType。最后存储到上面说的typeHandlerMap中。
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());
...
}
//解析typeHandler上的MappedJdbcTypes注解,拿到jdbcType
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);
}
}
/**
最后的注册方法,存储到上面所的typeHandlerMap结构中
**/
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
//拿出javaType对应的所有类型解析器
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);
}
语句解析
XMLStatementBuilder.parseStatementNode解析statment语句。也就是对应配置的select|insert|update|delete标签。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
...
//解析sql语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//解析的语句构造一个MappedStatement对象进行存储
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
上面的parseStatementNode方法会将语句标签上配置的属性解析出来,然后sql解析是下面调用链。
XMLStatementBuilder.parseStatementNode
XMLLanguageDriver.createSqlSource
XMLScriptBuilder.parseScriptNode
new RawSqlSource(SqlSourceBuilder.parse())
GenericTokenParser.parse()
//最后返回一个StaticSqlSource类型的sqlSource,handler是ParameterMappingTokenHandler,解析的参数都在sqlSource里
return new StaticSqlSource(configuration, sql, handler.getParameterMappings())
具体sql语句解析交给GenericTokenParser.parse()。具体的就是把#{}配置的参数按顺序解析成参数集,然后sql变成带?的预编译sql语句。parse()方法里就是对sql进行字符串处理,两个变量openToken = "#{",closeToken = "}"用来找到变量的位置,然后将#{expression}中的expression交给ParameterMappingTokenHandler.handleToken()方法进行解析参数映射。最后返回 如语句
select * from sys_user where userNo = #{userNo,jdbcType=VARCHAR}
最后语句会转成
select * from sys_user where userNo = ?
然后调用ParameterMappingTokenHandler.handleToken(”userNo,jdbcType=VARCHAR“)处理参数。
SqlSourceBuilder.handleToken 处理sql中的#{}变量,会放到parameterMappings中。parameterMappings是一个list,里面元素是ParameterMapping对象。ParameterMapping对象有以下主要属性,都可以通过#{expression}中的expression进行解析。在sql参数中最简单的配置方式是 #{name}这样配置,其实还可以指定以下参数javaType,jdbcType,typeHandler,mode 等。
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
这也是看到上面buildParameterMapping最后有对propertiesMap这些属性的处理。
public class ParameterMapping {
//属性名
private String property;
//参数类型 IN/OUT
private ParameterMode mode;
//java类型
private Class<?> javaType = Object.class;
//jdbc类型
private JdbcType jdbcType;
private Integer numericScale;
//类型处理器
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
//原始配置表达式
private String expression;
}
下面是ParameterMapping具体解析。SqlSourceBuilder.handleToken方法
public String handleToken(String content) {
//将变量放到参数映射map里
parameterMappings.add(buildParameterMapping(content));
//返回?变量占位符
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
/**将表达式(#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler})转成map
变量名的key会转成property
*/
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
//推断变量的类型
if (metaParameters.hasGetter(property)) { //入参有该参数的get方法,是一个对象的属性
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 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 " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
接口参数解析
接口参数解析主要通过反射根据Method方法获取接口参数信息,ParamNameResolver.getNamedParams方法主要解析
//入参args是通过反射拿到的接口实参
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {//没有Param注解,参数只有一个
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {//多个参数返回ParamMap
final Map<String, Object> param = new ParamMap<>();
int i = 0;
//names是通过反射解析的形参名列表
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
//这里GENERIC_NAME_PREFIX值是param,
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
形参的解析默认情况下都是arg0,arg1,...这种样式。从jdk1.8开始可以指定java参数 -parameters保留形参。
入参赋值
主要在DefaultParameterHandler进行完成。看了上面的sql语句解析的过程这里就很好理解了。就是拿出上面解析的参数映射集parameterMappings,然后依次通过PreparedStatement对预编译sql进行参数赋值。最后会调用typeHandler.setParamenter进行赋值。
//parameterObject是接口实参
public void setParameters(PreparedStatement ps) {
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {//参数类型是入参
Object value;
String propertyName = parameterMapping.getProperty();//拿出参数名
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//A实参有对应的类型处理handler,进行参数赋值
value = parameterObject;
} else {//B使用反射获取参数值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
//参数赋值,最后会调用setNonNullParameter方法
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
结合上面的例子,来看以下几个例子:
1、接口:User findUserByNo(String userNo);
sql: select * from sys_user where userNo = #{a1} and userName = #{b1}
最后会发现该方法能正常执行,然后a1和b1都会赋值成userNo的值。这是因为a1和b1的赋值都会走上面代码A处逻辑。所以当入参是基本类型(有对应的类型处理器),sql参数可以随便写。
2、接口:User findUserByNo1(String userNo);
sql: select * from sys_user where userNo = #{a1} and userName = #{b1}
直接报异常:org.apache.ibatis.binding.BindingException: Parameter 'a1' not found. Available parameters are [arg1, arg0, param1, param2]
在前面说缓存的时候,同一个接口且入参相等,所以在构造缓存key的时候会根据sql的参数映射取一次入参变量。executor.createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
...
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
return cacheKey;
}
这里parameterObject是多个参数会返回一个paramMap,会走最后的else通过反射获取属性值,a1在对象中获取不到就抛异常了。
3、接口:User findUserByNo2(User user);
sql: select * from sys_user where userNo = #{userNo} and userName = #{userName}
正常执行,一个参数,会通过反射从入参对应get方法直接取值
4、接口:User findUserByNo3(User user,User1 user1);//User和User1相同
sql: select * from sys_user where userNo = #{userNo} and userName = #{userName}
这里执行又报2的错误了,来分析下多个参数parameterObject还是paramMap,最后还是反射获取值
从metaObject.getValue开始
//入参是参数名
public Object getValue(String name) {
//A 解析sql变量名
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {//B 多级属性
//hasNext就是判断有没有children,看下面的PropertyTokenizer方法就能够指定,参数包含.
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());//indexName是.的前半部分
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {//C这里又调用自己,下次在来的时候就会走上层的else方法了。
return metaValue.getValue(prop.getChildren());
}
} else {//D 这里objectWrapper实例是MapWrapper,因为参数是paramsMap类型
return objectWrapper.get(prop);
}
}
A处代码, PropertyTokenizer构造函数,解析参数名
public PropertyTokenizer(String fullname) {
int delim = fullname.indexOf('.');//参数是否包含 .
if (delim > -1) {//包含,将fullname拆成两部分 name.children
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf('[');
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
D处代码 解析方法在MapWrapper.get方法
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, map);
return getCollectionValue(prop, collection);
} else {
return map.get(prop.getName());
}
}
所以参数userNo走的过程是,A解析变量名,然后不含子属性,直接走D。D里又走的else直接取值,最后从参数集里[arg0,arg1,param1,param2]获取不到属性。抛出异常。
多个对象参数变量要指定具体sql参数输入哪个形参,然后使用 形参.属性名 的形式设置。这里sql的条件就需要改成where userNo = #{param1.userNo} and userName = #{param1.userName}
我们来分析下param1.userNo的解析过程。首先走步骤A解析变量名,含有'.'有children,被分成name.children两部分,进到B。然后根据前半部分param1调用getValue方法,又从A开始,这次会直接走到D,这个时候在MapWrapper.get方法里也就是D能直接获取到param1,然后在B里又根据children从上面获取的param1里反射获取userNo,这里就能获取到值了。
小结
在语句解析的时候会根据#{}占位解析出sql中所有变量到parameterMappings,并且生成带?的预编译sql语句。最后在DefaultParameterHandler.setParameters中根据parameterMappings十二猴子PrepareStatment所有参数。取值是根据sql中变量从mapper接口实参取值。当mapper接口只有一个参数时,且typeHandlerRegistry有该参数类型的解析器,则sql变量取值直接取接口实参,如果没有类型解析器则会尝试通过反射找该变量在入参中的get方法取值。当有多个接口参数时,形参会封装成paramMap, sql中语句变量需要以 形参名形式明确指定,如果时简单基本属性直接使用形参名,如果时对象属性,需要使用 形参名.属性名 的形式mybatis才能解析找到其对应的get方法。
总结一句话入参赋值,第一步需要根据#{}占位表达式变量从接口的入参根据反射获取变量值,第二部根据参数类型找到对应typeHandler对PrepareStatment语句赋值。
resultMap解析
resultMap配置属性字段映射。如果result节点里不配置javaType,则会从返回实体类中去寻找该property对应的get方法,通过入参解析对应类型javaType,拿到javaType后利用上面的typeHandlerMap就可以获取到TypeHandler了。因为jdbcType是可以不配置的,所以也有了上面的null key值。
<resultMap id="userResultMap1" type="com.test.bean.User">
<result property="userId" column="userId" javaType="java.lang.Long" jdbcType="BIGINT"/>
<result property="loginTime" column="loginTime" typeHandler="com.test.mybatis.MyDateTypeHandler" />
</resultMap>
解析开始位置在XMLMapperBuilder.resultMapElement()方法
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
//获取返回类型
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
//获取所有的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {//constructor节点类型处理
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {//discriminator类型节点处理
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//解析result、collection 、association类型配置
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
...
}
//解析result节点
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
//解析xml节点所有配置属性
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap", () ->
processNestedResultMappings(context, Collections.emptyList(), resultType));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//构建ResultMapping对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
MapperBuilderAssistant.buildResultMapping()方法
public ResultMapping buildResultMapping(
Class<?> resultType,
String property,
String column,
Class<?> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class<? extends TypeHandler<?>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
//获取java类型
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//根据指定的typeHandler获取handler实例,如果未指定返回null
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
List<ResultMapping> composites;
if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
composites = Collections.emptyList();
} else {
composites = parseCompositeColumnName(column);
}
//构造ResultMapping对象
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
ResultMapping对象build方法会调用resolveTypeHandler()方法获取具体的typeHandler
private void resolveTypeHandler() {
//配置未指定类型解析器并且javaType不为空
if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
Configuration configuration = resultMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
//获取typeHandler,从上面说的typeHandlerMap里获取对应的TypeHandler
resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
}
}
出参赋值
返回值前面的文章说过是从PrepareStatment里先拿到列的jdbcType,然后根据映射获取property的javaType。这个时候jdbcType和javaType都是有值的。从TypeHandlerRegistry.typeHandlerMap里能获取到handler类型。然后调用handler的getResult()方法最后会调getNullableResult方法获取值。然后赋值给返回对象实例。
DefaultResultSetHandler的getPropertyMappingValue方法
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {//获取类型解析器
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
//获取列值
return typeHandler.getResult(rs, column);
}
}
自定义类型处理器
看了上面的入参出参的出来过程,都可以显示指定类型处理器
入参 :#{name,typeHandler=MyTypeHandler}
出参在resultMap中
<result property="name" column="name" typeHandler="com.test.mybatis.MyTypeHandler" />
如果我们想使用自己的类型解析器,首先要定义自己的TypeHandler然后继承BaseTypeHandler实现其中4个抽象方法。使用@MappedJdbcTypes和@MappedTypes注解标明处理的类型。如下面的自定义Date类型解析器,将数据库bigint类型时间转为java的Date类型。
@MappedJdbcTypes(JdbcType.BIGINT)
@MappedTypes(Date.class)
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i,parameter.getTime());
}
@Override
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
return new Date(rs.getLong(columnName));
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return new Date(rs.getLong(columnIndex));
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getDate(columnIndex);
}
}
最后在配置中注册我们的类型处理器
<typeHandlers>
<typeHandler handler="com.test.mybatis.MyDateTypeHandler"/>
</typeHandlers>
总结
mybatis在初始化的时候会加载所有的类型解析器,存放到TypeHandlerRegistry.typeHandlerMap中。 mapper文件sql语句可以解析出对应的变量参数列表放到Configuration对象mappedStatements集合里,mapper接口方法通过反射也会拿到参数列表。在代理调用的时候可以拿到接口实参列表和形参。在sql参数的具体赋值的时候,根据sql变量从代理实参根据反射推断取值,最后PrepareStatment参数赋值时使用类型解析器对占位变量进行赋值。可以在sql文件里明确的指定javaType,mybatis也可以通过反射自动推断出javaType。 在执行sql和构造缓存key的时候会根据sql参数从接口入参取值。在查询返回的时候jdbcType可以从resultset获取,javaType可以通过反射根据property从entity总获取,可以根据typeHandlerMap获取对应类型解析器进行赋值。