在项目开发过程中,我们经常遇到这样的情况:Java 对象中的数据类型与数据库中的字段类型不一致。这时,我们需要在保存数据到数据库和从数据库检索数据时进行类型转换。例如:
jsonb
或数组类型),这些类型可能不被 MyBatis 默认支持,因此需要特殊处理。Enum
)类型或特殊类型,而在数据库中,这些数据可能需要存储为字符串(String
)或整数(Integer
)。Date
)类型的字段,而在数据库中,相应的字段可能是以字符串(varchar
)形式存储的日期。这些类型不匹配的情况会导致大量的手动数据类型转换,这不仅麻烦,而且容易出错。为了解决这个问题,MyBatis 提供了一种功能强大的机制:TypeHandler
类型处理器。通过实现和使用类型处理器,我们可以自动化地进行数据类型转换,简化代码,提高开发效率。类型处理器使得 MyBatis 能够智能地处理那些它默认不支持的数据库字段类型,同时也方便了开发者在复杂数据类型和数据库类型之间进行无缝转换。
在 MyBatis 中,TypeHandler
(类型处理器)的主要作用是帮助我们在 Java 代码中使用的数据类型(JavaType
)和数据库中的数据类型(JdbcType
)之间进行转换。这就像是在 Java 世界和数据库世界之间搭建了一座桥梁。
当你需要把数据从 Java 发送到数据库时(比如,插入或更新数据),TypeHandler
确保 Java 类型的数据能够转换成数据库能够理解的格式。这个过程涉及到使用 PreparedStatement
,它是一种预编译的 SQL 语句。TypeHandler
负责把 Java 类型的数据正确地放置到 SQL 语句的参数中。
当你从数据库获取数据时(比如,查询操作),TypeHandler
确保从数据库中获取的数据(通过 ResultSet
或 CallableStatement
)能够转换成 Java 程序中能够使用的格式。这样,你就可以在 Java 程序中方便地处理数据库返回的数据。
重要的一点是,MyBatis 已经内置了许多常见基本类型(如整数、字符串等)的类型处理器。这意味着对于这些基本数据类型,MyBatis 能够自动进行 Java 类型和数据库类型之间的转换,你无需做额外工作。
但是,如果你需要处理一些特殊的数据类型(这些类型可能不是基本类型,比如某种特定格式的字符串,或者是你自定义的复杂类型),MyBatis 就无法直接处理了。在这种情况下,你就需要自定义类型处理器。通过自定义类型处理器,你可以指定如何将这些特殊的 Java 类型数据转换为数据库可以理解的类型,反之亦然。
TypeHandler<T>
接口在 MyBatis 中起着桥梁的作用,它连接了 Java 程序中的数据类型和数据库中的数据类型。这个接口确保了你在 Java 代码中使用的数据类型可以正确地转换成数据库能理解的格式,反之亦然。简单来说,它就像是一个翻译器,帮助 Java 代码和数据库之间进行数据交流。
public interface TypeHandler<T> {
/**
* 设置 PreparedStatement 的指定参数。
*
* @param ps PreparedStatement 对象。
* @param index 参数在 PreparedStatement 中的位置。
* @param parameter 要设置的参数值。
* @param jdbcType JDBC 类型。这是一个可选参数,可以用来控制设置参数时的行为。
* @throws SQLException 如果在设置参数时发生 SQL 异常。
*/
void setParameter(PreparedStatement ps, int index, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 从 ResultSet 中获取数据并转换为 Java 类型。
*
* @param rs ResultSet 对象。
* @param columnName 要获取的数据的列名。
* @return 转换后的 Java 类型数据。
* @throws SQLException 如果在获取数据时发生 SQL 异常。
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 从 ResultSet 中获取数据并转换为 Java 类型。
*
* @param rs ResultSet 对象。
* @param columnIndex 要获取的数据的列索引。
* @return 转换后的 Java 类型数据。
* @throws SQLException 如果在获取数据时发生 SQL 异常。
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* 从 CallableStatement 中获取数据并转换为 Java 类型。
*
* @param cs CallableStatement 对象。
* @param columnIndex 要获取的数据的列索引。
* @return 转换后的 Java 类型数据。
* @throws SQLException 如果在获取数据时发生 SQL 异常。
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
接口中的方法分别处理不同的数据转换场景:
setParameter(PreparedStatement ps, int index, T parameter, JdbcType jdbcType)
parameter
)转换成数据库能理解的格式,并把它放在 SQL 语句的正确位置(index
)。getResult(ResultSet rs, String columnName)
ResultSet
)时,这个方法帮助你把结果集中某一列的数据取出,并转换成 Java 类型的数据。columnName
),它就会处理这一列的数据。getResult(ResultSet rs, int columnIndex)
getResult(CallableStatement cs, int columnIndex)
总的来说,TypeHandler<T>
就像是一个双向翻译器,它确保 Java 程序和数据库在数据类型上的沟通是流畅和准确的。
实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler
类型来实现自定义类型处理器。
这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 类型可以极大地降低开发难度。
下面定义三种常用的自定义处理器:
public class IntegerArrayTypeHandler extends BaseTypeHandler<Integer[]> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer[] parameter, JdbcType jdbcType) throws SQLException {
// 将Java类型转换为数据库类型
Array array = ps.getConnection().createArrayOf("integer", parameter);
ps.setArray(i, array);
}
@Override
public Integer[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从数据库类型转换为Java类型
Array array = rs.getArray(columnName);
return (Integer[]) array.getArray();
}
@Override
public Integer[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Array array = rs.getArray(columnIndex);
return (Integer[]) array.getArray();
}
@Override
public Integer[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Array array = cs.getArray(columnIndex);
return (Integer[]) array.getArray();
}
}
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.postgresql.util.PGobject;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JsonbTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
PGobject jsonObject = new PGobject();
jsonObject.setType("jsonb");
jsonObject.setValue(parameter);
ps.setObject(i, jsonObject);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
public enum StatusEnum {
ACTIVE,
INACTIVE;
public static StatusEnum fromValue(String value) {
for (StatusEnum status : values()) {
if (status.name().equalsIgnoreCase(value)) {
return status;
}
}
throw new IllegalArgumentException("Unknown enum value: " + value);
}
}
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 StatusEnumTypeHandler extends BaseTypeHandler<StatusEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, StatusEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name());
}
@Override
public StatusEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return value == null ? null : StatusEnum.fromValue(value);
}
@Override
public StatusEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return value == null ? null : StatusEnum.fromValue(value);
}
@Override
public StatusEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return value == null ? null : StatusEnum.fromValue(value);
}
}
每一种都测试过,把踩过的坑用黑体标识了。
<resultMap id="BaseResultMap" type="com.xxx.EntiyDto">
<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.IntegerArrayTypeHandler"/>
</resultMap>
mybatis-plus:
type-handlers-package: com.xxx.handler
@TableName(autoResultMap = true)
,否则不生效@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
private Long id;
...
/**
* 注意!! 必须开启映射注解
*
* @TableName(autoResultMap = true)
*
* 以下两种类型处理器,二选一 也可以同时存在
*
* 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
*/
@TableField(typeHandler = IntegerArrayTypeHandler.class)
private Integer[] integerArray;
}
<typeHandlers>
<typeHandler handler="com.xxx.handler.IntegerArrayTypeHandler"/>
</typeHandlers>