前言
本篇需要对于MyBatis有一定的认识,而且只是针对于TypeHandler接口来讨论,暂不讨论其他方面的问题
TypeHandler概叙
TypeHandler是MyBatis设计的一个用于参数的接口,你们会不会很好奇MyBatis是如何把整形,时间,字符串等映射到数据表字段的,实际上就是这个接口来做的,在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());
}
所以我们平时不用设计什么就可以进行自动转换,而一种特殊情况下,我们需要自己定义,也就是枚举,这也是我们经常用的技术,比如有以下的对象
public class SysUser {
/**
* 用户名
*/
private String name;
/**
* 密码-加密模式
*/
private String password;
/**
* 数据状态,0-正常,1-删除
*/
private DataStateEnum deleted = DataStateEnum.NORMAL;
}
package com.zxc.movie.common.enums;
/**
* 数据状态
*/
public enum DataStateEnum implements BaseEnum<DataStateEnum>{
NORMAL(0, "正常"),
DELETE(1, "删除"),
;
private final Integer code;
private final String desc;
DataStateEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getDesc() {
return desc;
}
}
那么我们如何进行转换呢,其实有很多种方式,下面一个个说下
常规方式
直接继承BaseTypeHandler,然后实现就行,看下面的描述就很简单了
package com.zxc.movie.common.typehandler.common;
import com.zxc.movie.common.enums.BaseEnumUtils;
import com.zxc.movie.common.enums.DataStateEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
/**
* @author zxc_user
* @date 2023/8/7 19:15
* @version 1.0
* @description 数据状态返回
**/
public class DataStateEnumHandler extends BaseTypeHandler<DataStateEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, DataStateEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public DataStateEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
return getByCode(rs.getInt(columnName));
}
@Override
public DataStateEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return getByCode(rs.getInt(columnIndex));
}
@Override
public DataStateEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return getByCode(cs.getInt(columnIndex));
}
private DataStateEnum getByCode(Integer code) {
DataStateEnum[] dataStateEnums = DataStateEnum.values();
for (DataStateEnum baseEnum : dataStateEnums) {
if(Objects.equals(baseEnum.getCode(), code)) {
return baseEnum;
}
}
}
}
这种方式很简单,但是如果有多个枚举类会存在大量的重复代码,所以我们可以把公用逻辑进行抽取,其实只要让枚举有统一的类型,并且获取到所有枚举就行了,那么怎么做呢,需要有以下的步骤
进阶方式
首先,要提供一个接口,然后所有枚举都实现这个接口,如下
/**
* @author zxc_user
* @date 2023/8/7 19:01
* @version 1.0
* @description 枚举基础类
**/
public interface BaseEnum<T>{
Integer getCode();
String getDesc();
}
/**
* 数据状态
*/
public enum DataStateEnum implements BaseEnum<DataStateEnum>{
NORMAL(0, "正常"),
DELETE(1, "删除"),
;
private final Integer code;
private final String desc;
DataStateEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getDesc() {
return desc;
}
}
这样一来,所有枚举都属于BaseEnum类型
其次提供了一个公用的TypeHandler处理器BaseEnumHandler,定义如下
package com.zxc.movie.common.typehandler;
import com.zxc.movie.common.enums.BaseEnum;
import com.zxc.movie.common.enums.BaseEnumUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author zxc_user
* @date 2023/8/7 18:52
* @version 1.0
* @description 通用的枚举类解释器
**/
public abstract class BaseEnumHandler<T extends BaseEnum<T>> extends BaseTypeHandler<T> {
//进一步优化
protected abstract BaseEnum<T>[] getBaseEnumArray();
//根据code获取对应的数据
private BaseEnum<T> getBaseEnum(Integer code) {
BaseEnum<T>[] baseEnumArray = getBaseEnumArray();
for (BaseEnum<T> baseEnum : baseEnumArray) {
if(Objects.equals(baseEnum.getCode(), code)) {
return baseEnum;
}
}
return null;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return (T) getBaseEnum(rs.getInt(columnName));
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return (T) getBaseEnum(rs.getInt(columnIndex));
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return (T) getBaseEnum(cs.getInt(columnIndex));
}
}
原来的DataStateEnumHandler只需要继承上面的处理器,然后实现方法即可,如下
package com.zxc.movie.common.typehandler.common;
import com.zxc.movie.common.enums.BaseEnum;
import com.zxc.movie.common.enums.DataStateEnum;
import com.zxc.movie.common.typehandler.BaseEnumHandler;
/**
* @author zxc_user
* @date 2023/8/7 19:15
* @version 1.0
* @description 数据状态返回
**/
public class DataStateEnumHandler extends BaseEnumHandler<DataStateEnum> {
@Override
protected BaseEnum<DataStateEnum>[] getBaseEnumArray() {
return DataStateEnum.values();
}
}
是不是很简单,到这里其实就已经很容易了,但是其实还能进一步优化,回看逻辑,其实我们只要有办法拿到所有枚举的对象就可以处理,那么有没有办法呢,其实是有的,可以通过反射,如下
最终方式
package com.zxc.movie.common.typehandler;
import com.zxc.movie.common.enums.BaseEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
/**
* @author zxc_user
* @date 2023/8/7 18:52
* @version 1.0
* @description 通用的枚举类解释器
**/
public abstract class BaseEnumHandler<T extends BaseEnum<T>> extends BaseTypeHandler<T> {
private BaseEnum<T>[] baseArrayEnum;
public BaseEnumHandler(){
//获取泛型的类型
Type genericSuperclass = this.getClass().getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
//获取枚举的所有对象,因为都实现了BaseEnum接口
this.baseArrayEnum = (BaseEnum[]) ((Class)actualTypeArgument).getEnumConstants();
}
}
}
//根据code获取对应的数据
private BaseEnum<T> getBaseEnum(Integer code) {
for (BaseEnum<T> baseEnum : baseArrayEnum) {
if(Objects.equals(baseEnum.getCode(), code)) {
return baseEnum;
}
}
return null;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return (T) getBaseEnum(rs.getInt(columnName));
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return (T) getBaseEnum(rs.getInt(columnIndex));
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return (T) getBaseEnum(cs.getInt(columnIndex));
}
}
最重要的地方就是构造器里面通过反射获取到枚举所有类型,然后原来的处理器只需要直接继承就行了,如下
package com.zxc.movie.common.typehandler.common;
import com.zxc.movie.common.enums.DataStateEnum;
import com.zxc.movie.common.typehandler.BaseEnumHandler;
/**
* @author zxc_user
* @date 2023/8/7 19:15
* @version 1.0
* @description 数据状态返回
**/
public class DataStateEnumHandler extends BaseEnumHandler<DataStateEnum> {
}
是不是变的很简单?唯一的缺点就是还是需要创建一个转换器,但是转换器已经不需要实现任何内容了,只需要提供一个类就行了
TypeHandler生效
老版本的MyBatis是通过xml文件注册的,这里就不说了,因为现在都是需要和Spring进行集成的,而且还是SpringBoot,所以这里只说SpringBoot的方式
第一种
在application.yml文件中配置如下内容,type-handlers-package为处理类所在包
mybatis-plus:
type-handlers-package: com.zxc.movie.common.typehandler
第二种
往IOC容器中注入一个Bean,如下,这是MyBatis-PLUS提供的扩展点,是不是很方便?
/**
* @author zxc_user
* @date 2023/8/7 19:17
* @version 1.0
* @description mybatis-plus配置类
**/
@Configuration
public class MyBatisPlusConfig {
/**
* 自定义配置,看源码发现的
* @return
*/
@Bean
public ConfigurationCustomizer mybatisConfiguration() {
return configuration -> {
//注册解析器
configuration.getTypeHandlerRegistry().register(DataStateEnumHandler.class);
};
}
}
这里顺便说一下,第二种方式的优先度比第一种的要高,这个逻辑需要在源码中查看或者官网介绍,源码逻辑在MybatisPlusAutoConfiguration.applyConfiguration中有所体现
总结
到这里本篇文章就结束了,其实并不复杂,主要是为了统一TypeHandler的处理逻辑,最妙的是通过反射获取的枚举列表方法,如下,虽然不难,不过也可以记录一下
public BaseEnumHandler(){
//获取枚举类型
Type genericSuperclass = this.getClass().getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
this.baseArrayEnum = (BaseEnum[]) ((Class)actualTypeArgument).getEnumConstants();
}
}
}