博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mybatis 3的TypeHandler深入解析(及null值的处理)
阅读量:6200 次
发布时间:2019-06-21

本文共 9209 字,大约阅读时间需要 30 分钟。

最近,在测试迁移公司的交易客户端连接到自主研发的中间件时,调用DAO层时,发现有些参数并没有传递,而在mapper里面是通过parameterMap传递的,因为有些参数为null,这就导致了参数传递到数据库的时候也是null,进而导致出错。因为我们公司的业务代码是通过类似一种模板的方式封装的,所以一开始调整了生成代码的模板,使得null在业务代码中一传入的时候进行判断赋值。可技术总监死都不同意,有些时候政治权力是很强大的,很多结果并不是因为最佳合理或者优化而选择,也并不总是成本最低的方案会被选择,这其中会涉及很多的因素,可能是面子问题、影响力等等,终归一句话就是必须在中间件处理掉,不能对现有的业务代码有任何的变动,很明智的我就从了。

因为所有的业务都在存储过程中处理,决定在DAO层进行处理,我们知道,mybatis在处理参数和返回值,对于特定类型都会选择特定的类型处理器以便进行恰当的转换,只不过在大部分的场景中,默认值的处理方式已经足够,所以真正生产中需要进行自定义类型处理的还真不多,通常更多的是为了处理兼容性或者适配性的问题,亦或是某些特殊的持久化实现需要对接。

默认情况下,mybatis提供了几乎所有内置类型的typehandler,在org.apache.ibatis.type包中,如下:

其中,TypeHandler接口是一个回调接口,所有的内置和自定义类型处理器均要实现该接口。

/** * @author Clinton Begin */public interface TypeHandler
{ void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException;}

和其他框架如spring/netty等一样,mybatis也提供了绝大部分常见场景的默认实现以及一个模板类BaseTypeHandler(一般用户自定义的时候都应该继承或者重新实现该类)。

/* *    Copyright 2009-2012 the original author or authors. * *    Licensed under the Apache License, Version 2.0 (the "License"); *    you may not use this file except in compliance with the License. *    You may obtain a copy of the License at * *       http://www.apache.org/licenses/LICENSE-2.0 * *    Unless required by applicable law or agreed to in writing, software *    distributed under the License is distributed on an "AS IS" BASIS, *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *    See the License for the specific language governing permissions and *    limitations under the License. */package org.apache.ibatis.type;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import org.apache.ibatis.session.Configuration;/** * @author Clinton Begin * @author Simone Tripodi */public abstract class BaseTypeHandler
extends TypeReference
implements TypeHandler
{ protected Configuration configuration; public void setConfiguration(Configuration c) { this.configuration = c; } public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { setNonNullParameter(ps, i, parameter, jdbcType); } } public T getResult(ResultSet rs, String columnName) throws SQLException { T result = getNullableResult(rs, columnName); if (rs.wasNull()) { return null; } else { return result; } } public T getResult(ResultSet rs, int columnIndex) throws SQLException { T result = getNullableResult(rs, columnIndex); if (rs.wasNull()) { return null; } else { return result; } } public T getResult(CallableStatement cs, int columnIndex) throws SQLException { T result = getNullableResult(cs, columnIndex); if (cs.wasNull()) { return null; } else { return result; } } public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;}

 

下面,来简单介绍下自定义TypeHandler。总的来说,为特定的jdbcType/javaType选择TypeHandler时,有两个层面:

  • 字段实例级别。也就是在特定的parameter上设置,如<parameter property="operator_company_no" jdbcType="INTEGER" typeHandler="com.ld.net.typehandler.LdIntegerHandler" mode="IN"/>
  • 类型级别。在mybatis-config.xml中configuration->typeHandlers下设置:

<configuration>

   <typeHandlers>

      <typeHandler jdbcType="VARCHAR" handler="com.ld.net.core.typehandler.LdStringTypeHandler"/>

   </typeHandlers>

</typeHandlers>

     这两种场景其实都是有的,前者主要用于一些特殊类型字段的处理,比如clob/json类型等等。后者一般用于框架层面居多。

     实现LdStringTypeHandler一般来说继承BaseTypeHandler或者内置的具体实现比如StringTypeHandler。

/* *    Copyright 2009-2012 the original author or authors. * *    Licensed under the Apache License, Version 2.0 (the "License"); *    you may not use this file except in compliance with the License. *    You may obtain a copy of the License at * *       http://www.apache.org/licenses/LICENSE-2.0 * *    Unless required by applicable law or agreed to in writing, software *    distributed under the License is distributed on an "AS IS" BASIS, *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *    See the License for the specific language governing permissions and *    limitations under the License. */package org.apache.ibatis.type;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;/** * @author Clinton Begin */public class StringTypeHandler extends BaseTypeHandler
{ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); }}

   内置的默认实现仅实现了setNonNullParameter并没有setParameter,所以,如果我们需要为null赋默认值的话,则需要实现setParameter方法,例如:

package com.ld.net.core.typehandler;import java.sql.PreparedStatement;import java.sql.SQLException;import org.apache.commons.lang.StringUtils;import org.apache.ibatis.type.JdbcType;import org.apache.ibatis.type.StringTypeHandler;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class LdStringTypeHandler extends StringTypeHandler{  static Logger logger = LoggerFactory.getLogger(LdStringTypeHandler.class);  public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException  {    logger.debug(getClass().getCanonicalName() + ".setParameter");    ps.setString(i, StringUtils.isEmpty(parameter) ? " " : parameter);  }}

 

一般来说,这样配置好之后,理论上就可以了,但是测试的时候,笔者发现当参数为null的时候,mybatis死活没有调用设置的LdStringTypeHandler,而是进入了内置的BaseTypeHandler。测试了N次,参数不为null的时候,自定义的typeHander都是有效的。所以最后选择了从mybatis源码拉出BaseTypeHandler,覆盖实现为如下:

 

import java.math.BigDecimal;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import org.apache.commons.lang.StringUtils;import org.apache.ibatis.session.Configuration;/** * @author Clinton Begin * @author Simone Tripodi * @author zhjh256@163.com */public abstract class BaseTypeHandler
extends TypeReference
implements TypeHandler
{ protected Configuration configuration; public void setConfiguration(Configuration c) { this.configuration = c; } public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { switch(jdbcType) { case INTEGER: ps.setInt(i, (Integer) (parameter == null ? 0 : parameter)); break; case BIGINT: ps.setLong(i, (Long) (parameter == null ? 0L : parameter)); break; case DECIMAL: case NUMERIC: case DOUBLE: case FLOAT: ps.setBigDecimal(i, (BigDecimal) (parameter == null ? new BigDecimal("0.0") : parameter)); break; case VARCHAR: case NVARCHAR: ps.setString(i, (String) (StringUtils.isEmpty((String) parameter) ? " " : parameter)); break; default: ps.setNull(i, jdbcType.TYPE_CODE); break; } } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { setNonNullParameter(ps, i, parameter, jdbcType); } }

我们只是覆盖了我们规定的类型,其他的可以自定决定。并删除了mybatis-config.xml中的TypeHandler之后,参数已经如期的传递了默认值。

跟踪期间发现,如果需要根据参数名进行判断,也是可以做到的,只不过不在TypeHandler中,而是在org.apache.ibatis.scripting.defaults.DefaultParameterHandler中。如下:

在这里,所有的参数值就都可以精确进行控制了。

转载地址:http://tdtca.baihongyu.com/

你可能感兴趣的文章
部署Thomas Kyte 的 runstats 工具
查看>>
STM32 ~ 查看系统时钟
查看>>
Django基础之response对象
查看>>
/bin/ls: Permission denied
查看>>
痞子衡嵌入式:ARM Cortex-M内核那些事(3)- 功能模块
查看>>
洛谷——P2813 母舰
查看>>
Ubuntu中搭建Hadoop2.5.2完全分布式系统(一)
查看>>
mySagasoft MIS 架构 (五)
查看>>
打印沙漏
查看>>
(转)Android 命令行手动编译打包详解
查看>>
odoo - context
查看>>
谷哥镜像
查看>>
图像分类中max-pooling和average-pooling之间的异同
查看>>
Android中Json数据读取与创建的方法
查看>>
PHP 高精度计算
查看>>
ios中封装九宫格的使用(二级导航)
查看>>
后台验证及struts2表单验证里field-validator type值的含义?
查看>>
Swift 4.0 中的 open,public,internal,fileprivate,private
查看>>
安装anaconda遇到的问题
查看>>
程序员的情人节故事
查看>>