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 ...
随机推荐
- cmder设置打开时的默认目录
cmder设置打开时的默认目录 打开cmder自动进入工作目录,怎么配置? http://superuser.com/questions/1005285/run-a-bat-file-with-cmd ...
- 快速入门系列--WCF--02消息、会话与服务寄宿
经过WCF基础的ABC学习,已经可以构建简单的WCF的服务,使用不同的服务地址和绑定类型,根据业务提供所需的服务契约.但不禁想问,服务所使用的消息报文是什么样的形式么?蕴含什么样内容呢?WCF服务是否 ...
- iBatis + SQL Server 项目开发实战小结
几年前跟随项目经理做的一个ERP小项目,自己业余时间整理的开发手册,供参考. 开发环境配置:编程环境为Microsoft Visual Studio 2010,数据库是SQL Server 2008 ...
- Android入门(九)文件存储与SharedPreferences存储
原文链接:http://www.orlion.ga/578/ Android系统中主要提供了三种方式用于简单地实现数据持久化功能,即文件存储.SharedPreference存储以及数据库存储.当然, ...
- poj1006生理周期(中国剩余定理)
/* 中国剩余定理可以描述为: 若某数x分别被d1..….dn除得的余数为r1.r2.….rn,则可表示为下式: x=R1r1+R2r2+…+Rnrn+RD 其中R1是d2.d3.….dn的公倍数,而 ...
- tomcat对请求路径的匹配过程(原创)
1.匹配服务 如果有两个应用,一个应用只能通过80端口访问,另一个应用只能通过8080端口访问,这种情况下,可以分开两个服务,然后分别创建80端口和8080端口的连接器. 2.匹配主机 一个服务下配置 ...
- [Qt5] How to connect c++ with QML
Qt5处于过度阶段,架构繁琐,学习成本不低.尤其是UI代码竟然被重写,变了天. Qt中的c++可能是连接OPENCV与QML的一个不错的桥梁,在此学习这部分实用的技术. Reference: http ...
- 【转】 制作Android Demo GIF:程序演示效果GIF图录制
在平时写博客或者分享自己写的程序效果的时候经常需要做成GIF图,以下就是介绍几种常用的GIF录制方法: 一.录制工具 1.(生成动画的工具:Ulead GIF Animator),可以讲单独的图片生成 ...
- Android中动态更新ListView(转)
在使用ListView时,会遇到当ListView列表滑动到最底端时,添加新的列表项的问题,本文通过代码演示如何动态的添加新的列表项到ListView中.实现步骤:调用ListView的setOnSc ...
- 一行代码,让你的应用中UIScrollView的滑动与侧滑返回并存
侧滑返回是iOS系统的一个很贴心的功能,特别是在大屏手机上,单手操作的时候去按左上角的返回键特别不方便.当我在使用一个APP的时候,如果控制器不能侧滑返回,我会觉得这个APP十分不友好...这款产品在 ...