最近在读刘增辉老师所著的《MyBatis 从入门到精通》一书, 很有收获, 于是将自己学习的过程以博客形式输出, 如有错误, 欢迎指正, 如帮助到你, 不胜荣幸!
本篇博客主要讲解在 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.EnumTypeHandler 和 org.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 从入门到精通》
5. 最后
来源: https://www.cnblogs.com/zwwhnly/p/11238131.html