这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
MyBatis实现自定义转换类型
MyBatis为我们提供了很多内置的TypeHandler(类型转换), 随便打开一个TypeHandler,看其源码,都可以看到,它继承自一个抽象类:BaseTypeHandler, 那么我们是不是也能通过继承BaseTypeHandler,从而实现自定义的TypeHandler ? 答案是肯定的, 那么现在下面就为大家演示一下自定义TypeHandler。
我们选择继承BaseTypeHandler来完成工作,BaseTypeHandler需要实现4个方法:
-
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException用于定义设置参数时,该如何把 Java 类型的参数转换为对应的数据库类型
-
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的 Java 类型
-
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的 Java 类型
-
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException用定义调用存储过程后,如何把数据库类型转换为对应的 Java 类型
1.List<String>和varchar类型转换
1.1 环境准备
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`hobby` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
);
个人感觉这种情况还是比较使用的,在关系型数据中可以经常搞一下,方便。
public class Person {
private Integer id;
private List<String> hobbyList;
// 省略getter和setter方法
}
1.2 自定义转换类型
其实就是对预处理赋值的操作和结果集的转换,下面的转换器对JavaType和JdbcType进行互转
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class MyListTypeHandler extends BaseTypeHandler<List<String>> {
@Override
public List<String> getNullableResult( ResultSet rs , String columnName ) throws SQLException {
return this.getList(rs.getString(columnName));
}
@Override
public List<String> getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
return this.getList(rs.getString(columnIndex));
}
@Override
public List<String> getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
return this.getList(cs.getString(columnIndex));
}
@Override
public void setNonNullParameter( PreparedStatement ps , int index , List<String> parameter , JdbcType jdbcType )
throws SQLException {
//1.List集合转字符串
StringBuffer sb = new StringBuffer();
for (String value : parameter) {
sb.append(value).append(",");
}
//2.设置给ps
ps.setString(index, sb.toString().substring(0, sb.toString().length() - 1));
}
private List<String> getList(String columnValue){
if (columnValue == null) {
return null;
}
return Arrays.asList(columnValue.split(","));
}
}
//参考代码:https://www.cnblogs.com/ifme/p/12766128.html
1.3 查询和变更操作
<mapper namespace="com.shxt.model.Person">
<resultMap type="com.shxt.model.Person" id="BaseResultMapper">
<id column="id" property="id"/>
<!-- 使用自定义的转换 -->
<result typeHandler="com.hanpang.handler.MyListTypeHandler"
column="hobby" jdbcType="VARCHAR"
property="hobbyList" javaType="list"
/>
</resultMap>
<select id="load" parameterType="int" resultMap="BaseResultMapper">
SELECT id,hobby FROM person WHERE id=#{id}
</select>
<insert id="add" parameterType="com.shxt.model.Person">
INSERT INTO person (hobby) VALUES (
# {hobbyList,
javaType=java.util.List,
jdbcType=VARCHAR,
typeHandler=com.hanpang.handler.MyListTypeHandler}
)
</insert>
</mapper>
2.String[]和varchar的类型转换
直接上代码
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class MyArrayTypeHander extends BaseTypeHandler<String[]> {
/**
* 获取数据结果集时把数据库类型转换为对应的Java类型
* @param rs 当前的结果集
* @param columnName 当前的字段名称
* @return 转换后的Java对象
* @throws SQLException
*/
@Override
public String[] getNullableResult( ResultSet rs , String columnName ) throws SQLException {
return this.getStringArray(rs.getString(columnName));
}
/**
* 通过字段位置获取字段数据时把数据库类型转换为对应的Java类型
* @param rs 当前的结果集
* @param columnIndex 当前字段的位置
* @return 转换后的Java对象
* @throws SQLException
*/
@Override
public String[] getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
return this.getStringArray(rs.getString(columnIndex));
}
/**
* 调用存储过程后把数据库类型的数据转换为对应的Java类型
* @param cs 当前的CallableStatement执行后的CallableStatement
* @param columnIndex 当前输出参数的位置
* @return
* @throws SQLException
*/
@Override
public String[] getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
return this.getStringArray(cs.getString(columnIndex));
}
/**
* 把Java类型参数转换为对应的数据库类型
* @param ps 当前的PreparedStatement对象
* @param index 当前参数位置
* @param parameter 当前参数的Java对象
* @param jdbcType 当前参数的数据库类型
* @throws SQLException
*/
@Override
public void setNonNullParameter( PreparedStatement ps , int index , String[] parameter , JdbcType jdbcType )
throws SQLException {
// 由于BaseTypeHandler中已经把parameter为null的情况做了处理,所以这里我们就不用在判断parameter是否为空,直接用就可以了
StringBuffer result = new StringBuffer();
for (String value : parameter) {
result.append(value).append(",");
}
result.deleteCharAt(result.length() - 1);
ps.setString(index, result.toString());
}
/**
* 讲"book,music"转化为数组对象
* @param columnValue
* @return
*/
private String[] getStringArray(String columnValue) {
if (columnValue == null) {
return null;
}
return columnValue.split(",");
}
}
3.Boolean和varchar的类型转换
需求场景:当数据库中保存'Y'/'N',而对应bean字段的值的类型为boolean,这是就需要我们自定义类型转换器,在Mybatis执行SQL得到结果时,通过自定义类型转换器将CHAR或者VARCHAR2类型转换为boolean类型,Java代码如下:
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class MyBooleanTypeHandler extends BaseTypeHandler<Boolean> {
private Boolean getBoolean(String flag){
Boolean bool = Boolean.FALSE;
if (flag.equalsIgnoreCase("Y")){
bool = Boolean.TRUE;
}
return bool;
}
@Override
public Boolean getNullableResult( ResultSet rs , String columnName ) throws SQLException {
return this.getBoolean(rs.getString(columnName));
}
@Override
public Boolean getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
return this.getBoolean(rs.getString(columnIndex));
}
@Override
public Boolean getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
return this.getBoolean(cs.getString(columnIndex));
}
@Override
public void setNonNullParameter( PreparedStatement ps , int index , Boolean parameter , JdbcType jdbcType )
throws SQLException {
String flag = parameter?"Y":"N";
ps.setString(index, flag);
}
}
<mapper namespace="com.shxt.model.Person">
<resultMap type="com.shxt.model.Person" id="BaseResultMapper">
<id column="id" property="id"/>
<!-- 使用自定义的转换 -->
<result typeHandler="com.shxt.type.MyListTypeHandler"
column="hobby" jdbcType="VARCHAR"
property="hobbyList" javaType="list"
/>
<result typeHandler="com.shxt.type.MyBooleanTypeHandler"
column="flag" jdbcType="VARCHAR"
property="flag" javaType="boolean"
/>
</resultMap>
<select id="load" parameterType="int" resultMap="BaseResultMapper">
SELECT id,hobby,flag FROM person WHERE id=#{id}
</select>
<insert id="add" parameterType="com.shxt.model.Person">
INSERT INTO person
(hobby,flag)
VALUES
(
#{hobbyList,
javaType=java.util.List,
jdbcType=VARCHAR,
typeHandler=com.shxt.type.MyListTypeHandler},
#{flag,typeHandler=com.shxt.type.MyBooleanTypeHandler}
)
</insert>
</mapper>
4.枚举类型转换器
public interface BaseEnum {
int getCode();
}
public enum ProcessStatus implements BaseEnum{
RUNNING(100, "running"),
BLOCKED(101, "blocked"),
STOPPED(102, "stopped");
private int code;
private String desc;
ProcessStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
然后再使用一个枚举工作类来完成从枚举 code 值获得枚举实例的工作:
public class EnumUtils {
public static <T extends Enum<?> & BaseEnum> T codeOf(Class<T> enumClass, int code) {
T[] enumConstants = enumClass.getEnumConstants();
for (T t : enumConstants) {
if (t.getCode() == code) {
return t;
}
}
return null;
}
}
接下来完成自定义枚举处理器:
import com.foo.BaseEnum;
import com.foo.EnumUtils;
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;
public class EnumCodeTypeHandler<E extends Enum<E> & BaseEnum> extends BaseTypeHandler<E> {
private final Class<E> type;
public EnumCodeTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
return cs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
}
}
在 mybatis-config.xml 中注册枚举处理器
<typeHandlers>
<typeHandler handler="com.foo.EnumCodeTypeHandler" javaType="com.foo.ProcessStatus" />
</typeHandlers>
假设数据库process表有如下记录:
| id | name | status |
|---|---|---|
| 1 | first | 101 |
ProcessMapper.java使用如下查询:
@Select("select * from process where id=#{id}")
public Process findById(int id);
查询结果 status 值将会对应到枚举实例 ProcessStatus.BLOCKED上。
查询结果Process{id=1, name='first', status=BLOCKED}
注意:设置默认枚举处理器
在 mybatis-config.xml 中为单个枚举注册枚举处理器的方式在需要处理的枚举数量增长时,会带来很多不必要的工作量,根据官方文档,我们可以在 configuration - settings节点下设置默认枚举处理器,没有特殊指定处理器的枚举都将默认使用这个处理器。
<settings>
<setting name="defaultEnumTypeHandler" value="com.foo.EnumCodeTypeHandler" />
</settings>