0
点赞
收藏
分享

微信扫一扫

MyBatis的类型处理器TypeHandler与自定义实现

流程原理分析系列:
​MyBatis原理分析之获取SqlSessionFactoryMyBatis原理分析之获取SqlSessionMyBatis原理分析之获取Mapper接口的代理对象MyBatis原理分析之查询单个对象

通过前面的分析 ,我们可以看到MyBatis在进行参数设置与结果取值的时候,都应用到了各种TypeHandler。这里我们回顾一下设置参数与获取结果的代码。

为参数赋值如下图所示:
MyBatis的类型处理器TypeHandler与自定义实现_sql

从结果取值之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 DEFERED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}

【1】 类型处理器(typeHandlers)

MyBatis 在​​设置预处理语句(​​PreparedStatement​​)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型​。下表描述了一些默认的类型处理器。

提示 从 ​​3.4.5​​​ 开始,MyBatis 默认支持 ​​JSR-310(日期和时间 API)​​ 。

类型处理器

Java 类型

JDBC 类型

BooleanTypeHandler

java.lang.Boolean, boolean

数据库兼容的 BOOLEAN

ByteTypeHandler

java.lang.Byte, byte

数据库兼容的 NUMERIC 或 BYTE

ShortTypeHandler

java.lang.Short, short

数据库兼容的 NUMERIC 或 SMALLINT

IntegerTypeHandler

java.lang.Integer, int

数据库兼容的 NUMERIC 或 INTEGER

LongTypeHandler

java.lang.Long, long

数据库兼容的 NUMERIC 或 BIGINT

FloatTypeHandler

java.lang.Float, float

数据库兼容的 NUMERIC 或 FLOAT

DoubleTypeHandler

java.lang.Double, double

数据库兼容的 NUMERIC 或 DOUBLE

BigDecimalTypeHandler

java.math.BigDecimal

数据库兼容的 NUMERIC 或 DECIMAL

StringTypeHandler

java.lang.String

CHAR, VARCHAR

ClobReaderTypeHandler

java.io.Reader

-

ClobTypeHandler

java.lang.String

CLOB, LONGVARCHAR

NStringTypeHandler

java.lang.String

NVARCHAR, NCHAR

NClobTypeHandler

java.lang.String

NCLOB

BlobInputStreamTypeHandler

java.io.InputStream

-

ByteArrayTypeHandler

byte[]

数据库兼容的字节流类型

BlobTypeHandler

byte[]

BLOB, LONGVARBINARY

DateTypeHandler

java.util.Date

TIMESTAMP

DateOnlyTypeHandler

java.util.Date

DATE

TimeOnlyTypeHandler

java.util.Date

TIME

SqlTimestampTypeHandler

java.sql.Timestamp

TIMESTAMP

SqlDateTypeHandler

java.sql.Date

DATE

SqlTimeTypeHandler

java.sql.Time

TIME

ObjectTypeHandler

Any

OTHER 或未指定类型

EnumTypeHandler

Enumeration Type

VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)

EnumOrdinalTypeHandler

Enumeration Type

任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。

SqlxmlTypeHandler

java.lang.String

SQLXML

InstantTypeHandler

java.time.Instant

TIMESTAMP

LocalDateTimeTypeHandler

java.time.LocalDateTime

TIMESTAMP

LocalDateTypeHandler

java.time.LocalDate

DATE

LocalTimeTypeHandler

java.time.LocalTime

TIME

OffsetDateTimeTypeHandler

java.time.OffsetDateTime

TIMESTAMP

OffsetTimeTypeHandler

java.time.OffsetTime

TIME

ZonedDateTimeTypeHandler

java.time.ZonedDateTime

TIMESTAMP

YearTypeHandler

java.time.Year

INTEGER

MonthTypeHandler

java.time.Month

INTEGER

YearMonthTypeHandler

java.time.YearMonth

VARCHAR 或 LONGVARCHAR

JapaneseDateTypeHandler

java.time.chrono.JapaneseDate

DATE

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

具体做法为:实现 ​​org.apache.ibatis.type.TypeHandler​​​ 接口, 或继承一个很便利的类 ​​org.apache.ibatis.type.BaseTypeHandler​​, 并且可以(可选地)将它映射到一个 JDBC 类型。

实例代码如下:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}

@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}

@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}

@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler" />
</typeHandlers>

<!-- typeHandler指定JavaType jdbcType -->
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"
javaType="java.lang.String" jdbcType="VARCHAR" />

使用上述的类型处理器将会覆盖已有的处理 ​​Java String​​​ 类型的属性以及 ​​VARCHAR​​ 类型的参数和结果的类型处理器。

要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

通过类型处理器的泛型,​​MyBatis​​ 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:​​javaType="String"​​);
  • 在类型处理器的类上增加一个​​@MappedTypes​​​ 注解指定与其关联的​​Java​​​ 类型列表。 如果在​​javaType​​ 属性中也同时指定,则注解上的配置将被忽略。

可以通过两种方式来指定关联的 JDBC 类型:

  • 在类型处理器的配置元素上增加一个​​jdbcType​​​ 属性(比如:​​jdbcType="VARCHAR"​​);
  • 在类型处理器的类上增加一个​​@MappedJdbcTypes​​​ 注解指定与其关联的​​JDBC​​​ 类型列表。 如果在​​jdbcType​​ 属性中也同时指定,则注解上的配置将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 ​​Java​​​ 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 ​​javaType=[Java 类型], jdbcType=null​​ 的组合来选择一个类型处理器。

这意味着使用 ​​@MappedJdbcTypes​​​ 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ​​ResultMap​​​ 中将不会生效。 如果希望能在 ​​ResultMap​​​ 中隐式地使用类型处理器,那么设置 ​​@MappedJdbcTypes​​​ 注解的 ​​includeNullJdbcType=true​​ 即可。

然而从 ​​Mybatis 3.4.0​​​ 开始,如果某个 ​​Java​​​ 类型只有一个注册的类型处理器,即使没有设置 ​​includeNullJdbcType=true​​​,那么这个类型处理器也会是 ​​ResultMap​​​ 使用 ​​Java​​ 类型时的默认处理器。

最后,可以让 MyBatis 帮你查找类型处理器:

<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>

注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。

你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 ​​class​​​ 作为参数的构造器,这样 ​​MyBatis​​ 会在构造一个类型处理器实例的时候传入一个具体的类。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

private Class<E> type;

public GenericTypeHandler(Class<E> type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
}
//...

​EnumTypeHandler 和 EnumOrdinalTypeHandler​​ 都是泛型类型处理器,我们将会在接下来的部分详细探讨。

日期和时间的处理

JDK1.8以前一直是个头疼的问题。我们通常使用​​JSR310​​​规范领导者Stephen Colebourne创建的​​Joda-Time​​​来操作。1.8已经实现全部的​​JSR310​​​规范了。日期时间处理上,我们可以使用MyBatis基于​​JSR310(Date and Time API)​​​编写的各种日期时间类型处理器。​​MyBatis3.4​​​以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。
MyBatis的类型处理器TypeHandler与自定义实现_java_02

【2】实现TypeHandler的两种方式

① 继承BaseTypeHandler抽象类

​BaseTypeHanler extends TypeReference<T> implements TypeHandler<T>​​​ ,我们只需要实现BaseTypeHanler 四个抽象方法即可。
MyBatis的类型处理器TypeHandler与自定义实现_sql_03

如下所示,是mybatis提供的BigInteger类型转换。

public class BigIntegerTypeHandler extends BaseTypeHandler<BigInteger> {

@Override
public void setNonNullParameter(PreparedStatement ps, int i, BigInteger parameter, JdbcType jdbcType) throws SQLException {
ps.setBigDecimal(i, new BigDecimal(parameter));
}

@Override
public BigInteger getNullableResult(ResultSet rs, String columnName) throws SQLException {
BigDecimal bigDecimal = rs.getBigDecimal(columnName);
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}

@Override
public BigInteger getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
BigDecimal bigDecimal = rs.getBigDecimal(columnIndex);
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}

@Override
public BigInteger getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
BigDecimal bigDecimal = cs.getBigDecimal(columnIndex);
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}
}

② 实现TypeHandler接口

TypeHandler接口如下所示,这里的​​T​​表示参数类型。

public interface TypeHandler<T> {

void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

T getResult(ResultSet rs, String columnName) throws SQLException;

T getResult(ResultSet rs, int columnIndex) throws SQLException;

T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

自定义实现实例如下:

public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
//定义当前数据如何保存到数据库中
@Override
public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
JdbcType jdbcType) throws SQLException {
System.out.println("要保存的状态码:"+parameter.getCode());
ps.setString(i, parameter.getCode().toString());
}
@Override
public EmpStatus getResult(ResultSet rs, String columnName)
throws SQLException {
//需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
int code = rs.getInt(columnName);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(ResultSet rs, int columnIndex)
throws SQLException {
int code = rs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(CallableStatement cs, int columnIndex)
throws SQLException {
int code = cs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
}

③ 配置TypeHandler

第一种,在全局配置文件配置

<typeHandlers>
<typeHandler handler="com.mybatis.typehandler.MyEnumEmpStatusTypeHandler"
javaType="com.mybatis.bean.EmpStatus"/>
</typeHandlers>

第二种,结果映射时配置具体属性

<resultMap type="com.mybatis.bean.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler="XXXXX"/>
</resultMap>

第三种,保存数据时具体参数配置

<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender,empStatus)
values
(#{lastName},#{email},#{gender},#{empStatus,typeHandler=xxxx})
</insert>

需要注意的是,如果在具体属性配置,请务必保证查询和保存配置的类型转换器一致。


举报

相关推荐

0 条评论