使用iBatis的类型处理器TypeHandlerCallback

iBatis的扩展组件主要有TypeHandlerCallback、CacheController、DataSourceFactory、TransactionConfig。其中TypeHandlerCallback可以实现自定义的类型处理逻辑,以便处理非标准数据库、驱动程序和(或)数据类型。

 

场景举例

有如下数据表,请注意字段status的类型及含义:

 

CREATE DATABASE ibatis;
USE test_ibatis;
CREATE TABLE person
(
   id INT NOT NULL AUTO_INCREMENT,
   name VARCHAR(20) NOT NULL comment '姓名',
   status TINYINT NOT NULL comment '状态(1可用,-1不可用,0默认)',
   PRIMARY KEY (id)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8;

 

对应的实体模型类里将status定义为枚举型,如:

 

public class Person {

	private int id;
	private String name;
	private Status status; // 注意这里

	// getters and setters...
}

 

注:如果将status定义为整型,虽然与表中的字段类型一致了,但在赋值时setStatus(int status),只能传数字,不易被理解,没有人会明白应该传什么值,1或-1又代表了什么含义,且易产生脏数据,比如有意无意间会传321。

 

用枚举型给status赋值,既直观又限定范围。如:

 

......
Person person = new Person();
person.setName("yumin");
person.setStatus(Status.AVAILABLE);; // 注意这里
boolean result = DAO.insert(person);
.......

 

代码里出现的枚举型Status,可以这样进行定义:

 

public enum Status {
	// 默认0, 可用1, 不可用-1
	DEFAULT(0), AVAILABLE(1), UNAVAILABLE(-1);
}

 

但按上述做法执行写入操作时,发生了如下异常:

 

Caused by: java.sql.SQLException: 
Incorrect integer value: 'AVAILABLE' for column 'status' at row 1

 

类型不匹配,AVAILABLE用在了整型字段status。

 

异常出现的症结在于,数据表里的字段是用整型存储这没问题,但Java代码里应该用枚举型会更友好,两者间在数据类型上就存在一定的矛盾,即"javaType=enum"而"jdbcType=integer"。这时就需用到TypeHandlerCallback,通过实现该接口来处理中间的转换。

 

题外话:还有一种做法,定义Person类时,定义两个status,第一个status为整型,但取消setStatus方法,只负责在数据表存储时用,另一个叫status2为枚举型,负责与外部交互,赋值时只能用status2,内部将status2转换为status,这样既能保证友好又能通过转换保持类型的一致。但暂时让我们看看若通过iBatis的扩展应该如何实现。

 

实例演示

TypeHandlerCallback接口,下面是它的源代码:

 

public abstract interface TypeHandlerCallback {

      public abstract void setParameter(ParameterSetter setter, Object object) 
throws SQLException;

      public abstract Object getResult(ResultGetter getter) throws SQLException;

      public abstract Object valueOf(String string);
}

 

接下来我们一起Step-by-step操作 ——

 

第一步:基于TypeHandlerCallback实现类

 

/**
 * 
 */
package me.yumin.java.ibatis.handler;

import com.ibatis.sqlmap.client.extensions.ParameterSetter;
import com.ibatis.sqlmap.client.extensions.ResultGetter;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
import java.sql.SQLException;
import java.sql.Types;
import me.yumin.java.ibatis.enumtype.Status;

/**
 * @author yumin
 * 
 */
public class StatusHandler implements TypeHandlerCallback {

	@Override
	public void setParameter(ParameterSetter setter, Object parameter) 
throws SQLException {

		/*
		 * 在sqlMap中配parameterMap的属性typeHandler
		 * 和sqlMapConfig中配全局typeHandler时才会触发
		 */
		if (null == parameter) {
			setter.setNull(Types.INTEGER); // 若没有传值则null,表字段不允许则异常
		} else {
			setter.setInt(((Status) parameter).getValue());
		}
	}

	@Override
	public Object getResult(ResultGetter getter) throws SQLException {

		/*
		 * 仅在sqlMap中配置resultMap的属性typeHandler
		 * 和在sqlMapConfig中配全局typeHandler才会触发
		 */
		Object result = null;

		//if (!getter.wasNull() && null != getter.getObject()) { 
		// 上面有问题,修复如下.wasNull是判断前一个字段是否为null
		if (null != getter.getObject()) {
			result = getStatus(getter.getInt());
		}

		return result;
	}

	@Override
	public Object valueOf(String string) {

		/*
		 * 处理属性或参数为null情况
		 * 入参即为配置项nullValue
		 */
		Object result = null;
		int integer = 0;

		if (null != string && 0 < string.length()) {
			try {
				integer = Integer.parseInt(string);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}
		}
		result = getStatus(integer);

		return result;
	}

	/**
	 * 
	 * @param value
	 * @return
	 */
	private Object getStatus(int value) {

		Object result = null;

		for (Status status : Status.values()) {
			if (value == status.getValue()) {
				result = status;
				break;
			}
		}

		return result;
	}

}
 

第二步:在sqlMap中指定属性typeHandler

所有完整的sqlMap配置文件如下:

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Person">

	<typeAlias alias="Person" type="me.yumin.java.ibatis.domain.Person" />
	<parameterMap class="Person" id="person">
		<parameter property="name" />
		<parameter property="status" typeHandler="me.yumin.java.ibatis.handler.StatusHandler" />
		<!-- 若没有使用parameterMap且未给属性指定typeHandler
		     则在给SQL传值时不会调Handler中的setParameter方法 -->
	</parameterMap>
	<resultMap class="Person" id="person">
		<result property="id" column="id" />
		<result property="name" column="name" />
		<result property="status" column="status" typeHandler="me.yumin.java.ibatis.handler.StatusHandler" nullValue="0" />
		<!-- 同上若没用resultMap且未给属性指定typeHandler
		     则在返回表数据时不会调Handler中的getResult方法 -->
	</resultMap>

	<insert id="insert" parameterMap="person">
		<![CDATA[
			INSERT INTO person (name, status) VALUES (?, ?)
		]]>
		<selectKey keyProperty="id" resultClass="java.lang.Integer">
			SELECT LAST_INSERT_ID()
		</selectKey>
	</insert>

	<select id="query" parameterClass="java.lang.Integer" resultMap="person">
		<![CDATA[
			SELECT id, name, status FROM person WHERE id=#id#
		]]>
	</select>

</sqlMap>
 

主要处理过程是,因在sqlMap中给属性配置了typeHandler,所以在写或改数据前,先调用setParameter方法,再生成SQL,这时已将枚举型转换为整型;在读取数据后映射类时,会调用getResult方法,将整型转换为枚举型。

 

完整的示例请见附件,导入到IDE即可运行,IBatisTest是测试用例。

 

上述实例满足了一开始的需求,即Java类中将属性定义为枚举型,在存储时仍用整型,既友好直观地让使用者给类赋值又满足了存储需要。应用场景还有很多,如:对所有字符串型数据先压缩再存储,则可在sqlMapConfig中定义全局typeHandler,凡"javaType=java.lang.String"时,都通过该扩展在写之前先压缩,读取后先解压再返回。


若将Handler定义在sqlMapConfig,则该typeHandler是全局可用的。

 

全局typeHandler定义:

 

<sqlMapConfig>
	......
	<typeHandler javaType="package.Type" callback="package.Handler" />
	......
</sqlMapConfig>

 

你可能感兴趣的:(java,enum,ibatis)