MyBatis系统定义的TypeHandler 图片看不了?点击切换HTTP 返回上层
MyBatis 内部定义了许多有用的 typeHandler,如表 1 所示。
这些就是 MyBatis 系统已经创建好的 typeHandler。在大部分的情况下无须显式地声明 jdbcType 和 javaType,或者用 typeHandler 去指定对应的 typeHandler 来实现数据类型转换,因为 MyBatis 系统会自己探测。有时候需要修改一些转换规则,比如枚举类往往需要自己去编写规则。
在 MyBatis 中 typeHandler 都要实现接口 org.apache.ibatis.type.TypeHandler,首先让我们先看看这个接口的定义,如下所示。
其中 T 是泛型,专指 javaType,比如我们需要 String 的时候,那么实现类可以写为 implements TypeHandler<String>。
setParameter 方法,是使用 typeHandler 通过 PreparedStatement 对象进行设置 SQL 参数的时候使用的具体方法,其中 i 是参数在 SQL 的下标,parameter 是参数,jdbcType 是数据库类型。
其中有 3 个 getResult 的方法,它的作用是从 JDBC 结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个 getResult 方法是存储过程专用的。
在编写 typeHandler 前,先来研究一下 MyBatis 系统的 typeHandler 是如何实现的,所以有必要先研究一下 MyBatis 系统的 typeHandler。如果读者打开源码,就可以发现它们都继承了 org.apache.ibatis.type.BaseTypeHandler,如下所示。
MyBatis 使用最多的 typeHanlder 之一 —— StringTypeHandler。它用于字符串转换,其源码如下所示。
在这里,MyBatis 把 javaType 和 jdbcType 相互转换,那么它们是如何注册的呢?在 MyBatis 中采用 org.apache.ibatis.type.TypeHandlerRegistry 类对象的 register 方法进行注册,如下所示。
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte,byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short,short | 数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer,int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long,long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
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 |
NClobTypcHandler | 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 类型,存储枚举的索引(而不是名称) |
这些就是 MyBatis 系统已经创建好的 typeHandler。在大部分的情况下无须显式地声明 jdbcType 和 javaType,或者用 typeHandler 去指定对应的 typeHandler 来实现数据类型转换,因为 MyBatis 系统会自己探测。有时候需要修改一些转换规则,比如枚举类往往需要自己去编写规则。
在 MyBatis 中 typeHandler 都要实现接口 org.apache.ibatis.type.TypeHandler,首先让我们先看看这个接口的定义,如下所示。
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;
}
其中 T 是泛型,专指 javaType,比如我们需要 String 的时候,那么实现类可以写为 implements TypeHandler<String>。
setParameter 方法,是使用 typeHandler 通过 PreparedStatement 对象进行设置 SQL 参数的时候使用的具体方法,其中 i 是参数在 SQL 的下标,parameter 是参数,jdbcType 是数据库类型。
其中有 3 个 getResult 的方法,它的作用是从 JDBC 结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个 getResult 方法是存储过程专用的。
在编写 typeHandler 前,先来研究一下 MyBatis 系统的 typeHandler 是如何实现的,所以有必要先研究一下 MyBatis 系统的 typeHandler。如果读者打开源码,就可以发现它们都继承了 org.apache.ibatis.type.BaseTypeHandler,如下所示。
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> { protected Configuration configuration; public void setConfiguration(Configuration c) { this.configuration = c; } @Override 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 { try { setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + e, e); } } } @Override public T getResult(ResultSet rs, String columnName) throws SQLException { T result; try { result = getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.Cause: " + e, e); } if (rs.wasNull()) { return null; } else { return result; } } @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { T result; try { result = getNullableResult(rs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set.Cause: " + e, e); } if (rs.wasNull()) { return null; } else { return result; } } @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { T result; try { result = getNullableResult(cs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement.Cause: " + e, e); } 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 的源码。
- BaseTypeHandler 是个抽象类,需要子类去实现其定义的 4 个抽象方法,而它本身实现了 typeHandler 接口的 4 个方法。
- getResult 方法,非空结果集是通过 getNullableResult 方法获取的。如果判断为空,则返回 null。
- setParameter 方法,当参数 parameter 和 jdbcType 同时为空时,MyBatis 将抛出异常。如果能明确 jdbcType,则会进行空设置;如果参数不为空,那么它将采用 setNonNullParameter 方法设置参数。
- getNullableResult 方法用于存储过程。
MyBatis 使用最多的 typeHanlder 之一 —— StringTypeHandler。它用于字符串转换,其源码如下所示。
@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); }显然它实现了 BaseTypeHandler 的 4 个抽象方法,代码也非常简单。
在这里,MyBatis 把 javaType 和 jdbcType 相互转换,那么它们是如何注册的呢?在 MyBatis 中采用 org.apache.ibatis.type.TypeHandlerRegistry 类对象的 register 方法进行注册,如下所示。
public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); .... register(byte[].class, jdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, jdbcType.LONGVARBINARY, new BlobTypeHandler()); .... }这样就实现了用代码的形式注册 typeHandler。注意,自定义的 typeHandler 一般不会使用代码注册,而是通过配置或扫描,下一节我们开始学习MyBatis自定义TypeHandler。