mybatis typehandler

时间:2022-03-19 19:21:50

建立TypeHandler

我们知道java有java的数据类型,数据库有数据库的数据类型,那么我们在往数据库中插入数据的时候是如何把java类型当做数据库类型插入数据库,在从数据库读取数据的时候又是如何把数据库类型当做java类型来处理呢?这中间必然要经过一个类型转换。在Mybatis中我们可以定义一个叫做TypeHandler类型处理器的东西,通过它可以实现Java类型跟数据库类型的相互转换。下面将就如何建立自己的TypeHandler做一个简要介绍。

TypeHandler接口

在Mybatis中要实现自己的TypeHandler就需要实现Mybatis为我们提供的TypeHandler接口。在TypeHandler中定义了四个方法:

public interface TypeHandler<T> {  

    /** 

     * 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型 

     * @param ps 当前的PreparedStatement对象 

     * @param i 当前参数的位置 

     * @param parameter 当前参数的Java对象 

     * @param jdbcType 当前参数的数据库类型 

     * @throws SQLException 

     */  

    void setParameter(PreparedStatement ps, int i, T parameter,  

           JdbcType jdbcType) throws SQLException;  

    /** 

     * 用于在Mybatis获取数据结果集时如何把数据库类型转换为对应的Java类型 

     * @param rs 当前的结果集 

     * @param columnName 当前的字段名称 

     * @return 转换后的Java对象 

     * @throws SQLException 

     */  

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

    /** 

     * 用于在Mybatis通过字段位置获取字段数据时把数据库类型转换为对应的Java类型 

     * @param rs 当前的结果集 

     * @param columnIndex 当前字段的位置 

     * @return 转换后的Java对象 

     * @throws SQLException 

     */  

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

    /** 

     * 用于Mybatis在调用存储过程后把数据库类型的数据转换为对应的Java类型 

     * @param cs 当前的CallableStatement执行后的CallableStatement 

     * @param columnIndex 当前输出参数的位置 

     * @return 

     * @throws SQLException 

     */  

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

}  

现在假设我们有一个实体对象User,其中有一个属性interests是String数组类型,如下所示:

public class User {  

    private int id;  

    private String name;  

    private int age;  

    private String[] interests;  

    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 int getAge() {  

       return age;  

    }  

    public void setAge(int age) {  

       this.age = age;  

    }  

    public String[] getInterests() {  

       return interests;  

    }  

    public void setInterests(String[] interests) {  

       this.interests = interests;  

    }  

    @Override  

    public String toString() {  

       return "User [age=" + age + ", id=" + id + ", interests="  

              + Arrays.toString(interests) + ", name=" + name + "]";  

    }  

}  

我们需要把它以拼接字符串的形式存到数据库中,然后在取出来的时候又把它还原为一个String数组。这个时候我们就可以给它定义一个TypeHandler专门来处理String数组类型和数据库VARCHAR类型的相互转换。在这里我们建立一个名叫StringArrayTypeHandler的TypeHandler,代码如下所示:

package com.tiantian.mybatis.handler;  

import java.sql.CallableStatement;  

import java.sql.PreparedStatement;  

import java.sql.ResultSet;  

import java.sql.SQLException;  

import java.sql.Types;  

import org.apache.ibatis.type.JdbcType;  

import org.apache.ibatis.type.TypeHandler;  

public class StringArrayTypeHandler implements TypeHandler<String[]> {  

       public String[] getResult(ResultSet rs, String columnName)  

                     throws SQLException {  

              String columnValue = rs.getString(columnName);  

              return this.getStringArray(columnValue);  

       }  

       public String[] getResult(ResultSet rs, int columnIndex)  

                     throws SQLException {  

              String columnValue = rs.getString(columnIndex);  

              return this.getStringArray(columnValue);  

       }  

       public String[] getResult(CallableStatement cs, int columnIndex)  

                     throws SQLException {  

              // TODO Auto-generated method stub  

              String columnValue = cs.getString(columnIndex);  

              return this.getStringArray(columnValue);  

       }  

       public void setParameter(PreparedStatement ps, int i, String[] parameter,  

                     JdbcType jdbcType) throws SQLException {  

              if (parameter == null)  

                     ps.setNull(i, Types.VARCHAR);  

              else {  

                     StringBuffer result = new StringBuffer();  

                     for (String value : parameter)  

                            result.append(value).append(",");  

                     result.deleteCharAt(result.length()-1);  

                     ps.setString(i, result.toString());  

              }  

       }  

       private String[] getStringArray(String columnValue) {  

              if (columnValue == null)  

                     return null;  

              return columnValue.split(",");  

       }  

}  

BaseTypeHandler抽象类

在实现自己的TypeHandler时,除了上面提到的实现最原始的接口之外,Mybatis还为我们提供了一个实现了TypeHandler接口的抽象类BaseTypeHandler。所以我们也可以通过继承BaseTypeHandler来实现自己的TypeHandler。

我们先来看一下BaseTypeHandler类的定义:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {  

  protected Configuration configuration;  

  public void setConfiguration(Configuration c) {  

    this.configuration = c;  

  }  

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

    if (parameter == null) {  

      if (jdbcType == null) {  

        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");  

      }  

      try {  

        ps.setNull(i, jdbcType.TYPE_CODE);  

      } catch (SQLException e) {  

        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +  

             "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +  

             "Cause: " + e, e);  

      }  

    } else {  

      setNonNullParameter(ps, i, parameter, jdbcType);  

    }  

  }  

  public T getResult(ResultSet rs, String columnName) throws SQLException {  

    T result = getNullableResult(rs, columnName);  

    if (rs.wasNull()) {  

      return null;  

    } else {  

      return result;  

    }  

  }  

  public T getResult(ResultSet rs, int columnIndex) throws SQLException {  

    T result = getNullableResult(rs, columnIndex);  

    if (rs.wasNull()) {  

      return null;  

    } else {  

      return result;  

    }  

  }  

  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {  

    T result = getNullableResult(cs, columnIndex);  

    if (cs.wasNull()) {  

      return null;  

    } else {  

      return result;  

    }  

  }  

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;  

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;  

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;  

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;  

}  

我们可以看到BaseTypeHandler对TypeHandler接口的四个方法做了一个简单的选择,把null值的情况都做了一个过滤,核心的取值和设值的方法还是抽象出来了供子类来实现。使用BaseTypeHandler还有一个好处是它继承了另外一个叫做TypeReference的抽象类,通过TypeReference的getRawType()方法可以获取到当前TypeHandler所使用泛型的原始类型。这对Mybatis在注册TypeHandler的时候是非常有好处的。在没有指定javaType的情况下,Mybatis在注册TypeHandler时可以通过它来获取当前TypeHandler所使用泛型的原始类型作为要注册的TypeHandler的javaType类型,这个在讲到Mybatis注册TypeHandler的方式时将讲到。

当通过继承BaseTypeHandler来实现自己的TypeHandler时,我们的StringArrayTypeHandler应该这样写:

public class StringArrayTypeHandler extends BaseTypeHandler<String[]> {  

    @Override  

    public String[] getNullableResult(ResultSet rs, String columnName)  

           throws SQLException {  

       return getStringArray(rs.getString(columnName));  

    }  

    @Override  

    public String[] getNullableResult(ResultSet rs, int columnIndex)  

           throws SQLException {  

       return this.getStringArray(rs.getString(columnIndex));  

    }  

    @Override  

    public String[] getNullableResult(CallableStatement cs, int columnIndex)  

           throws SQLException {  

       return this.getStringArray(cs.getString(columnIndex));  

    }  

    @Override  

    public void setNonNullParameter(PreparedStatement ps, int i,  

           String[] parameter, JdbcType jdbcType) throws SQLException {  

       //由于BaseTypeHandler中已经把parameter为null的情况做了处理,所以这里我们就不用再判断parameter是否为空了,直接用就可以了  

       StringBuffer result = new StringBuffer();  

       for (String value : parameter)  

           result.append(value).append(",");  

       result.deleteCharAt(result.length()-1);  

       ps.setString(i, result.toString());  

    }  

    private String[] getStringArray(String columnValue) {  

       if (columnValue == null)  

           return null;  

       return columnValue.split(",");  

    }  

}

注册TypeHandler

建立了自己的TypeHandler之后就需要把它注册到Mybatis的配置文件中,让Mybatis能够识别并使用它。注册TypeHandler主要有两种方式,一种是通过在Mybatis配置文件中定义typeHandlers元素的子元素typeHandler来注册;另一种是通过在Mybatis配置文件中定义typeHandlers元素的子元素package来注册。使用typeHandler子元素注册时一次只能注册一个TypeHandler,而使用package子元素注册时,Mybatis会把指定包里面的所有TypeHandler都注册为TypeHandler。使用typeHandler子元素注册时我们需要通过它的handler属性来指明当前要注册的TypeHandler的全名称,这个属性是必须要的。另外还有两个附加属性可以指定,一个是javaType,用以指定对应的java类型;另一个是jdbcType,用以指定对应的jdbc类型。使用package子元素注册时需要我们通过它的name属性来指定要扫描的包,如果这个时候我们也需要指定对应TypeHandler的javaType和jdbcType的话就需要我们在TypeHandler类上使用注解来定义了。Mybatis注册TypeHandler最基本的方式就是建立一个javaType、jdbcType和TypeHandler的对应关系。在使用typeHandler子元素进行注册的时候,有三种类型的注册方式:

1.如果我们指定了javaType和jdbcType,那么Mybatis会注册一个对应javaType和jdbcType的TypeHandler。

2.如果我们只指定了javaType属性,那么这个时候又分两种情况:

(1)如果我们通过注解的形式在TypeHandler类上用@MappedJdbcTypes指定了对应的jdbcType,那么Mybatis会一一注册指定的javaType、jdbcType和TypeHandler的组合,也包括使用这种形式指定了jdbcType为null的情况。现假设我们有如下这样一个StringArrayTypeHandler:

@MappedJdbcTypes({JdbcType.VARCHAR})  

public class StringArrayTypeHandler implements TypeHandler<String[]> {  

    //..中间的实现代码省略了  

    //..  

}  

然后我们在Mybatis的配置文件中这样注册它:

<typeHandlers>  

   <typeHandler handler="com.tiantian.mybatis.handler.StringArrayTypeHandler" javaType="[Ljava.lang.String;"/>  

</typeHandlers>  

则Mybatis在实际注册的时候是以javaType为String数组,jdbcType为VARCHAR来注册StringArrayTypeHandler的。