对mybatis的Handler 从使用角度介绍
最近在开发中,涉及到了讲数据库查询的类型,直接转为java需要的类型。 由于对handler 理解不到位 和 使用不当。躺了一些坑。
主要涉及的有2种。
1、varchar 转 List<T>
2、varchar 转Map<T>
如图是写的两个handler。
ListTypeHandler 为了保证 handler的通用性,采取了 将mybatis xml 配置中的type,传入 ListTypeHandler 中直接使用。 后来发现这是个大坑。
public class ListTypeHandler<E> extends BaseTypeHandler<List<E>> { private Class<E> type; public ListTypeHandler(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, List<E> parameter, JdbcType jdbcType) throws SQLException { String x = JSON.toJSONString(parameter); ps.setString(i, x); } @Override public List<E> getNullableResult(ResultSet rs, String columnName) throws SQLException { String s = rs.getString(columnName); return StringUtils.isBlank(s) ? null : JSON.parseArray(s, type); } @Override public List<E> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String s = rs.getString(columnIndex); return StringUtils.isBlank(s) ? null : JSON.parseArray(s, type); } @Override public List<E> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String s = cs.getString(columnIndex); return StringUtils.isBlank(s) ? null : JSON.parseArray(s, type); } }
public class MapTypeHandler extends BaseTypeHandler<Map<String, Object>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException { if(MapUtils.isNotEmpty(parameter)) { String x = JSON.toJSONString(parameter); ps.setString(i, x); }else{ ps.setString(i, null); } } @Override public Map<String, Object> getNullableResult(ResultSet rs, String columnName) throws SQLException { String s = rs.getString(columnName); return StringUtils.isBlank(s) ? null : JSON.parseObject(s, new TypeReference<Map<String, Object>>(){}); // JSON.parseObject(s, getRawType()); } @Override public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String s = rs.getString(columnIndex); return StringUtils.isBlank(s) ? null : JSON.parseObject(s, new TypeReference<Map<String, Object>>(){}); } @Override public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String s = cs.getString(columnIndex); return StringUtils.isBlank(s) ? null : JSON.parseObject(s, new TypeReference<Map<String, Object>>(){}); } }
首先没有在mybatis.xml 总注册,直接通过@result指定使用
@Select("<script>SELECT " + SELECT_FILEDS_AS + " FROM " + TABLE + " WHERE valid =1 and config_id = #{configId} " + SELECT_LIMIT + " </script>") @Results(value={ @Result(column="operation_content", property="operationContent", javaType=String.class, jdbcType=JdbcType.VARCHAR,typeHandler=ListTypeHandler.class), }) List<OperateRecord> selectList(@Param("configId") long configId,@Param("offset") int offset,@Param("limit") int limit);
由于希望 ListTypeHandler 可以通用一点,因此采用了传入的javaType 即String,使用, 这个时候查询也没问题。感觉还很完美。
接下来,开始写插入
@Insert({"INSERT INTO " + TABLE + " (config_id, operation_staff, operation_time, action, operation_content, ctime,valid) values " +"( #{configId}, #{operationStaff}, #{operationTime}, #{action}, #{operationContent, javaType=String, jdbcType=VARCHAR,typeHandler=ListTypeHandler}, unix_timestamp(),1 )"}) int insert(OperateRecord record);
这个时候就开始各种报错,最后经过一顿百度,发现是没有注册,于是增加注册。
<typeHandlers> <typeHandler handler="com.support.ListTypeHandler" javaType="List" jdbcType="VARCHAR" /> </typeHandlers>
这个时候,在插入,发现成功了。 这个时候,认为全部搞定了,开始部署线下,进行联调。结果最大的问题才刚刚开始。
后头测试查询的时候,发现查询又报错。。。 于是开始debug,发现 'name' 类型varchar,java类型 String。 也开始进入list转换。 一脸懵逼。
于是尝试性的去掉字段的别名,发现居然成功了。。。
然后开始进入另一个大坑。认为和别名有关。
进入下一步的尝试
@Result(column = "initiateName" ,property = "initiateName")
为字段指定result,发现问题完美解决。
这个时候,任务只要@Results(value={}),使用过handler的,其他字段就也要指定。
这个时候,终于认为大功告成,结果。。。。
测试另一个dao的时候,发现,这个dao完全没有使用handler, 查询结果中的字段,也会去转换。。
然后,又开始懵逼。。。 一顿百度,最终恍然大悟。 再mybatisxml配置之后,是全局匹配。
因为不能使用传入的java类型, 再listhandler写死。
结论:
1.查询时候,不需要注册,即可直接通过result使用。type指定也可以生效。
2.插入的时候必须注册。
3.注册后,通过java,jdbc类型,还有名字全局匹配
4.result中可以不写,自动匹配