Mybatis类型处理器原理以及实现自定义TypeHandler

一、基本使用

Mybatis中的TypeHandler有两个功能,一个是实现javaType到jdbcType的转换,另外一个是实现jdbcType到javaType的转换。

TypeHandler是一个接口,其中重点是四个方法,一个取值的方法,三个设置值的方法:


/**
 * @author Clinton Begin
 */
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;

}

执行的流程如该图所示,设置参数可以将java类型转换为jdbc类型;取值方法可以将jdbc类型转换为java类型,而这个TypeHandler扮演了中间转换的角色。

在mybatis中有一个类型处理器的注册工厂,该工厂维护的是一个Map,来维护Java数据类型和类型处理器的对应关系。

Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP

Mybatis内置了绝大多数的类型转换器,当出现复杂类型的映射时,才需要专门去定义自定义的类型转换器。

二、自定义类型转换器

我们在写mapper映射器的配置文件时,不经意间已经用到类型转换,不过是mybatis帮我们完成的。

<mapper namespace="com.hand.dao.UserMapper">

    <insert id="insertUser" parameterType="com.hand.dto.User">
        insert into tb_user (name,address,creation_date,last_update_date)
        values
        (#{name},#{address},#{creationDate},#{lastUpdateDate})
    </insert>
    
</mapper>

像上面例子,只需要向update方法传入一个user对象,mybatis利用反射拆开user对象,然后根据对象中的字段在预处理语句(PreparedStatement)中设置参数,并且根据字段的类型,使用setXXX()方式设置相应的值。XXX可以是Integer,String,Date等Java类型。 同理,在从结果集(ResultSet)中取出一个值时,将使用rs.getInt、rs.getString、rs.getTimeStamp等方法将数据转换为Java对象。

那么问题来了,javaType和jdbcType的转换关系由谁来定呢?这就是类型处理器(type handlers)的功能所在。 
比如java.lang.String转成JDBC.Varchar,java.lang.Integer转成JDBC.int。MyBatis使用内建的类型处理器能转换所有的基本数据类型、基本类型的包装类型、byte[] 、java.util.Date、java.sql.Date、java,sql.Time、java.sql.Timestamp、java枚举类型等。

不过对于自定义的类型怎么办呢? 
假设上面的address在数据库字段类型是varchar(50),但是在User类中的address字段并不是String类型,而是一个自定义的Address类型:

public class Address {
    private String province;//省份
    private String city;//城市

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }
}

此时,MyBatis并不知道该怎样来处理这个类型的对象。 因此,需要创建一个自定义的类型处理器(TypeHandler)了。

1、创建类型转换器:

/**
 * @author [email protected]
 * @version 1.0
 * @name
 * @description
 * @date 2018/8/19
 */
public class AddressTypeHandler extends BaseTypeHandler<Address> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Address address, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, address.toString());
    }
    @Override
    public Address getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return new Address(resultSet.getString(s));
    }
    @Override
    public Address getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return new Address(resultSet.getString(i));
    }
    @Override
    public Address getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return new Address(callableStatement.getString(i));
    }
}

2、给Address创建构造函数,生成对象

 //假设我们存储在db中的字符串是以","号分隔省市关系的
    public Address(String address) {
        if (address != null) {
            String[] segments = address.split(",");
            if (segments.length > 1) {
                this.province = segments[0];
                this.city = segments[1];
            }
            else if (segments.length > 0) {
                this.city = segments[0];
            }
        }
    }

3、实现Address的set方法

set方法是用来将java类型转成数据库存储的类型。重写Address的toString()方法

@Override
public String toString() {
        return this.province + "," + this.city;
}

4、在mybatis文件中注册该类

在放置typeHandlers的各个标签的位置时需要注意放置的顺序

需要按照这个顺序来放置,否则会报错,因为在mybatis在启动时会根据这个顺序去加载各个节点标签的属性值到config对象中。

原博文参考地址:http://blog.csdn.net/soonfly/article/details/65628372 

猜你喜欢

转载自blog.csdn.net/jiaqingShareing/article/details/81876649