- Mybatis标签之 typeHandlers 使用及解析
- 一、typeHandler 作用
- 二、typeHandler 的使用
- 三、TypeHandlerRegistry
- 四、自定义TypeHandler的解析注册
由于Java 类型和数据库的 JDBC 类型不是一一对应的(比如 String 与 varchar), 所以我们把 Java 对象转换为数据库的值,和把数据库的值转换成 Java 对象,需要经过 一定的转换,这两个方向的转换就要用到 TypeHandler,那么有人可能在想了,我们平时没有做任何关于TypeHandler的配置,为什么实体类对象里面的String属性,可以保存成数据库里面的varchar字段或者保存成char字段呢,这是因为MyBatis中已经内置了很多TypeHandler。
二、typeHandler 的使用这里以这么一个需求来举例说明: 如果我们的对象里面有复杂对象,比如 BlogComment 里面包括了一个 Comment 对象,这个 时候 Comment 对象的全部属性不能直接映射到数据库的一个字段。创建一个 TypeHandler,可以将Comment 转换为 json 字符串,保存到数据库的 VARCHAR 类型中。在从数据库查询的时候,再转换为原来的Comment 对象。
BlogComment实体类:
@Data
public class BlogComment{
private Integer bid;
private String name;
private Integer authorId;
private Comment comment;
}
Comment实体类:
@Data
public class Comment {
Integer commentId;
Integer bid;
String content;
}
1. 编写CommentTypeHandler
public class CommentTypeHandler extends baseTypeHandler
2. 在mybatis-config.xml 文件中注册CommentTypeHandler
** 3. 在需要使用的字段上指定CommentTypeHandler**
比如插入值的时候使用:
insert into blog (bid, name, author_id, comment) values ( #{bid,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{authorId,jdbcType=INTEGER}, #{comment,jdbcType=VARCHAR, typeHandler=com.zdp.type.CommentTypeHandler} )
在查询的时候使用:
select * from blog where bid = #{bid}
4. 测试类
插入测试类
@Test
public void testInsertComment() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogCommentMapper mapper = session.getMapper(BlogCommentMapper.class);
BlogComment blog = new BlogComment();
blog.setAuthorId(3);
blog.setName("name...");
blog.setBid(14);
Comment comment = new Comment();
comment.setBid(233);
comment.setCommentId(999);
comment.setContent("评论....啊啊啊啊啊啊啊嗷嗷嗷");
blog.setComment(comment);
int i = mapper.insertBlog(blog);
session.commit();
} finally {
session.close();
}
}
查询测试类
@Test
public void testQueryOne() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogCommentMapper mapper = session.getMapper(BlogCommentMapper.class);
BlogComment blog = mapper.selectBlogById(14);
System.out.println(blog);
} finally {
session.close();
}
}
** 5. 结果**
插入结果
查询结果
Configuration配置类初始化的时候会去将MyBatis内置的TypeHandler注册到TypeHandlerRegitstry中,这些内置的TypeHandler都在MyBatis的type包下面
public class Configuration {
// ....
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
// ....
}
TypeHandlerRegistry 构造函数
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(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());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, unknownTypeHandler);
register(Object.class, JdbcType.OTHER, unknownTypeHandler);
register(JdbcType.OTHER, unknownTypeHandler);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
register(Instant.class, new InstantTypeHandler());
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalTime.class, new LocalTimeTypeHandler());
register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
register(OffsetTime.class, new OffsetTimeTypeHandler());
register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
register(Month.class, new MonthTypeHandler());
register(Year.class, new YearTypeHandler());
register(YearMonth.class, new YearMonthTypeHandler());
register(JapaneseDate.class, new JapaneseDateTypeHandler());
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
四、自定义TypeHandler的解析注册
从上面我们可看出MyBatis为我们提供了很多内置的TypeHandler,为我们带来了很大的便利,接下来我们看一下我们自定义的TypeHandler在MyBatis中是如何进行解析注册的
当我们在使用SqlSessionFactoryBuilder的build方法 构建 SqlSessionFactory 的时候,会对Mybatis的核心配置文件进行解析
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
在SqlSessionFactoryBuilder的build方法中会使用 XMLConfigBuilder 的 parse()方法对配置文件进行解析
public class SqlSessionFactoryBuilder {
// ....
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//.....
}
在 parse()方法中,parseConfiguration()方法会选取configuration根标签开始解析
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
typeHandlerElement()方法对typeHandlers 标签进行解析
private void parseConfiguration(XNode root) {
try {
//....这里只看typeHandlers的解析
typeHandlerElement(root.evalNode("typeHandlers"));
//....
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
自定义TypeHandler的注册,在MyBatis中提供了typeHandler的两种配置方式,一种是通过package,
一种通过typeHandler
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//读取配置的包路径
String typeHandlerPackage = child.getStringAttribute("name");
//将该包路径下的typeHandler都注册到TypeHandlerRegistry 中
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//读取各标签下的属性配置
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
当typeHandlers标签解析完成后,会将自定义的TypeHandler保存到 TypeHandlerRegistry 的 allTypeHandlersMap里面
我们已经知道MyBatis是如何解析typeHandler的了,那么在查询的时候,MyBatis是如何调用我们自定义的TypeHandler将结果集进行映射的呢?我们接着看
在MyBatis执行查询的时候会调用query()方法在query()方法中调用execute()方法获取到查询结果集之后,会使用ResultSetHandler的handleResultSets()方法进行结果集的转换
@Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
@Override public List
在handleResultSet() 方法中调用了handleRowValue() 方法处理行数据
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//....
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
//....
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//....
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
//....
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
//....
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
//....
}
return rowValue;
}
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, metaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
//....
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
//....
}
最后在获取属性映射值的时候,调用了typeHandler.getResult(rs, column);方法,这里返回的typeHandler就是我们在xml中配置的typeHandler的实例,这里我们断点看一下
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);
}
}
最后调用我们自己实现的CommentTypeHandler 中的 getNullableResult() 对结果集进行转换
public class CommentTypeHandler extends baseTypeHandler
以上就是对typeHandlers标签的简单解析了,希望对你有点帮助!



