3.3 MyBatis TypeHandler 3.3 MyBatis TypeHandler 详解 MyBatis 的 TypeHandler 允许你控制 MyBatis 如何将 Java 类型转换为 JDBC 类型,以及如何将 JDBC 类型转换为 Java 类型。这在处理特殊类型转换需求时非常有用,例如: 枚举类型转换: 将 Java 枚举类型转换为数据库中存储的字符串或整数。 JSON 类型转换: 将 Java 对象序列化为 JSON 字符串存储在数据库中,反之亦然。 自定义类型转换: 处理数据库中特定格式的数据,并将其转换为 Java 中更易于使用的类型。 3.3.
MyBatis 的 TypeHandler 允许你控制 MyBatis 如何将 Java 类型转换为 JDBC 类型,以及如何将 JDBC 类型转换为 Java 类型。这在处理特殊类型转换需求时非常有用,例如:
枚举类型转换: 将 Java 枚举类型转换为数据库中存储的字符串或整数。
JSON 类型转换: 将 Java 对象序列化为 JSON 字符串存储在数据库中,反之亦然。
自定义类型转换: 处理数据库中特定格式的数据,并将其转换为 Java 中更易于使用的类型。
MyBatis 提供了 org.apache.ibatis.type.TypeHandler 接口,你需要实现这个接口来创建自定义的 TypeHandler。接口定义如下:
package org.apache.ibatis.type; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public interface TypeHandler<T> { /** * 设置非空参数 * @param ps 当前statement对象 * @param i 参数的位置 * @param parameter 参数的值 * @param jdbcType jdbc类型 * @throws SQLException */ void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; /** * 通过列名称获取可为空的结果 * @param rs 当前的结果集 * @param columnName 列名称 * @return 转换后的java对象 * @throws SQLException */ T getResult(ResultSet rs, String columnName) throws SQLException; /** * 通过列索引获取可为空的结果 * @param rs 当前的结果集 * @param columnIndex 列索引 * @return 转换后的java对象 * @throws SQLException */ T getResult(ResultSet rs, int columnIndex) throws SQLException; /** * 通过存储过程获取可为空的结果 * @param cs 当前的存储过程对象 * @param columnIndex 列索引 * @return 转换后的java对象 * @throws SQLException */ T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
接口中的方法:
setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):将 Java 类型 T 的参数设置到 PreparedStatement 中。
ps:PreparedStatement 对象。
i:参数在 SQL 语句中的索引位置(从 1 开始)。
parameter:Java 类型的参数值。
jdbcType:对应的 JDBC 类型。
getResult(ResultSet rs, String columnName):从 ResultSet 中根据列名获取数据,并转换为 Java 类型 T。
rs:ResultSet 对象。
columnName:列名。
getResult(ResultSet rs, int columnIndex):从 ResultSet 中根据列索引获取数据,并转换为 Java 类型 T。
rs:ResultSet 对象。
columnIndex:列索引(从 1 开始)。
getResult(CallableStatement cs, int columnIndex):从 CallableStatement 中根据列索引获取数据,并转换为 Java 类型 T(用于存储过程)。
cs:CallableStatement 对象。
columnIndex:列索引(从 1 开始)。
MyBatis 还提供了一个方便的抽象类 org.apache.ibatis.type.BaseTypeHandler,它实现了 TypeHandler 接口,并处理了 null 值的判断,可以继承它来简化 TypeHandler 的实现。
假设我们有一个 OrderStatus 枚举类型:
public enum OrderStatus { PENDING(1, "待处理"), PROCESSING(2, "处理中"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"); private final int code; private final String description; OrderStatus(int code, String description) { this.code = code; this.description = description; } public int getCode() { return code; } public String getDescription() { return description; } public static OrderStatus fromCode(int code) { for (OrderStatus status : OrderStatus.values()) { if (status.getCode() == code) { return status; } } throw new IllegalArgumentException("Invalid OrderStatus code: " + code); } }
我们希望将 OrderStatus 枚举类型存储为数据库中的整数 code。下面是对应的 TypeHandler 实现:
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; public class OrderStatusTypeHandler extends BaseTypeHandler<OrderStatus> { @Override public void setNonNullParameter(PreparedStatement ps, int i, OrderStatus parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException { int code = rs.getInt(columnName); if (rs.wasNull()) { return null; } return OrderStatus.fromCode(code); } @Override public OrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int code = rs.getInt(columnIndex); if (rs.wasNull()) { return null; } return OrderStatus.fromCode(code); } @Override public OrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int code = cs.getInt(columnIndex); if (cs.wasNull()) { return null; } return OrderStatus.fromCode(code); } }
代码解释:
setNonNullParameter 方法:将 OrderStatus 的 code 值设置到 PreparedStatement 中。
getNullableResult 方法:从 ResultSet 或 CallableStatement 中获取整数 code,然后使用 OrderStatus.fromCode() 方法将其转换为 OrderStatus 枚举类型。注意处理 null 值的情况。
TypeHandler 需要在 MyBatis 的配置文件中注册才能生效。有以下几种注册方式:
1. 在 mybatis-config.xml 中注册:
<configuration> <typeHandlers> <typeHandler handler="com.example.OrderStatusTypeHandler"/> </typeHandlers> </configuration>
2. 在 Mapper XML 文件中注册:
<resultMap id="OrderResult" type="com.example.Order"> <result property="status" column="status_code" typeHandler="com.example.OrderStatusTypeHandler"/> </resultMap> <insert id="insertOrder" parameterType="com.example.Order"> INSERT INTO orders (status_code) VALUES (#{status, typeHandler=com.example.OrderStatusTypeHandler}) </insert> <select id="getOrder" resultMap="OrderResult"> SELECT status_code FROM orders WHERE id = #{id} </select>
3. 使用注解注册:
在 TypeHandler 类上使用 @MappedTypes 和 @MappedJdbcTypes 注解:
import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; @MappedTypes(OrderStatus.class) @MappedJdbcTypes(JdbcType.INTEGER) public class OrderStatusTypeHandler extends BaseTypeHandler<OrderStatus> { // ... (TypeHandler implementation) ... }
然后,在 mybatis-config.xml 中扫描 TypeHandler 所在的包:
<configuration> <typeHandlers> <package name="com.example"/> </typeHandlers> </configuration>
推荐使用第一种方式,即在 mybatis-config.xml 中全局注册 TypeHandler。
下面是 TypeHandler 在 MyBatis 中的工作流程图:
流程说明:
setParameter(): 当 MyBatis 需要将 Java 对象作为参数传递给 SQL 语句时,会调用对应的 TypeHandler 的 setParameter() 方法,将 Java 对象转换为 JDBC 类型,并设置到 PreparedStatement 中。
getResult(): 当 MyBatis 需要从 ResultSet 或 CallableStatement 中获取数据时,会调用对应的 TypeHandler 的 getResult() 方法,将 JDBC 类型的数据转换为 Java 对象。
假设我们需要将 Java 对象序列化为 JSON 字符串存储在数据库中。可以使用 Jackson 或 Gson 等 JSON 库来实现。
下面是一个使用 Jackson 的 JSON TypeHandler 示例:
import com.fasterxml.jackson.databind.ObjectMapper; 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; public class JsonTypeHandler<T> extends BaseTypeHandler<T> { private static final ObjectMapper objectMapper = new ObjectMapper(); private final Class<T> type; public JsonTypeHandler(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Type argument cannot be null"); } this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { try { ps.setString(i, objectMapper.writeValueAsString(parameter)); } catch (Exception e) { throw new SQLException("Error serializing JSON to string: " + e.getMessage(), e); } } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); if (json == null) { return null; } try { return objectMapper.readValue(json, type); } catch (Exception e) { throw new SQLException("Error deserializing JSON from string: " + e.getMessage(), e); } } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String json = rs.getString(columnIndex); if (json == null) { return null; } try { return objectMapper.readValue(json, type); } catch (Exception e) { throw new SQLException("Error deserializing JSON from string: " + e.getMessage(), e); } } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String json = cs.getString(columnIndex); if (json == null) { return null; } try { return objectMapper.readValue(json, type); } catch (Exception e) { throw new SQLException("Error deserializing JSON from string: " + e.getMessage(), e); } } }
代码解释:
JsonTypeHandler 是一个泛型类,需要传入 Java 对象的类型。
setNonNullParameter 方法:使用 ObjectMapper 将 Java 对象序列化为 JSON 字符串,并设置到 PreparedStatement 中。
getNullableResult 方法:从 ResultSet 或 CallableStatement 中获取 JSON 字符串,然后使用 ObjectMapper 将其反序列化为 Java 对象。
注册 JsonTypeHandler:
<typeHandlers> <typeHandler handler="com.example.JsonTypeHandler" javaType="com.example.MyObject"/> </typeHandlers>
使用 JsonTypeHandler:
<resultMap id="MyObjectResult" type="com.example.MyObject"> <result property="data" column="data_json" typeHandler="com.example.JsonTypeHandler"/> </resultMap> <insert id="insertMyObject" parameterType="com.example.MyObject"> INSERT INTO my_objects (data_json) VALUES (#{data, typeHandler=com.example.JsonTypeHandler}) </insert> <select id="getMyObject" resultMap="MyObjectResult"> SELECT data_json FROM my_objects WHERE id = #{id} </select>
MyBatis TypeHandler 是一个强大的工具,可以让你灵活地处理 Java 类型和 JDBC 类型之间的转换。通过自定义 TypeHandler,你可以轻松地处理枚举类型、JSON 类型、自定义类型等特殊类型转换需求,从而更好地适应不同的数据库和业务场景。
希望这篇文章能够帮助你更好地理解和使用 MyBatis TypeHandler。