最近,在测试迁移公司的交易客户端连接到自主研发的中间件时,调用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值的处理)的更多相关文章

  1. 使用fastjson,gson解析null值的时候键保留

    由于业务需求...所以查阅资料,总结如下: 使用gson实现方法:只需要把new Gson()改为: new GsonBuilder().serializeNulls().create(); 就可以了 ...

  2. mybatis 3的TypeHandler深入解析(及null值的处理)

    最近,在测试迁移公司的交易客户端连接到自主研发的中间件时,调用DAO层时,发现有些参数并没有传递,而在mapper里面是通过parameterMap传递的,因为有些参数为null,这就导致了参数传递到 ...

  3. MyBatis 返回类型resultType为map时的null值不返回问题

    问题一:    查询结果集中 某字段 的值为null,在map中不包含该字段的key-value对 解决:在mybatis.xml中添加setting参数 <!-- 在null时也调用 sett ...

  4. Mybatis jpa mini 代码解析

    源码地址(git):https://github.com/LittleNewbie/mybatis-jpa 一.Mybatis简介 mybatis中文官方文档:http://www.mybatis.o ...

  5. MyBatis启动之XMLConfigBuilder解析配置文件(二)

    前言 XMLConfigBuilder 是BaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中.本文 ...

  6. 深入浅出Mybatis系列五-TypeHandler简介及配置(mybatis源码篇)

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliase ...

  7. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  8. mybatis源码配置文件解析之二:解析settings标签

    在前边的博客中分析了mybatis解析properties标签,<mybatis源码配置文件解析之一:解析properties标签>.下面来看解析settings标签的过程. 一.概述 在 ...

  9. mybatis源码配置文件解析之三:解析typeAliases标签

    在前边的博客在分析了mybatis解析settings标签,<mybatis源码配置文件解析之二:解析settings标签>.下面来看解析typeAliases标签的过程. 一.概述 在m ...

随机推荐

  1. Java EE开发平台随手记6——Mybatis扩展4

    这篇博客中来说一下对Mybatis动态代理接口方式的扩展,对于Mybatis动态代理接口不熟悉的朋友,可以参考前一篇博客,或者研读Mybatis源码. 扩展11:动态代理接口扩展 我们知道,真正在My ...

  2. Python数据类型之“文本序列(Text Sequence)”

    Python中的文本序列类型 Python中的文本数据由str对象或字符串进行处理. 1.字符串 字符串是Unicode码值的不可变序列.字符串字面量有多种形式: 单引号:'允许嵌入"双&q ...

  3. vs xamarin android 读取rest

    private void Btn_Click(object sender, EventArgs e) { var u = FindViewById<EditText>(Resource.I ...

  4. Prim算法(一)之 C语言详解

    本章介绍普里姆算法.和以往一样,本文会先对普里姆算法的理论论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 目录 1. 普里姆算法介绍 2. 普里姆算法图解 3. 普里 ...

  5. tomcat对请求路径的匹配过程(原创)

    1.匹配服务 如果有两个应用,一个应用只能通过80端口访问,另一个应用只能通过8080端口访问,这种情况下,可以分开两个服务,然后分别创建80端口和8080端口的连接器. 2.匹配主机 一个服务下配置 ...

  6. Android基于mAppWidget实现手绘地图(三)--环境搭建

    首先,你在Eclispe开发环境中新建一个项目:然后,可以通过Eclispe项目管理工具把这个项目集成到你的app项目中.下面手册会手把手教你如何正确创建一个地图对象. 第一: 新建一个新的Anroi ...

  7. Deep learning:五十(Deconvolution Network简单理解)

    深度网络结构是由多个单层网络叠加而成的,而常见的单层网络按照编码解码情况可以分为下面3类: 既有encoder部分也有decoder部分:比如常见的RBM系列(由RBM可构成的DBM, DBN等),a ...

  8. 如何用Ajax加载服务器的图片

    用Ajax请求服务器的图片,并显示在浏览器中 前言 一直在数据库里面存的都是图片在服务器的地址,然后再到浏览器中显示,但是发现两个问题 第一:为了安全起见,js是无法读取本地的图片的,不然你写一个js ...

  9. 解决AndroidADT自带Eclipse编辑器不能自动代码提示的问题。

    今天发现,我下载的AndroidADT开发套装中自带的Eclipse没有自动代码提示功能.通过参考http://blog.csdn.net/coolszy/article/details/724195 ...

  10. SQL Server如何在变长列上存储索引

    这篇文章我想谈下SQL Server如何在变长列上存储索引.首先我们创建一个包含变长列的表,在上面定义主键,即在上面定义了聚集索引,然后往里面插入80000条记录: -- Create a new t ...