赞
踩
TypeHandler是Mybatis中Java对象和数据库JDBC之间进行类型转换的桥梁
是Mybatis内部的一个接口,实现它就可以完成Java对象到数据库之间的转换
内部结构如下:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
第一个方法是从Java对象到数据库的转换,后面三个的是从数据库到Java对象的转换
直接实现TypeHandler接口
Mybatis中有一个抽象类BaseTypeHandler,实现了TypeHandler并进行了扩展
采用直接继承BaseTypeHandler
有一张数据库表,其中有一个details字段为json类型,其DDL为
CREATE TABLE `test` (
`id` int NOT NULL AUTO_INCREMENT,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`details` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
将Java对象转换成数据库中json字段, 通过实现自定义的TypeHandler,完成对test表的查询和插入
首先定义JavaBean
@Data
public class TestDO {
private int id;
private LocalDateTime createTime;
private PersonDO personDO;
}
@Data
public class PersonDO {
private String name;
private int age;
}
TestDO对应test数据库表,PersonDO对应着数据库中的details字段
接下来继承BaseTypeHandler
@MappedJdbcTypes(JdbcType.VARCHAR) @MappedTypes({PersonDO.class}) public class ObjectJSONTypeHandler extends BaseTypeHandler<PersonDO> { private Gson gson = new Gson(); @Override public void setNonNullParameter(PreparedStatement ps, int i, PersonDO parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, gson.toJson(parameter)); } @Override public PersonDO getNullableResult(ResultSet rs, String columnName) throws SQLException { return gson.fromJson(rs.getString(columnName), PersonDO.class); } @Override public PersonDO getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return gson.fromJson(rs.getString(columnIndex), PersonDO.class); } @Override public PersonDO getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return gson.fromJson(cs.getString(columnIndex), PersonDO.class); } }
注意:
剩下的,就是在mapper文件中查询和插入时,指定要使用的typeHandler
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.demoxxx.TestMapper" > <resultMap id="resultMap" type="com.demo.xxx.TestDO"> <id column="id" property="id"/> <result column="create_time" property="createTime"/> <result column="details" property="personDO" typeHandler="com.demo.xxx.ObjectJSONTypeHandler"/> </resultMap> <select id="selectById" parameterType="int" resultMap="resultMap"> select id,create_time,details from test where id=#{param1}; </select> <insert id="insert"> insert into test(create_time,details) values (#{createTime},#{personDO, typeHandler=com.demo.xxx.ObjectJSONTypeHandler}) </insert> </mapper>
或者,不在<resultMap>中指定的话,可以在application.yml文件中指定扫描的类(SpringBoot),这样<resultMap>和insert中就可以不写typeHandler
mybatis:
type-handlers-package: com.xxx.typehandler
区别在于如果写在配置文件中,任何使用PersonDO的地方都会进行转换,写在mapper中,只有对应的SQL会进行转换
当在yml中指定TypeHandler时,它的在于SqlSessionFactoryBean中进行,具体为其中的buildSqlSessionFactory方法
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null; ...省略 if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); }); } if (hasLength(this.typeHandlersPackage)) { scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); }); } ...省略 return this.sqlSessionFactoryBuilder.build(targetConfiguration); }
scanClasses方法进行加载自定义的typeHandler
在Mapper中自定义时,依然也是在SqlSessionFactoryBean的buildSqlSessionFactory方法,不过是在scanClasses下面,毕竟没在配置文件中配置TypeHandler
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null; ...省略 if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } ... 省略 return this.sqlSessionFactoryBuilder.build(targetConfiguration);
这里会扫描mapper文件,扫描完后就会进入xmlMapperBuilder.parse()中解析
//XmlMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
具体的解析"/mapper"元素
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")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
可以看到有resultMap的解析,有insert的解析,这里只看resultMap的解析
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { ...省略 for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } 省略... }
重点在于buildResultMappingFromContext方法中
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { 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); return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
可以看到其中String typeHandler = context.getStringAttribute(“typeHandler”);
在这里完成了Typehandler的加载
总结一下的话
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。