mybatis 3的TypeHandler解析(null值的处理)
最近,在测试迁移公司的交易客户端连接到自主研发的中间件时,调用DAO层时,发现有些参数并没有传递,而在mapper里面是通过parameterMap传递的,因为有些参数为null,这就导致了参数传递到数据库的时候也是null,进而导致出错。因为我们公司的业务代码是通过类似一种模板的方式封装的,所以一开始调整了生成代码的模板,使得null在业务代码中一传入的时候进行判断赋值。可技术总监死都不同意,有些时候政治权力是很强大的,很多结果并不是因为最佳合理或者优化而选择,也并不总是成本最低的方案会被选择,这其中会涉及很多的因素,可能是面子问题、影响力等等,终归一句话就是必须在中间件处理掉,不能对现有的业务代码有任何的变动,很明智的我就从了。
因为所有的业务都在存储过程中处理,决定在DAO层进行处理,我们知道,mybatis在处理参数和返回值,对于特定类型都会选择特定的类型处理器以便进行恰当的转换,只不过在大部分的场景中,默认值的处理方式已经足够,所以真正生产中需要进行自定义类型处理的还真不多,通常更多的是为了处理兼容性或者适配性的问题,亦或是某些特殊的持久化实现需要对接。
默认情况下,mybatis提供了几乎所有内置类型的typehandler,在org.apache.ibatis.type包中,如下:

其中,TypeHandler接口是一个回调接口,所有的内置和自定义类型处理器均要实现该接口。
/**
* @author Clinton Begin
*/
public interface TypeHandler<T> { 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<T> extends TypeReference<T> implements TypeHandler<T> { 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<String> { @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<T> extends TypeReference<T> implements TypeHandler<T> { 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中。如下:


在这里,所有的参数值就都可以精确进行控制了。
mybatis 3的TypeHandler解析(null值的处理)的更多相关文章
- 使用fastjson,gson解析null值的时候键保留
由于业务需求...所以查阅资料,总结如下: 使用gson实现方法:只需要把new Gson()改为: new GsonBuilder().serializeNulls().create(); 就可以了 ...
- mybatis 3的TypeHandler深入解析(及null值的处理)
最近,在测试迁移公司的交易客户端连接到自主研发的中间件时,调用DAO层时,发现有些参数并没有传递,而在mapper里面是通过parameterMap传递的,因为有些参数为null,这就导致了参数传递到 ...
- MyBatis 返回类型resultType为map时的null值不返回问题
问题一: 查询结果集中 某字段 的值为null,在map中不包含该字段的key-value对 解决:在mybatis.xml中添加setting参数 <!-- 在null时也调用 sett ...
- Mybatis jpa mini 代码解析
源码地址(git):https://github.com/LittleNewbie/mybatis-jpa 一.Mybatis简介 mybatis中文官方文档:http://www.mybatis.o ...
- MyBatis启动之XMLConfigBuilder解析配置文件(二)
前言 XMLConfigBuilder 是BaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中.本文 ...
- 深入浅出Mybatis系列五-TypeHandler简介及配置(mybatis源码篇)
注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliase ...
- (一) Mybatis源码分析-解析器模块
Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...
- mybatis源码配置文件解析之二:解析settings标签
在前边的博客中分析了mybatis解析properties标签,<mybatis源码配置文件解析之一:解析properties标签>.下面来看解析settings标签的过程. 一.概述 在 ...
- mybatis源码配置文件解析之三:解析typeAliases标签
在前边的博客在分析了mybatis解析settings标签,<mybatis源码配置文件解析之二:解析settings标签>.下面来看解析typeAliases标签的过程. 一.概述 在m ...
随机推荐
- 无法启动此程序,因为计算机中丢失AdbWinApi.dll。尝试重新安装该程序以解决此问题
第一次搭建android开发环境,装完adb以后,打开DOS验证安装是否成功:但输入adb logcat调试时,系统弹出以下异常的对话框: 无法启动此程序,因为计算机中丢失AdbWinApi.dll. ...
- js算法之最常用的排序
引入 大学学习计算机语言的那几年,从c语言,到c++,再到数据结构JAVA..让我印象最深刻的还是最开始老师讲冒泡算法的时候,直到现在大四快毕业了我才渐渐通窍了.刚学前端的时候以为前端就是做出好看很炫 ...
- Bootstrap排版中地址与引用详解
地址元素address 我们的地址在HTML5中增加了一个address标签,可以把我们的地址写在address标签里面,address里面强调换行等等都是可以的. 实例: <address&g ...
- KendoUI系列:Window
1.基本使用 <link href="@Url.Content("~/Content/kendo/2014.1.318/kendo.common.min.css") ...
- 【WP开发】使用磁倾仪
磁倾仪,也叫倾斜仪,主要用来检测手机设备在各个轴上旋转的角度.注意,磁倾仪与陀螺仪的差异,陀螺仪的关注点是旋转的角速度,它并不关注角度,只注重速度.而磁倾仪的读数就是设备倾斜的角度. 不管是使用重力感 ...
- Mysql存储过程语法
一口气弄完了! 一.条件语句if-then-else: create procedure demo_1(in param int) begin declare var int; ; then inse ...
- Deep learning:四十三(用Hessian Free方法训练Deep Network)
目前,深度网络(Deep Nets)权值训练的主流方法还是梯度下降法(结合BP算法),当然在此之前可以用无监督的方法(比如说RBM,Autoencoder)来预训练参数的权值,而梯度下降法应用在深度网 ...
- offsetTop、clientTop、scrollTop、offsetTop
好好看看下面那张图,基本上就没啥问题了! scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop ...
- 最简明的JavaScript闭包解释
最简明的JavaScript闭包解释 JavaScript是这几年最火的编程语言之一,从前端到服务器端,再到脚本,好像没有一个地方没有JavaScript的身影.这个世界上任何的一种事物的存在必然有其 ...
- [New Portal]Windows Azure Virtual Machine (22) 使用Azure PowerShell,设置Virtual Machine Endpoint
<Windows Azure Platform 系列文章目录> 我们可以通过Windows Azure Management Portal,打开Virtual Machine的Endpoi ...