在JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQL语句参数。 执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些MyBatis是根据数据的类型通过typeHandler来实现的。
在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,javaType用于定义Java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换。
默认的TypeHandler如下:
在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;
}
通过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一般不会使用代码注册,而是通过配置或扫描。
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会自动扫描所配置的包名下所有类型转换器,并进行注册。
该类型转换器继承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注解中即可。
/**
* 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"标明即可,如下:
其中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属性配置靠前即可。