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 ...
随机推荐
- ViewPager做图片浏览器,加载大量图片OOM的问题修正
/** * @author CHQ * @version 1.0 * @date 创建时间: 2016/7/26 17:18 * @parameter * @return * 图片查看器 * //可以 ...
- JavaScript内存优化
JavaScript内存优化 相对C/C++ 而言,我们所用的JavaScript 在内存这一方面的处理已经让我们在开发中更注重业务逻辑的编写.但是随着业务的不断复杂化,单页面应用.移动HTML5 应 ...
- WPF combobox
先写一个数据类Grade.cs using System; using System.Collections.Generic; using System.Linq; using System.Text ...
- hibernate(九)多对多关联
原文链接:http://www.orlion.ml/29/ 一.多对多单向关联 假设一个老师教多个学生,一个学生被多个老师教,这就是典型的多对多关系 配置方式是在Teacher类的getStudent ...
- 替换变量&和&&
替换变量可看作是占位符 一. 单个&符号替换 SQL语句中最基本.最普遍元素替换形式是单个&符号替换.&符号是用来在语句中指定替换变量的符号.当执行语句时,Oracle服务器处 ...
- JavaScript作用域原理(二)——预编译
JavaScript是一种脚本语言, 它的执行过程, 是一种翻译执行的过程.并且JavaScript是有预编译过程的,在执行每一段脚本代码之前, 都会首先处理var关键字和function定义式(函数 ...
- Spring MVC 学习总结(六)——Spring+Spring MVC+MyBatis框架集成
与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管理,Spring MVC负责请求的转发和 ...
- MySQL ibdata1文件迁移
目的:主机系统/var目录快满了,经查询最大的文件是mysql的ibdata1文件,有17G大小,故需要迁移这个文件到其他目录下,以释放/var目录空间. 1.先备份下数据库是个好习惯 # mysql ...
- [Basic] The most basic things about java
[Basic] The most basic things about java // */ // ]]> [Basic] The most basic things about java ...
- JAVA 设计模式 命令模式
用途 命令模式 (Command) 将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化:对请求排队或请求日志,以及支持可撤销的操作. 命令模式是一种行为型模式. 结构