起因
在业务开发过程中,会经常碰到一些不需要检索,仅仅只是查询后使用的字段,例如配置信息,管理后台操作日志明细等,我们会将这些信息以json的方式存储在
假设某表
id | bar | create_time |
---|---|---|
1 | {"name":"Shary","quz":10,"timestamp":1574698533370} | 2019-11-26 00:15:50 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Data public class Foo { private Long id; private String bar; private Bar barObj; private Date createTime; } @Data public class Bar { private String name; private Integer quz; private Date timestamp; } |
在代码中,比较原始的解决方式是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Override public Foo getById(Long id) { Foo foo = fooMapper.selectByPrimaryKey(id); String bar = foo.getBar(); Bar barObj = JsonUtil.fromJson(bar, Bar.class); foo.setBarObj(barObj); return foo; } @Override public boolean save(Foo foo) { Bar barObj = foo.getBarObj(); foo.setBar(JsonUtil.toJson(barObj)); return fooMapper.insert(foo) > 0; } |
这种方式,存在两个问题
- 需要在实体类添加额外的非数据库字段(
barObj ) - 需要在业务逻辑里手动转换,业务逻辑糅杂非业务代码,不够优雅
解决方案
- 定义一个抽象类,继承于
org.apache.ibatis.type.BaseTypeHandler ,用作对象 类型的换转基类;之后但凡想varchar(longvarchar) 与对象 互转,继承此基类即可
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 | public abstract class AbstractObjectTypeHandler<T> extends BaseTypeHandler<T> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JsonUtil.toJson(parameter)); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { String data = rs.getString(columnName); return StringUtils.isBlank(data) ? null : JsonUtil.fromJson(data, (Class<T>) getRawType()); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String data = rs.getString(columnIndex); return StringUtils.isBlank(data) ? null : JsonUtil.fromJson(data, (Class<T>) getRawType()); } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String data = cs.getString(columnIndex); return StringUtils.isBlank(data) ? null : JsonUtil.fromJson(data, (Class<T>) getRawType()); } } |
- 定义具体实现类,继承上述
步骤1 中定义的AbstractObjectTypeHandler ,泛型中填上要转换的Java类型Bar
1 | public class BarTypeHandler extends AbstractObjectTypeHandler<Bar> {} |
- 删除
Foo 中String bar ,并将Bar barObj 改成Bar bar ,让Foo 的字段名跟数据库字段名一一对应
1 2 3 4 5 6 | @Data public class Foo { private Long id; private Bar bar; private Date createTime; } |
- 配置类型处理器扫包路径
- 如果使用
mybatis-spring-boot-starter ,可以在application.properties 里配置mybatis.typeHandlersPackage={BarTypeHandler所在包路径} ; - 如果只使用
mybatis-spring ,可以构造一个SqlSessionFactoryBean 对象,并调用其setTypeHandlersPackage 方法设置类型处理器扫包路径 - 使用其它
Mybatis 扩展组件的,例如mybatis-plus ,同理配置typeHandlersPackage 属性即可
经过上述四个步骤之后,程序就能正常运行,无论插入数据,或者从数据库获取数据,都由
1 2 3 4 5 6 7 8 9 | @Override public Foo getById(Long id) { return fooMapper.selectByPrimaryKey(id); } @Override public boolean save(Foo foo) { return fooMapper.insert(foo) > 0; } |
原理分析
如果只是于使用而言,按照步骤1234走即可,而且4只需要走一次。但是,我们显然不能止步于此,知其然,知其所以然,才能用的安心,用的放心,用的顺手
接下来会以
Configuration
使用
1 | public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { |
1 2 3 4 5 6 7 8 9 | @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } |
而在
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 | protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; // ...(省略) configuration = new Configuration(); // ...(省略) if (hasLength(this.typeHandlersPackage)) { //配置的类型处理器所在包 String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { // 扫包进行注册 configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } // ...(省略) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); // ...(省略) } |
下面按照1个、2个、3个参数的
1个参数
- register(String packageName)
- 扫描packageName包下的TypeHandler类,如果非匿名内部类、非接口、非抽象类,就调用
register(typeHandlerClass) 进行注册
- 扫描packageName包下的TypeHandler类,如果非匿名内部类、非接口、非抽象类,就调用
- register(Class typeHandlerClass)
- 如果
typeHandlerClass 上有MappedTypes 注解,且注解里配置了映射的类型,就调用register(javaTypeClass, typeHandlerClass) 进行注册 - 否则,调用
getInstance 生成TypeHandler 实例,并调用register(typeHandler) 进行注册
- 如果
- register(TypeHandler
typeHandler) - 如果
typeHandler 的Class上有MappedTypes 注解,且注解里配置了映射的类型,就调用register(handledType, typeHandler) 进行注册 - 否则,
typeHandler 如果是TypeReference 的实例,就调用register(typeReference.getRawType(), typeHandler) 进行注册。typeReference.getRawType() 获得的结果是TypeReference 的泛型 - 否则,调用
register((Class ) null, typeHandler)进行注册
- 如果
2个参数
- register(String javaTypeClassName, String typeHandlerClassName)
-
Mybatis 并没有直接使用到,内部是将javaTypeClassName 、typeHandlerClassName 分别转成Class类型,并调用register(javaTypeClass, typeHandlerClass) 进行注册
-
- register(TypeReference
javaTypeReference, TypeHandler handler) -
Mybatis 并没有直接使用到,内部是从javaTypeReference 获取到rawType 之后,调用register(javaType, typeHandler) 进行注册
-
- register(Class javaTypeClass, Class typeHandlerClass)
- 调用
getInstance 生成TypeHandler 实例后,调用register(javaTypeClass, typeHandler) 进行注册 - 该方法在
TypeHandlerRegistry 构造函数中被大量调用,主要用于支持JSR310 的日期类型处理(Since Mybatis 3.4.5),如this.register(Instant.class, InstantTypeHandler.class) 。不过需要吐槽的一点是,由于开发者与之前不同,因此注册的风格与之前不同,调用的API也不同,增加了学习成本
- 调用
- register(Type javaType, TypeHandler typeHandler)
- 如果
typeHandler 的Class上有MappedJdbcTypes 注解- 注解里配置了JdbcType,
调用register(javaType, handledJdbcType, typeHandler) 进行注册 - 否则,若
includeNullJdbcType = true ,调用register(javaType, null, typeHandler) 进行注册
- 注解里配置了JdbcType,
- 否则,调用
register(javaType, null, typeHandler) 进行注册
- 如果
- register(Class
javaType, TypeHandler typeHandler) - 内部调用
register(javaType, typeHandler) - 该方法在
TypeHandlerRegistry 构造函数中被大量调用,如register(Date.class, new DateTypeHandler())
- 内部调用
- register(JdbcType jdbcType, TypeHandler handler)
- 将
的映射关系保存到JDBC_TYPE_HANDLER_MAP - 该方法在
TypeHandlerRegistry 构造函数中被大量调用,如register(JdbcType.INTEGER, new IntegerTypeHandler())
- 将
3个参数
- register(Class javaTypeClass, JdbcType jdbcType, Class typeHandlerClass)
- 调用
getInstance 生成TypeHandler 实例后,调用register(javaTypeClass, jdbcType, typeHandler) 进行注册 - 很少用到,只有在
Mybatis 解析``mybatis-config.xml的 typeHandlers`元素时,可能会调用该方法进行注册,而前文已说过,与spring结合后,该文件已经被抛弃,故不用太关注
- 调用
- register(Class
type, JdbcType jdbcType, TypeHandler handler) - 内部将type强转为
Type 类型后,直接调用register((Type) javaType, jdbcType, handler)
- 内部将type强转为
- register(Type javaType, JdbcType jdbcType, TypeHandler handler)
- 若
javaType 非空,将 的映射关系保存到> TYPE_HANDLER_MAP 中,从中可以看出,对于一个javaType ,可能存在多个typeHandler ,用于跟不同的jdbcType 进行转换 - 将
的映射关系保存到ALL_TYPE_HANDLERS_MAP 中
- 若
以上是从代码的角度进行解读,确保逻辑无误,但容易让人云里雾里,不便于理解,因此有必要在此基础上总结一下规律:
- 单参数的
register 方法有3个,双参数的6个,三参数的3个,共计12个;将拥有相同入参数量的register 方法归为同一层,各层次内部有调用的关系,上层也会调用下层方法,但不存在跨层调用,而最下层,是将注册的各个类型保存到Map维护起来 -
12个
register 方法,目的都是为了寻找JavaType、JdbcType、TypeHandler 及他们之间的关系,最终维护在3个Map中:JDBC_TYPE_HANDLER_MAP 、TYPE_HANDLER_MAP 、ALL_TYPE_HANDLERS_MAP -
javaType、javaTypeClass 描述的是待转换java的类型,在例子中就是Bar.class ;JdbcType 是一个枚举类型,代表Jdbc类型,典型的取值有JdbcType.VARCHAR、JdbcType.BIGINT ;typeHandler、BarTypeHandler 分别代表类型转换器实例及其Class实例,在例子中就是BarTypeHandler、BarTypeHandler.class -
MappedTypes 、MappedJdbcTypes 是两个注解,作用于TypeHandler 上,用于指示、限定其所能支持的JavaType 以及JdbcType
出于篇幅原因以及理解复杂度的考虑,本篇不涉及注解方案,会在后续篇章继续介绍注解的使用姿势及原理,消化了本篇所介绍的内容,届时会更容易理解注解的使用。
接着,回到
1 2 3 4 5 6 7 8 9 10 11 | public void register(String packageName) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); for (Class<?> type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { register(type); } } } |
逻辑会走到下边部分,根据
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 | public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; // 本篇不涉及注解使用方式,因此 mappedTypeFound = false MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { // 走这段逻辑 register(getInstance(null, typeHandlerClass)); } } public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { // 省略try catch if (javaTypeClass != null) { Constructor<?> c = typeHandlerClass.getConstructor(Class.class); return (TypeHandler<T>) c.newInstance(javaTypeClass); } Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); } |
同样忽略注解部分。从2012年发布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { register(handledType, typeHandler); mappedTypeFound = true; } } // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and are not assignable, so just ignore it } } if (!mappedTypeFound) { register((Class<T>) null, typeHandler); } } |
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 | public abstract class TypeReference<T> { private final Type rawType; protected TypeReference() { rawType = getSuperclassTypeParameter(getClass()); } Type getSuperclassTypeParameter(Class<?> clazz) { Type genericSuperclass = clazz.getGenericSuperclass(); if (genericSuperclass instanceof Class) { // try to climb up the hierarchy until meet something useful if (TypeReference.class != genericSuperclass) { return getSuperclassTypeParameter(clazz.getSuperclass()); } throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. " + "Remove the extension or add a type parameter to it."); } Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; // TODO remove this when Reflector is fixed to return Types if (rawType instanceof ParameterizedType) { rawType = ((ParameterizedType) rawType).getRawType(); } return rawType; } // ...(省略) } |
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } } |
终于来到最后维护Map的方法,根据源码,很容易看出主要是维护
1 2 3 4 5 6 7 8 9 10 11 | private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<JdbcType, TypeHandler<?>>(); TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler); } ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); } |
上面分析
注: 由于接下来基本与
继续回到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // ...(省略) if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); // ...(省略) } } // ...(省略) |
使用
1 2 3 4 5 6 7 8 9 10 11 | // org.apache.ibatis.builder.xml.XMLMapperBuilder#parse public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解配`xml`文件中 mapper元素 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } // ...(省略) } |
parameterMap– Deprecated! Old-school way to map parameters. Inline parameters are preferred and this element may be removed in the future. Not documented here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap元素 sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析CRUD 元素 } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } } |
ParameterMapping、ResultMapping
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; 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; // ...(省略) } |
ResultMapping: 结果集的映射关系,是对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class ResultMapping { private Configuration configuration; private String property; private String column; private Class<?> javaType; private JdbcType jdbcType; private TypeHandler<?> typeHandler; private String nestedResultMapId; private String nestedQueryId; private Set<String> notNullColumns; private String columnPrefix; private List<ResultFlag> flags; private List<ResultMapping> composites; private String resultSet; private String foreignColumn; private boolean lazy; // ...(省略) } |
二者有3个同名参数需要我们重点关注:
构造
回到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // org.apache.ibatis.builder.xml.XMLMapperBuilder private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); // 省略try catch statementParser.parseStatementNode(); } } |
SqlSouce: 代表从
Represents the content of a mapped statement read from an XML file or an annotation. It creates the SQL that will be passed to the database out of the input parameter received from the user.
1 2 3 4 5 6 7 8 9 10 11 | public void parseStatementNode() { // ...(省略) String parameterType = context.getStringAttribute("parameterType"); // ...(省略) // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); } |
接下来以
1 2 3 4 5 6 7 | <insert id="insert" parameterType="com.example.demo.model.Foo" > <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" > SELECT LAST_INSERT_ID() </selectKey> insert into foo (bar, create_time) values (#{bar,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}) </insert> |
接着调用到
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 | // org.apache.ibatis.scripting.xmltags.XMLLanguageDriver public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { // 走这儿,parameterType代表入参的类型,在我们case中代表Foo.class sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) { this(configuration, getSql(configuration, rootSqlNode), parameterType); } // sql 代表从statement中提取的原始未经加工的SQL,带有#{bar,jdbcType=VARCHAR}等信息 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<String, Object>()); } public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { // ParameterMapping处理器 ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); // 解析器,解析 #{} GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); // 重点 String sql = parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); } |
来到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // org.apache.ibatis.parsing.GenericTokenParser#parse public String parse(String text) { // ...(省略) builder.append(handler.handleToken(expression.toString())); // ...(省略) } // org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler#handleToken public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } |
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 ParameterMapping buildParameterMapping(String content) { // ...(省略) // propertyType = Bar.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 // ...(省略) } return builder.build(); } |
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 | // org.apache.ibatis.mapping.ParameterMapping.Builder#build public ParameterMapping build() { resolveTypeHandler(); validate(); return parameterMapping; } private void resolveTypeHandler() { // 再次解析typeHandler if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) { Configuration configuration = parameterMapping.configuration; TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 根据javaType、jdbcType去typeHandlerRegistry中找typeHandler parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType); } } private void validate() { // javaType为ResultSet类型,这种使用姿势较少,可以跳过 if (ResultSet.class.equals(parameterMapping.javaType)) { if (parameterMapping.resultMapId == null) { throw new IllegalStateException("Missing resultmap in property '" + parameterMapping.property + "'. " + "Parameters of type java.sql.ResultSet require a resultmap."); } } else { // 再次解析后还空,抛出异常 if (parameterMapping.typeHandler == null) { throw new IllegalStateException("Type handler was null on parameter mapping for property '" + parameterMapping.property + "'. It was either not specified and/or could not be found for the javaType (" + parameterMapping.javaType.getName() + ") : jdbcType (" + parameterMapping.jdbcType + ") combination."); } } } |
在我们的case中,并未明确指定
先根据
再根据
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 | // org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) { return getTypeHandler((Type) type, jdbcType); } private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler<T>) handler; } private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) { Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type); if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { return null; } if (jdbcHandlerMap == null && type instanceof Class) { Class<?> clazz = (Class<?>) type; if (clazz.isEnum()) { jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz); if (jdbcHandlerMap == null) { register(clazz, getInstance(clazz, defaultEnumTypeHandler)); return TYPE_HANDLER_MAP.get(clazz); } } else { jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz); } } TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap); return jdbcHandlerMap; } |
经过上述分析,我们对于一个
接下来,我们看执行
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 | // org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 拿出启动过程过程构建的ParameterMapping 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; // ...(省略) value = metaObject.getValue(propertyName); } // 从parameterMapping中取出typeHandler与jdbcType TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } // 忽略try catch // 调用typeHandler的setParameter方法,完成JavaType到数据库字段的转化 typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } // org.apache.ibatis.type.BaseTypeHandler#setParameter public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { // ...(省略) setNonNullParameter(ps, i, parameter, jdbcType); } |
最终,代码走到我们自定义的
1 2 3 4 5 6 7 8 9 | public abstract class AbstractObjectTypeHandler<T> extends BaseTypeHandler<T> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JsonUtil.toJson(parameter)); } // ...(省略) } |
总结
- 本文一开始提出在表中存储json串的需求,并展示了
手动 将对象与json互转的原始方式,随后给出了Mybatis 优雅存取json字段的解决方案 -TypeHandler - 接着,从
TypeHandler 的注册过程开始介绍,分析了12个register 方法之间错综复杂的关系,最终得出注册过程就是构建三个Map的过程,核心是TYPE_HANDLER_MAP ,它维护着 的映射关系,在构造> ParameterMapping 、ResultMapping 时使用到 - 然后,详细阐述了在应用启动过程中,
Mybatis 如何根据Mapper.xml 和TYPE_HANDLER_MAP 构造ParameterMapping - 最后,简述了当一个
方法被调用时,typeHandler 如何工作
本文力求围绕核心主题,紧着一条主脉落进行讲解,为避免被过多的分支干扰,省略了不少旁枝末节,其中还包含一些比较重要的特性,因此下一篇,将分析