【保姆级】使用Mybatis的TypeHandler优雅的存取自定义类型

2 篇文章 0 订阅
2 篇文章 0 订阅

在网上找了很多文章,要么太浅,要么太深,案例较少,所以自己总结了一篇保姆级的使用教程

文中主要举了三种比较常见的例子(自定义类、自定义枚举、自定义类的集合)

快速使用直接跳到结论即可

本文仅讲述使用mybatis-plus的注解方式实现(XML配置实现见最下方的参考资料)

文章目录

1 问题描述

创建的Java实体类中含有自定义的对象/枚举,想把这个自定义的对象/枚举,以某种格式存入到数据库,再从数据库取出后得到自定义的类型。有时自定义的类型也会放在集合中进行存取。

2 场景举例

2.1 需要将User对象存入到数据库

public class User{
	private Integer id;
    private String name;
    private UserInfo userInfo;
}
public class UserInfo {
    private String address;
    private String phone;
}

2.2 以往的解决方案

public class User{
	private Integer id;
    private String name;
    private String userInfo;
}

将User中userInfo的类型改为String,在保存到User之前就将UserInfo转成Json格式的字符串,然后保存到userInfo中,最后将User存入数据库

缺点:略显繁琐,每次存取之前都要进行JSON转换

当然你可能只需要把数据库保存的字符串类型的JSON数据返还给前端即可,但是如果你在后端需要利用这个JSON中的一些数据进行一些业务逻辑判断和操作的话,还是需要将这个字符串类型的JSON数据转换为自定义的对象,所以不如一劳永逸,在实体类中就使用自定义的类型。

2.3 直接将User对象存入到数据库

插入失败,因为mybatis无法识别我们自定义的UserInfo

2.4 解决方法

使用TypeHandle

3 了解TypeHandler

前提:我们通过mybatis对数据库的存取都要通过TypeHandler进行类型转换

举个例子:Java中定义了UserInfo类型,但是数据库不认识,我们现在就需要将UserInfo转换为数据库认识的类型

3.1 为什么Java自带的类型在存取的时候不会出错,我们自定义的类型就会出错

mybatis已经将这些类型的TypeHandler提前写好了

在这里插入图片描述

3.2 TypeHandler解决了什么问题?

1.可以指定我们在Java实体类所包含的自定义类型存入数据库后的类型是什么

2.从数据库中取出该数据后自动转换为我们自定义的Java类型

3.3 使用TypeHandler之前需要明确的事

1.想让当前实体类中自定义的对象存入到数据库后是什么格式

2.想让这个字段取出后是什么格式(通常来说,存入前和取出后都是想要我们自定义的类型)

3.4 BaseTypeHandler

这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法。

继承 BaseTypeHandler 类型可以极大地降低开发难度。

4 使用TypeHandler

4.1 继承BaseTypeHandler

继承BaseTypeHandler后需要实现四个方法

4.1.1 setNonNullParameter(插入时设置参数类型)
preparedStatement:SQL预编译对象
i:需要赋值的索引位置(相当于在JDBC中对占位符的位置进行赋值)
userInfo:索引位置i需要赋的值(原本要给这个位置赋的值,在setNonNullParameter方法中主要解决的问题就是将这个自定义类型变成数据库认识的类型)
jdbcType:jdbc的类型
public void setNonNullParameter(PreparedStatement preparedStatement, int i, UserInfo userInfo, JdbcType jdbcType)

注意:第三个参数为BaseTypeHandler的泛型(这个反省就是要进行映射的Java类),实现方法的时候自动生成。

4.1.2 getNullableResult(获取时转换回的自定义类型)
根据列名获取
resultSet:结果集
columnName:列名
public UserInfo getNullableResult(ResultSet resultSet, String columnName)
根据索引位置获取
resultSet:结果集
columnIndex:列索引
public UserInfo getNullableResult(ResultSet resultSet, int columnIndex)
根据存储过程获取
callableStatement:结果集
columnIndex:列索引
public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex)
取得结果,供存储过程使用,针对存储过程而设,通过列下标的方式来获取存储过程输出结果中的数据

getNullableResult详细的调用过程在DefaultResultSetHandler类,搜索:typeHandler.getResult

4.1.3 完整示例

这里是将UserInfo转换为Json格式的字符串,然后插入数据库(这里转换Json使用了自己写的一个工具类JsonUtils封装的Gson,后面附带源码)

从数据库中取出Json格式的UserInfo,然后转换为UserInfo对象
在这里插入图片描述

4.2 相关注解

4.2.1 @TableName

来自mybatisplus

使用位置:实体类上

其中包含一个属性:autoResultMap

可取值:true(自动构建)、false(不自动构建)

作用:表示是否自动构建resultMap 并使用

注意:在使用TypeHandler的时候需要指定autoResultMap为true,否则无效

4.2.2 @TableField

来自mybatisplus

使用位置:实体类中的属性上

其中包含一个属性:typeHandler

可取值:继承了TypeHandler的自定义TypeHandler

作用:指定该字段使用哪个TypeHandler进行类型转换

注意:在使用TypeHandler的时候需要指定typeHandler,否则无效

4.1.3 @MappedTypes

来自mybatis

使用位置:自定义的TypeHandler类上

可取值:Java类

作用:需要进行映射(转换)的Java类

4.1.4 @MappedJdbcTypes

来自mybatis

使用位置:自定义的TypeHandler类上

作用:需要转换为数据库中的什么类型(使用mybatis中的JdbcType枚举类进行声明)

5 使用示例

5.1 转换对象

User

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 19:34
 */
@Data
@TableName(value = "user",autoResultMap = true)
public class User {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String name;

    @TableField(value = "user_info",typeHandler = UserInfoTypeHandler.class)
    private UserInfo userInfo;
}

UserInfo

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 19:36
 */
@Data
public class UserInfo {

    private String address;

    private String phone;
}

5.1.1 没有自定义TypeHandler以及没有在实体类中加注解的时候

插入User失败

因为UserInfo不是Java自带的类型,所以我们在插入这个类型mybatis无法识别

在这里插入图片描述

获取User失败

这里可以看到,数据库确实查询到了该数据,没有设置映射规则,所以为null。

在这里插入图片描述

5.1.2 定义UserInfoTypeHandler

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 21:05
 */
@MappedTypes(UserInfo.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, UserInfo userInfo, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, JsonUtils.toJson(userInfo));
    }

    @Override
    public UserInfo getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        return JsonUtils.fromJson(resultSet.getString(columnName),UserInfo.class);
    }

    @Override
    public UserInfo getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        return JsonUtils.fromJson(resultSet.getString(columnIndex),UserInfo.class);
    }

    @Override
    public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        return JsonUtils.fromJson(callableStatement.getString(columnIndex),UserInfo.class);
    }
}

5.1.3 分析UserInfoTypeHandler

在setNonNullParameter方法中

将当前UserInfo对象通过JsonUtils.toJson转换为Json格式的字符串,然后通过JDBC的方式将字符串填充到占位符

在getNullableResult方法中

因为UserInfo在数据库中保存的是Json格式的字符串,所以先将该字符串通过JsonUtils.fromJson转换为UserInfo再进行返回

5.1.4 自定义TypeHandler,在实体类中加入注解

插入User成功

在这里插入图片描述

获取User成功

在这里插入图片描述

5.2 转换枚举

Teacher

/**
 * 教师实体类
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 19:04
 */
@Data
@TableName(value = "teacher",autoResultMap = true)
public class Teacher {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String name;

    @TableField(value = "teacher_level",typeHandler = TeacherLevelEnumTypeHandler.class)
    private TeacherLevelEnum teacherLevel;
}

TeacherLevelEnum

/**
 * 教师等级枚举
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 21:53
 */
public enum  TeacherLevelEnum {

    LOW_LEVEL(1),
    MIDDLE_LEVEL(2),
    HIGH_LEVEL(3);

    @EnumValue
    @Getter
    @JsonValue
    private int value;

    @JsonCreator
    TeacherLevelEnum(int value) {
        this.value = value;
    }

    @JsonCreator
    public static TeacherLevelEnum valueOfStr(String value) {
        return Stream.of(TeacherLevelEnum.values()).filter(queryOpType -> Objects.equals(queryOpType.getValue(), Integer.valueOf(value))).findFirst().orElse(null);
    }
}

5.2.1 没有自定义TypeHandler以及没有在实体类中加注解的时候

插入Teacher失败

在这里插入图片描述

获取Teacher失败

在这里插入图片描述

5.2.2 定义TeacherLevelEnumTypeHandler

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 22:05
 */
@MappedTypes(TeacherLevelEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class TeacherLevelEnumTypeHandler extends BaseTypeHandler<TeacherLevelEnum> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, TeacherLevelEnum teacherLevelEnum, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i,teacherLevelEnum.getValue());
    }

    @Override
    public TeacherLevelEnum getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        return TeacherLevelEnum.valueOfStr(resultSet.getString(columnName));
    }

    @Override
    public TeacherLevelEnum getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        return TeacherLevelEnum.valueOfStr(resultSet.getString(columnIndex));
    }

    @Override
    public TeacherLevelEnum getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        return TeacherLevelEnum.valueOfStr(callableStatement.getString(columnIndex));
    }
}

5.2.3 分析TeacherLevelEnumTypeHandler

在setNonNullParameter方法中

将当前TeacherLevelEnum枚举对象转换为对应的Integer值,然后通过JDBC的方式将字符串填充到占位符

在getNullableResult方法中

因为TeacherLevelEnum在数据库中保存的是枚举的Integer值,所以先将该Integer值通过TeacherLevelEnum中valueOfStr()方法转换TeacherLevelEnum类型再进行返回

5.2.4 自定义TypeHandler,在实体类中加入注解

插入Teacher成功

在这里插入图片描述

获取Teacher成功

在这里插入图片描述

5.3 转换集合

Student

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 19:02
 */
@Data
@TableName(value = "student",autoResultMap = true)
public class Student {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String name;

    @TableField(value = "transcript",typeHandler = ListTranscriptTypeHandler.class)
    private List<Transcript> transcripts;

}

Transcript

/**
 * 学生成绩单
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 22:26
 */
@Data
@Builder
public class Transcript {

    private String subject;

    private Integer score;
}

5.3.1 没有自定义TypeHandler以及没有在实体类中加注解的时候

插入Student失败

在这里插入图片描述

获取Student失败

在这里插入图片描述

5.3.2 定义ListTranscriptTypeHandler

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 22:24
 */
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class ListTranscriptTypeHandler extends BaseTypeHandler<List<Transcript>> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<Transcript> transcripts, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i,JsonUtils.toJson(transcripts));
    }

    @Override
    public List<Transcript> getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        return JsonUtils.fromJsonList(resultSet.getString(columnName),Transcript.class);
    }

    @Override
    public List<Transcript> getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        return JsonUtils.fromJsonList(resultSet.getString(columnIndex),Transcript.class);
    }

    @Override
    public List<Transcript> getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        return JsonUtils.fromJsonList(callableStatement.getString(columnIndex),Transcript.class);
    }
}

5.3.3 分析ListTranscriptTypeHandler

在setNonNullParameter方法中

将当前transcripts(本质是List)通过JsonUtils.toJson转换为Json格式的字符串,然后通过JDBC的方式将字符串填充到占位符

在getNullableResult方法中

因为transcripts在数据库中保存的是Json数组格式的字符串,所以先将该字符串通过JsonUtils.fromJsonList工具类转换为多个Transcript对象,再对临时的List进行填充,最后再返回这个List即可

5.3.4 自定义TypeHandler,在实体类中加入注解

插入Student成功

在这里插入图片描述

获取Student成功

在这里插入图片描述

6 工具类JsonUtils

/**
 * @Author ChenJiahao(五条)
 * @Date 2021/8/21 21:00
 */
public class JsonUtils {

    private static ObjectMapper objectMapper = new ObjectMapper();
    private static final Gson GSON = new Gson();

    static {
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        SimpleDateFormat sdf = new SimpleDateFormat();
        objectMapper.setDateFormat(sdf);

    }

    public static String toJson(Object object) {
        if (Objects.isNull(object)) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }

    public static <T> T fromJson(String json, Class<T> classOfT) {
        return GSON.fromJson(json, classOfT);
    }

    public static <T> List<T> fromJsonList(String json, Class<T> genericTypeOfList) {
        List<T> list = new ArrayList<>();
        JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray();
        for (JsonElement jsonElement : jsonArray) {
            list.add(GSON.fromJson(jsonElement, genericTypeOfList));
        }
        return list;
    }
}

7 结论

7.1 自定义TypeHandler

​ 1.继承BaseTypeHandler

​ 2.类上加注解@MappedTypes(声明需要处理Java中的什么类型)

​ 3.类上加注解@MappedJdbcTypes(声明需要转换数据库中的什么类型:使用mybatis中的JdbcType枚举类进行声明)

​ 4.setNonNullParameter()方法中,通过JDBC的方式将自定义类型转换为数据库能够认知的类型后填充到占位符

​ 5.getNullableResult()方法中,通过结果集和列名/列索引获取到返回结果,再将这个返回结果转换为想要的结果类型(对象/枚举/集合),将这个结果类型作为方法的返回值即可

7.2 实体类中

​ 1.在类上加@TableName,设置autoResultMap为true

​ 2.在属性上加@TableField,指定typeHandler为自定义的TypeHandler

7.3 注意事项

​ 在setNonNullParameter()和getNullableResult()方法中,只需要明确想要插入哪种类型以及想要获取到哪种类型之后就可以顺利完成实现方法中的代码逻辑编写

8 示例源码地址

https://github.com/ChenJiahao0205/demo-typehandler

参考资料

MyBatis自定义类型处理器 TypeHandler
[Mybatis] TypeHandler的简单应用及源码分析

最后

感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧,欢迎大家关注和收藏转发文章!

  • 33
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五条Programmer

比心~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值