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 >
转自http://blog.csdn.net/solo_knight/article/details/7715683