Mybatis类型转换器(typeHandler)

1.简介

      在JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQL语句参数。 执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些MyBatis是根据数据的类型通过typeHandler来实现的。
     在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,javaType用于定义Java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换。
     默认的TypeHandler如下:
Mybatis类型转换器(typeHandler)_第1张图片

2.使用

2.1基础接口

    在MyBatis中typeHandler都要实现接口org.apache.ibatis.type.TypeHandler。

// T是泛型,专指javaType
public interface TypeHandler<T> {
    // 通过PreparedStatement对象进行设置SQL参数 
    // i是参数在SQL的下标
    // parameter是参数
    // jdbcType是数据库类型
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    // 从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据
    T getResult(ResultSet var1, String var2) throws SQLException;
    T getResult(ResultSet var1, int var2) throws SQLException;
    // 存储过程专用的
    T getResult(CallableStatement var1, int var2) throws SQLException;
}

2.2类型处理器注册

    通过org.apache.ibatis.type.TypeHandlerRegistry类对象的register方法进行注册。

public TypeHandlerRegistry() {
    this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
    this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
    ...
}

    这样就实现了自带的typeHanlder注册。
自定义的typeHandler一般不会使用代码注册,而是通过配置或扫描。

2.3.Spring boot中配置类型转换器

mybatis:
  # 配置映射类所在包名
  type-aliases-package: com.supconit.suptap.ai.control.domain,
                        com.supconit.suptap.common.typehandler.alias,
                        com.supconit.suptap.common.domain.aicontrol
  # 枚举处理类
type-handlers-package: com.supconit.suptap.common.typehandler.handler

Spring boot会自动扫描所配置的包名下所有类型转换器,并进行注册。

2.4.自定义类型转换器

2.4.1.枚举类型转换器

      该类型转换器继承Mybatis自带的EnumOrdinalTypeHandler类,保存枚举的序号ordinal(从0开始,0,1,2,3…)到数据库中,相对,从数据库中取出序号转成枚举类。

/**
 * 枚举处理类
 * @author JWF
 * @date 2019/6/5
 * @param 
 */
@MappedTypes(value = {
        Control.class,
        CoordinateModeEnum.class
})
public class EnumTypeHandler<E extends Enum<E>> extends EnumOrdinalTypeHandler<E> {
    private Class<E> type;

    public EnumTypeHandler(Class<E> type) {
        super(type);
    }
}

        注解@MappedTypes表示Control、CoordinateModeEnum枚举类在mybatis中会自动使用自定义的EnumTypeHandler处理器进行处理,无需在每个需要使用的mapper.xml中单独配置处理器,如下:

#{item.alarmGrade, typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler},

同理其他的枚举类只要加在@MappedTypes注解中即可。

2.4.2.List类型处理器

2.4.2.1.定义映射类

Mybatis类型转换器(typeHandler)_第2张图片Mybatis类型转换器(typeHandler)_第3张图片

2.4.2.2定义ListTypeHandler处理器

/**
 * List TypeHandler
 * @author JWF
 * @date 2019/6/28
 */
@MappedTypes(value = {
        BooleanListAlias.class,
        DoubleListAlias.class,
        FloatListAlias.class,
        IntegerListAlias.class,
        StringListAlias.class
})
public class ListTypeHandler extends BaseTypeHandler<List<?>> {
    private static final String LIST_SPLIT_FLAG = ",";
    private static final String ALIAS = "alias";
    private final Class<?> clazz;
    private static List<Class<?>> clazzs = new ArrayList<>();
            //{Integer.class,String.class,Double.class,Float.class,Boolean.class};

    static {
        MappedTypes mappedTypes = ListTypeHandler.class.getAnnotation(MappedTypes.class);
        if(mappedTypes != null){
            for (Class<?> clz : mappedTypes.value()) {
                try {
                    Field field = clz.getDeclaredField(ALIAS);
                    clazzs.add(field.getType());
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public ListTypeHandler(Class<?> type) throws NoSuchFieldException {
         if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        Field declaredField = type.getDeclaredField(ALIAS);

        clazz = declaredField.getType();
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<?> parameter, JdbcType jdbcType) throws SQLException {
        List list = parameter;
        String rs = null;
        if(!CollectionUtils.isEmpty(list)){
            rs = Joiner.on(LIST_SPLIT_FLAG).join(list);
        }
        ps.setString(i,rs);
    }

    @Override
    public List<?> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        if (rs.wasNull()) {
            return new ArrayList<>();
        }
        String res = rs.getString(columnName);
        try {
            return getList(res);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column #" + columnName + " from result list" , e);
        }
    }

    private List<?> getList(String res) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if(StringUtils.isEmpty(res)){
            return new ArrayList<>();
        }
        List list = new ArrayList();
        for(Class<?> cl : clazzs){
            if(cl.isAssignableFrom(clazz)){
                Class<?> arg = String.class;
                if(cl.isAssignableFrom(String.class)){
                    arg = Object.class;
                }
                Method valueOf = cl.getMethod("valueOf", arg);
                for(String s : res.split(LIST_SPLIT_FLAG)){
                    list.add(valueOf.invoke(cl,s));
                }
            }
        }
        return list;
    }

    @Override
    public List<?> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        if (rs.wasNull()) {
            return new ArrayList<>();
        }
        String res = rs.getString(columnIndex);
        try {
            return getList(res);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column #" + columnIndex + " from result list" , e);
        }
    }

    @Override
    public List<?> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        if (cs.wasNull()) {
            return new ArrayList<>();
        }
        String res = cs.getString(columnIndex);
        try {
            return getList(res);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column #" + columnIndex + " from result list" , e);
        }
    }
}

        其中@ MappedTypes注解定义了BooleanListAlias.class, DoubleListAlias.class, FloatListAlias.class, IntegerListAlias.class, StringListAlias.class五钟类型映射类。
这样只需在mapper.xml中要用到List类型处理器的地方使用javaType=“IntegerListAlias"标明即可,如下:
Mybatis类型转换器(typeHandler)_第4张图片
        其中weeks属性为List weeks,Mybatis转换的时候回自动使用ListTypeHandler进行处理。
        同理,若为List< Boolean>类型,则用javaType=” BooleanListAlias"标明即可,其他情况以此类推。

        顺便说一下,也可自定义类的List处理器,可仿照转成string在数据库中保存,取出时转成对象即可。

注意

        在处理List<>类型的字段时,在mybatis的xml配置文件中,如果配置所在typeHandler的result前一个result值为空,则typeHandler处理的字段即使数据库中有值,也不会正确获取到值。
        如上图,weeks属性为List类型,如果前一个result中start_minute的数值为空,则weeks值也为空,即使weeks数据库中有值也一样为空。

条件:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GreenTimeSplit {
 @JsonIgnore
    private Integer id;
    private Integer a;
    private List<Integer> b;
}

如果在resultMap配置中,b前一个为a,如果a为空,则b也为空。
解决办法: 在resultMap中,将b属性配置靠前即可。

你可能感兴趣的:(mybatis,mybatis)