一 使用场景
在项目开发过程中,我们常常碰到这样的情况:Java 对象中的数据范例与数据库中的字段范例不一致。这时,我们需要在保存数据到数据库和从数据库检索数据时进行范例转换。例如:
- 对于一些数据库特有的数据范例(如 PostgreSQL 的 jsonb 或数组范例),这些范例可能不被 MyBatis 默认支持,因此需要特殊处理。
- 在 Java 实体(JavaBean)中,可能有一些字段是罗列(Enum)范例或特殊范例,而在数据库中,这些数据可能需要存储为字符串(String)或整数(Integer)。
- 同样,你的 Java 实体可能有日期(Date)范例的字段,而在数据库中,相应的字段可能是以字符串(varchar)形式存储的日期。
这些范例不匹配的情况会导致大量的手动数据范例转换,这不仅贫苦,而且轻易出错。为了解决这个问题,MyBatis 提供了一种功能强大的机制:TypeHandler 范例处理器。通过实现和使用范例处理器,我们可以自动化地进行数据范例转换,简化代码,提高开发效率。范例处理器使得 MyBatis 能够智能地处理那些它默认不支持的数据库字段范例,同时也方便了开发者在复杂数据范例和数据库范例之间进行无缝转换。
二 范例处理器 TypeHandler简介
在 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
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)
- 当你在 Java 代码中实行一个 SQL 语句并且需要向这个语句中传入参数时,这个方法就发挥作用了。
- 它告诉 MyBatis 怎样将 Java 范例的数据(parameter)转换成数据库能理解的格式,并把它放在 SQL 语句的精确位置(index)。
- getResult(ResultSet rs, String columnName)
- 当你实行了一个 SQL 查询并从数据库得到结果集(ResultSet)时,这个方法帮助你把结果集中某一列的数据取出,并转换成 Java 范例的数据。
- 你告诉它具体要转换哪一列(columnName),它就会处理这一列的数据。
- getResult(ResultSet rs, int columnIndex)
- 这个方法和上一个方法类似,但它是通过列的索引(位置)而不是列的名称来获取数据的。
- 它也是用来把结果集中的数据转换成 Java 范例的数据。
- getResult(CallableStatement cs, int columnIndex)
- 这个方法用在存储过程的场景。存储过程是在数据库中实行的一系列操作,它可以返回多个结果。
- 当你调用一个存储过程并想要处理返回的结果时,这个方法就会根据你指定的列索引来获取并转换这些结果。
总的来说,TypeHandler<T> 就像是一个双向翻译器,它确保 Java 步伐和数据库在数据范例上的沟通是流畅和精确的。
四 创建自定义处理器
实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler 范例来实现自定义范例处理器。
这个范例是抽象范例,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 范例可以极大地低落开发难度。
下面定义三种常用的自定义处理器:
自定义处理器1:数组范例
- 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();
- }
- }
复制代码 自定义处理器2:jsonb范例
- 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);
- }
- }
复制代码 自定义处理器3:罗列范例
- 定义罗列类
假设有一个罗列类 StatusEnum,它有两个值:ACTIVE 和 INACTIVE。
- 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);
- }
- }
复制代码
- 创建自定义范例处理器
创建一个范例处理器来处理 StatusEnum:
这个范例处理器将数据库中的字符串映射到 StatusEnum 罗列上。它假设数据库中存储的是罗列值的名称(例如,“ACTIVE” 或 “INACTIVE”)。
- 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);
- }
- }
复制代码 五 把TypeHandler配置到步伐中有四种方法:
每一种都测试过,把踩过的坑用黑体标识了。
- <resultMap id="BaseResultMap" type="com.xxx.EntiyDto">
- <result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.IntegerArrayTypeHandler"/>
- </resultMap>
复制代码
- 在springboot的yml配置文件中设置范例处理器所在的包名,不是处理器路径(应用到全局)
- 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>
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |