mybatis优雅处理枚举类-typeHandler
前言
上篇文章讲了Mybatis中的EnumOrdinalTypeHandler的使用
但该处理类无法完全通用,对枚举类的限制较多
所以就有了这篇文章,使用自定义typeHandler,自定义枚举类处理规则。
问题处理
自定义TypeHandler
TypeHandler职责
mybatis的TypeHandler的职责在于:
封装数据进preparedStatement中
对查询结果的数据进行转换
当mybatis在设置sql参数,或者是封装查询结果集时,会判断当前要设置进去的对象是否有配置对应的TypeHandler:
如果有,则使用该TypeHandler的setNonNullParameter方法进行参数设置。
无,则使用默认TypeHandler。
枚举类的默认TypeHandler会直接使用枚举数据的名称进行封装
前文设置的EnumOrdinalTypeHandler,则是使用ordinal进行封装。
要注意的是,mybatis调用 TypeHandler 时,设置sql参数时,传递进setNonNullParameter的数据一定是非空的,但封装查询结果集则可能为空,要注意判空处理。
看方法名称即可判别
自定义TypeHandler
mybatis提供了BaseTypeHandler,自定义TypeHandler继承该类,实现方法即可。
package com.will.cn.handler;
import com.will.cn.constants.enums.UserStatus;
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;
/**
* @author jeff
*/
public class UserStatusEnumsTypeHandler extends BaseTypeHandler<UserStatus> {
public UserStatusEnumsTypeHandler(){
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
System.out.println("正在设置PreparedStatement参数:"+parameter+",value:"+parameter.getValue());
ps.setInt(i, parameter.getValue());
}
UserStatus getValue(int value) {
System.out.println("正在转换结果集数据:"+value);
UserStatus[] values = UserStatus.values();
for (UserStatus userStatus : values) {
if (value==userStatus.getValue()){
System.out.println("已匹配到数据:"+userStatus);
return userStatus;
}
}
// TODO 如果找不到数据,是否抛出异常?或者是否要给个异常值?
System.out.println("未获取到正确数据,需要检查数据库内容");
return null;
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
int i =rs.getInt(columnName);
if (0==i && rs.wasNull()) return null;
return getValue(i);
}
@Override
public UserStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int i =rs.getInt(columnIndex);
if (0==i && rs.wasNull()) return null;
return getValue(i);
}
@Override
public UserStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int i =cs.getInt(columnIndex);
if (0==i && cs.wasNull()) return null;
return getValue(i);
}
}
注册TypeHandler
import com.will.cn.constants.enums.UserStatus;
import com.will.cn.handler.UserStatusEnumsTypeHandler;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* @author jeff
* @time 2023/4/30 22:19
*/
@Configuration
public class MybatisConfiguration implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//从spring容器获取sqlSessionFactory
SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
//获取typeHandler注册器
TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
//注册List的typeHandler
typeHandlerRegistry.register(UserStatus.class, UserStatusEnumsTypeHandler.class);
}
}
测试
数据准备
这里我准备了四条数据,两条正常,一条空,一条无法匹配的内容
sql语句![请添加图片描述](https://img-blog.csdnimg.cn/76835c75893a46499426bd56b153f90b.png)
测试结果
测试结果-数据设置
可以看到,使用了preparedStatement的查询,执行了setNonNullParameter
而第三个查询中,虽然也是preparedStatement语句,但因为UserStatus参数为空,因此未调用setNonNullParameter方法。
测试结果-数据封装
再看数据封装的内容,从最后一个查询中,可以看到mybatis为结果中每条数据的每个UserStatus,都调用了getNullableResult方法,至于具体是哪个,就不深究了。
可以看到的是,由于我们在getNullableResult中,获取查询结果的数据时,使用的是getInt方法,当结果数据为空时,int默认值为0,所以需要额外判断是否为Null
wasNull的坑
但是!!跟踪源码发现,wasNull这个方法,如果不先尝试获取数据,mysql-connector是不会初始化该数据的的,所以他的值就是boolean的默认值,false,所以,这里一定要先get,再判空!!!!
附上connector源码源码片段
然后看数据库中值为3的数据,由于未匹配到数据,最终返回了null值,这也体现在了最终返回前端的内容上。
总结
TypeHandler的基本内容到这里就结束了
数据封装问题
null值
数据库结果为null时,个人认为,常量数据,一般在设计数据库时,会规定为非空,所以null一般不用担心。
异常数据问题
这个问题,个人认为一般情况下也不会产生,因为数据的产生也是经过业务代码处理的,一般要么是空值(无法过数据库非空校验),要么就是自行在业务代码中设定的值。
所以一般情况下不会出现异常数据问题,但如果真的出现了异常,比如人为添加数据时产生了偏差,那就自行根据业务代码,判断是返回空,还是抛异常了。
不过,个人认为最合理的解决方法是,在枚举类中,定义一个通用的异常枚举,当获取不到匹配项时,返回异常枚举即可。
通用化TypeHandler
和前文提到的Conventer,Deserialzer,Serializer一样,我们不可能为每一个枚举,都写一套完善的转换规则。
所以就有了通用化的需求。
有空再写吧~