本篇博客主要讲解在MyBatis中如何使用类型处理器。

1. 明确需求

在设计之初,sys_role表的enabled字段有2个可选值,其中0 代表禁用,1代表启用,而且实体类中我们使用的是Interger类型:

/**
* 有效标志
*/
private Integer enabled; public Integer getEnabled() {
return enabled;
} public void setEnabled(Integer enabled) {
this.enabled = enabled;
}

如果要新增或者更新角色信息,我们肯定要校验enabled字段的值必须是0或者1,所以最初的部分代码可能是这样的:

if (sysRole.getEnabled() == 0 || sysRole.getEnabled() == 1) {
sysRoleMapper.updateById(sysRole); sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(0, sysRole.getEnabled());
} else {
throw new Exception("无效的enabled值");
}

这种硬编码的方式不仅看起来不友好,而且不利于后期维护,如果维护的程序员脾气不好,还会骂你,哈哈。

所以我们的需求就是,拒绝硬编码,使用友好的编码方式来校验enabled字段的值是否有效。

2. 使用MyBatis提供的枚举类型处理器

我们通常会使用枚举来解决这种场景。

首先新建com.zwwhnly.mybatisaction.type包,然后在该包下新建枚举Enabled:

package com.zwwhnly.mybatisaction.type;

public enum Enabled {
/**
* 禁用
*/
disabled, /**
* 启用
*/
enabled;
}

其中,disabled对应的索引为0,enabled对应的索引为1。

然后将SysRole类中原来为Integer类型的enabled字段修改为:

/**
* 有效标志
*/
private Enabled enabled; public Enabled getEnabled() {
return enabled;
} public void setEnabled(Enabled enabled) {
this.enabled = enabled;
}

此时原本硬编码的代码就可以修改为:

if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
sysRoleMapper.updateById(sysRole); sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
} else {
throw new Exception("无效的enabled值");
}

虽然上面的代码很完美的解决了硬编码的问题,但此时又引出一个新的问题:

数据库并不能识别Enabled枚举类型,在新增,更新或者作为查询条件时,需要将枚举值转换为数据库中的int类型,在查询数据时,需要将数据库的int类型的值转换为Enabled枚举类型。

带着这个问题,我们在SysRoleMapperTest测试类中添加如下测试方法:

@Test
public void testUpdateById() {
SqlSession sqlSession = getSqlSession(); try {
SysRoleMapper sysRoleMapper = sqlSession.getMapper(SysRoleMapper.class); // 先查询出id=2的角色,然后修改角色的enabled值为disabled
SysRole sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.enabled, sysRole.getEnabled()); // 修改角色的enabled为disabled
sysRole.setEnabled(Enabled.disabled); if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
sysRoleMapper.updateById(sysRole); sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
} else {
throw new Exception("无效的enabled值");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}

运行测试代码,发现抛出如下异常:

Error querying database. Cause: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'enabled' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.zwwhnly.mybatisaction.type.Enabled.1

这是因为MyBatis在处理Java类型和数据库类型时,使用TypeHandler(类型处理器)对这两者进行转换。

MyBatis为Java类型和数据库JDBC中的常用类型类型提供了TypeHandler接口的实现。

MyBatis在启动时会加载所有的JDBC对应的类型处理器,在处理枚举类型时默认使用org.apache.ibatis.type.EnumTypeHandler处理器,这个处理器会将枚举类型转换为字符串类型的字面值使用,对于Enabled枚举来说,就是“disabled"和”enabled"字符串。

而数据库中enabled字段的类型是int,所以在查询到角色信息将int类型的值1转换为Enabled类型报错。

那么如何解决这个问题呢?

MyBatis还提供了另一个枚举处理器:org.apache.ibatis.type.EnumOrdinalTypeHandler,这个处理器使用枚举的索引进行处理,可以解决此处转换报错的问题。

使用这个处理器,需要在之前的resources/mybatis-config.xml中添加如下配置:

<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>

再次运行测试代码,测试通过,输出日志如下:

DEBUG [main] - ==> Preparing: SELECT id,role_name,enabled,create_by,create_time FROM sys_role WHERE id = ?

DEBUG [main] - ==> Parameters: 2(Long)

TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 1

DEBUG [main] - ==> Preparing: UPDATE sys_role SET role_name = ?,enabled = ?,create_by=?, create_time=? WHERE id=?

DEBUG [main] - ==> Parameters: 普通用户(String), 0(Integer), 1(Long), 2019-06-27 18:21:12.0(Timestamp), 2(Long)

DEBUG [main] - <== Updates: 1

从日志中可以看出,在查询角色信息时,MyBatis将1转换为了Enabled.enabled,在更新角色信息时,MyBatis将Enabled.disabled转换为了0。

3. 使用自定义的类型处理器

假设enabled字段的值既不是枚举的字面值,也不是枚举的索引值,此时org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler都不能满足我们的需求,这种情况下我们就需要自己来实现类型处理器了。

首先修改下枚举类Enabled代码:

package com.zwwhnly.mybatisaction.type;

public enum Enabled {

    /**
* 启用
*/
enabled(1), /**
* 禁用
*/
disabled(0); private final int value; private Enabled(int value) {
this.value = value;
} public int getValue() {
return value;
}
}

然后在com.zwwhnly.mybatisaction.type包下新建类型处理器EnabledTypeHandler:

package com.zwwhnly.mybatisaction.type;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler; import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map; /**
* Enabled类型处理器
*/
public class EnabledTypeHandler implements TypeHandler<Enabled> {
private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>(); public EnabledTypeHandler() {
for (Enabled enabled : Enabled.values()) {
enabledMap.put(enabled.getValue(), enabled);
}
} @Override
public void setParameter(PreparedStatement preparedStatement, int i, Enabled enabled, JdbcType jdbcType) throws SQLException {
preparedStatement.setInt(i, enabled.getValue());
} @Override
public Enabled getResult(ResultSet resultSet, String s) throws SQLException {
Integer value = resultSet.getInt(s);
return enabledMap.get(value);
} @Override
public Enabled getResult(ResultSet resultSet, int i) throws SQLException {
Integer value = resultSet.getInt(i);
return enabledMap.get(value);
} @Override
public Enabled getResult(CallableStatement callableStatement, int i) throws SQLException {
Integer value = callableStatement.getInt(i);
return enabledMap.get(value);
}
}

自定义类型处理器实现了TypeHandler接口,重写了接口中的4个方法,并且在无参构造函数中遍历了枚举类型Enabled并对字段enabledMap进行了赋值。

想要使用自定义的类型处理器,也需要在resources/mybatis-config.xml中添加如下配置:

<typeHandlers>
<!--其他配置-->
<typeHandler handler="com.zwwhnly.mybatisaction.type.EnabledTypeHandler"
javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>

运行测试代码,输出日志和上面的输出日志一样,这里不再重复贴出。

4. 源码及参考

源码地址:https://github.com/zwwhnly/mybatis-action.git,欢迎下载。

刘增辉《MyBatis从入门到精通》

原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。

MyBatis从入门到精通(十四):在MyBatis中使用类型处理器的更多相关文章

  1. MyBatis从入门到精通(十二):使用collection标签实现嵌套查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解使用collectio ...

  2. MyBatis从入门到精通(十):使用association标签实现嵌套查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解使用associati ...

  3. 【PHP】最详细PHP从入门到精通(四)——PHP中的字符串

     PHP从入门到精通 之PHP中的字符串 大家好,继续跟进PHP最详尽的知识更新,本周,跟大家重点讲一下PHP中字符串的使用.在PHP中,字符串是非常重要的一个概念,基本上大家想到的字符串的处理功能, ...

  4. salesforce 零基础开发入门学习(十四)salesforce中工厂模式的运用

    提到工厂模式,想必大家都很熟悉,工厂模式作为一种设计模式,同样在salesforce中适用. 举一个例子,笔作为基类,可以有钢笔,铅笔,圆珠笔等等.有一个笔的工厂,当你向它要钢笔,它就会生产一支钢笔; ...

  5. MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. insert用法 1.1 简单的 ...

  6. MyBatis从入门到精通(一):MyBatis入门

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. MyBatis简介 ​ 2001 ...

  7. Bootstrap入门(二十四)data属性

    Bootstrap入门(二十四)data属性 你可以仅仅通过 data 属性 API 就能使用所有的 Bootstrap 插件,无需写一行 JavaScript 代码.这是 Bootstrap 中的一 ...

  8. MyBatis基础入门《二十》动态SQL(foreach)

    MyBatis基础入门<二十>动态SQL(foreach) 1. 迭代一个集合,通常用于in条件 2. 属性 > item > index > collection : ...

  9. 【OpenCV入门教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

    http://blog.csdn.net/poem_qianmo/article/details/26977557 本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog ...

随机推荐

  1. Qt使用第三方库3rdparty

    简述 在 Qt 中经常会用到第三方库,例如:FFmpeg.OpenCV 等.第三方库的使用比较简单,只需要一些基本的配置就可以搞定,一起来看看吧! 简述 第三方库 源代码 库文件 目标目录 第三方库 ...

  2. firemonkey 手机屏幕自适应程序问题

    我是新手.在我才学了2个星期的时候,那个白痴老板说什么手机屏幕自适应程序,我当时不能理解呀,觉得用Delphi的布局设计不就行了吗.结果他说:我就是想让控件内容什么的放在小屏幕手机上也不出来.我就说, ...

  3. QtScript, QML, Quick1, Quick2, Declarative 之间的关系

    QtScript是基于 ECMAScript 的脚本语言 在脚本中可以访问原有C++代码中的QObject类型及其子类的实例,连接信号和槽:也可以创建QObject类型及其子类的实例. 但是QtScr ...

  4. FMX有两种消息处理的实现方式,一种是用TMessageManager来实现自定义的消息,另外一种象TEdit中的实现,直接声明消息方法(firemonkey messaging)

    看FMX代码,发现有两种消息处理的实现方式,一种是用TMessageManager来实现自定义的消息,另外一种象TEdit中的实现,直接声明消息方法.   早前,看过文章说TMessageManage ...

  5. canvas的进阶 - 学习利用canvas做一个炫酷的倒计时功能

    先给大家贴一张图片,因为我不会上传视频( ̄□ ̄||) ,请大家谅解了~  如果有知道怎么上传视频的大神还请指点指点 ^_^ ~ 然后看一下代码: html部分 :  <!DOCTYPE html ...

  6. Java多线程同步工具类之CyclicBarrier

    一.CyclicBarrier使用 CyclicBarrier从字面上可以直接理解为线程运行的屏障,它可以让一组线程执行到一个共同的屏障点时被阻塞,直到最后一个线程执行到指定位置,你设置的执行线程就会 ...

  7. mk、cd、pwd、ls、touch、vi、cat、cp、mv的使用及命令快捷方式

    1 命令提示符 1.1 [ root @ oldboyedu62  ~  ] 1 2 3     4 1:登陆系统的用户身份 2:命令分割符合 3:主机名称信息 4:显示当前所在目录路径 1.2 系统 ...

  8. 【入门】WebRTC知识点概览 | 内有技术干货免费下载

    什么是WebRTC WebRTC 即Web Real-Time Communication(网页实时通信)的缩写,是一个支持网页浏览器之间进行实时数据传输(包括音频.视频.数据流)的技术.经过多年的发 ...

  9. Python自学day-6

    一.编程范式       编程:程序员用特定的语法.数据结构和算法告诉计算机如何执行任务的过程.实现任务有很多不同的方式,根据编程方式的特点进行归纳总结出来的编程方式类别,就叫编程范式.大多数语言只支 ...

  10. 安装Flume——海量日志收集聚合系统

    下载flume:  1.官方网站下载: http://flume.apache.org/download.html 2.百度网盘资源: apache-flume-1.9.0-bin.tar 链接:ht ...