⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 cnblogs.com/zwwhnly/p/11238131.html 「申城异乡人」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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 {
thrownew Exception("无效的enabled值");
}

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

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

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

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

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

package com.zwwhnly.mybatisaction.type;

publicenum 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 {
thrownew 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 {
thrownew 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;

publicenum Enabled {

/**
* 启用
*/
enabled(1),

/**
* 禁用
*/
disabled(0);

privatefinalint 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类型处理器
*/
publicclass EnabledTypeHandler implements TypeHandler<Enabled> {
privatefinal 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>

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

文章目录
  1. 1. 1. 明确需求
  2. 2. 2. 使用MyBatis提供的枚举类型处理器
  3. 3. 3. 使用自定义的类型处理器