博由
上一篇主要讲述了基本的mybatis的配置文件使用,但是没有详细针对typeHandler, plugins, objectFactory三个标签如何使用进行阐述,主要原因是因为这三个东西都是可以自定义的,其作用涉及到一些mybatis的高级功能。本文主要阐述typeHandler的使用方式。
什么是TypeHandler
顾名思义:类型处理器;既然是处理类型的,那么处理什么类型,如何处理类型,有什么作用,才是我们需要关心的问题。
ORM:最大的问题在于使用Java实体类替换直白的SQL Table,换句话说通常一个实体类对应一个Table;也就是我们可以通过操作Class来替换操作Table的方式,就不需要关系这是Oracle的Table还是MYSQL的Table或者其他数据库的Table。那么这里最大的问题就在于javaType和jdbcType之间的映射关系,简单来说就是:实体类的类型如何与数据库的字段类型进行匹配。这样我们在更新数据时,查询数据时可以进行转换,然后直接使用实体类。
预定义映射关系:[摘自源码:TypeHandlerRegistry.java]
构造初始化函数:Key:类型;value:类型处理器
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());
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 ClobTypeHandler());
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 ClobTypeHandler());
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, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
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());
// mybatis-typehandlers-jsr310
try {
// since 1.0.0
register("java.time.Instant", "org.apache.ibatis.type.InstantTypeHandler");
register("java.time.LocalDateTime", "org.apache.ibatis.type.LocalDateTimeTypeHandler");
register("java.time.LocalDate", "org.apache.ibatis.type.LocalDateTypeHandler");
register("java.time.LocalTime", "org.apache.ibatis.type.LocalTimeTypeHandler");
register("java.time.OffsetDateTime", "org.apache.ibatis.type.OffsetDateTimeTypeHandler");
register("java.time.OffsetTime", "org.apache.ibatis.type.OffsetTimeTypeHandler");
register("java.time.ZonedDateTime", "org.apache.ibatis.type.ZonedDateTimeTypeHandler");
// since 1.0.1
register("java.time.Month", "org.apache.ibatis.type.MonthTypeHandler");
register("java.time.Year", "org.apache.ibatis.type.YearTypeHandler");
// since 1.0.2
register("java.time.YearMonth", "org.apache.ibatis.type.YearMonthTypeHandler");
register("java.time.chrono.JapaneseDate", "org.apache.ibatis.type.JapaneseDateTypeHandler");
} catch (ClassNotFoundException e) {
// no JSR-310 handlers
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler())
思考:
默认情况下,mybatis会帮助我们完成一般类型(预定义类型)的转化过程,那么在某些情况下,需要一些特殊类型的处理,就没办法满足了,这个时候,就需要自定义类型处理器,并设定在特定场合触发类型转换。
自定义TypeHandler
我们描述了typeHandler的产生背景,接下来我们需要重点阐述的是如何使用和深度理解其内部机制。
案例
数据库表:
CREATE TABLE `user1` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(128) NOT NULL DEFAULT '' COMMENT '名称',
`created_at` varchar(10) NOT NULL DEFAULT '' COMMENT '创建时间',
`updated_at` varchar(10) NOT NULL DEFAULT '' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表 - 测试typeHandler';
满足:插入时写入Date数据,存储为字符串时间戳,查询获取时为Date类型;
实现自定义时间类型处理器
package com.typehandler.handlers;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by wangzhiping on 17/3/1.
*/
// 设置映射的数据库类型,必须是JdbcType指定类型
@MappedJdbcTypes(JdbcType.VARCHAR)
// 设置映射的java类型,主要是java的类型
@MappedTypes(value=java.util.Date.class)
// 如果没有在javacode中指定javaType 和 javaType那么需要配置文件指定,如果制定了可以不需要再制定
public class MyDateTypeHandler extends BaseTypeHandler<Date>{
// 继承BaseTypeHandler,期泛型就是需要转化的类型
/**
* 该函数:
* @param ps jdbc中的PreparedStatement对象
* @param i 数据的位置索引
* @param parameter 需要插入的数据参数(一般是执行更新、删除、插入时调用)
* @param jdbcType 数据库类型
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
// 注入值时
Long timestamp = parameter.getTime() / 1000;
ps.setString(i, timestamp.toString());
}
@Override
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从数据库中获取数据
String timestamp = rs.getString(columnName);
return new Date(Long.parseLong(timestamp) * 1000);
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String timestamp = rs.getString(columnIndex);
return new Date(Long.parseLong(timestamp) * 1000);
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String timestamp = cs.getString(columnIndex);
return new Date(Long.parseLong(timestamp) * 1000);
}
}
实体类
package com.typehandler.pojo;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
import java.util.Date;
/**
* Created by wangzhiping on 17/2/21.
*/
public class User1 implements Serializable{
private Integer id;
private String name;
private Date createdAt;
private Date updatedAt;
public User1() {
}
public User1(Integer id, String name, Date createdAt, Date updatedAt) {
this.id = id;
this.name = name;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}
配置typeHandler
<!-- 配置typehandlers -->
<typeHandlers>
<package name="com.typehandler.handlers" />
<!-- <typeHandler handler="com.typehandler.handlers.MyDateTypeHandler" /> -->
</typeHandlers>
其中package和typeHandler的概念与typeAliases中的package和typeAlias一致。
使用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.typehandler.pojo.User1">
<insert id="add" parameterType="User1">
INSERT INTO user1
VALUES(null, #{name},
#{createdAt, javaType=java.util.Date, jdbcType=VARCHAR, typeHandler=com.typehandler.handlers.MyDateTypeHandler},
#{createdAt, javaType=java.util.Date, jdbcType=VARCHAR, typeHandler=com.typehandler.handlers.MyDateTypeHandler})
</insert>
<resultMap id="user1Map" type="User1">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 需要做转换的字段必须指定typeHandler -->
<result property="createdAt" column="created_at" typeHandler="com.typehandler.handlers.MyDateTypeHandler"/>
<result property="updatedAt" column="updated_at" typeHandler="com.typehandler.handlers.MyDateTypeHandler"/>
</resultMap>
<select id="findById" parameterType="int" resultMap="user1Map">
SELECT *
FROM user1
WHERE id=#{id}
</select>
</<