自定义配置:typeHandlers和objectFactory

自定义配置

MyBatis最核心的全局配置文件中,给出了许多功能配置项,最近在一直看typeHandler和objectFactory这两个配置项,从名字就可以看出,typeHandler是“类型处理器”,objectFactory是“对象工厂”,现在就来给它们两个做一篇总结日志,为什么把这两个配置项放在一起总结?因为它们都有相似的特点,就是可以对数据进行自定义操作。让我想起当初的自定义线程池和自定义线程的创建,自定义线程池中,我们可以重写ThreadPoolExecutor类里面的三个方法,分别让线程池在执行任务前,完成任务后和线程池退出过程中,做点别的事情,例如输出一些变量,作为调试信息。而自定义线程,就像上面的objectFactory一样,线程同样有个线程工厂,ThreadFactory接口,里面有一个newThread(Runnable r)方法,每当创建线程时都会调用这个方法,自定义线程就是通过重写这个方法,可以让线程在创建时,输出一下自己的id等信息,下面你会看到,objectFactory也是如此,它可以让你在创建对象时,自定义一些执行动作。

 

typeHandlers配置文件

typeHandlers,它是干什么用的呢?从名字直译来看,类型处理器,大概猜到它是对数据类型进行处理,没错,typeHandlers的作用就是用来做“类型转换”的。在SQL映射配置文件里,有时我们会为SQL语句传入参数,参数类型可能是一些基本数据类型如int型、String型,或Date型,也有可能是用户自定义包装类型,所以在执行SQL操作时,需要把传入的参数,即Java类型的数据,转换成数据库对应的类型,这样才能完成增删查改等操作。同样,在数据库操作完成后,返回的结果集中的数据,也要从数据库的数据类型转换成Java类型数据,这样才能完成数据的映射,最后取出数据。上述的由Java数据类型到数据库数据类型之间的相互转换工作,就是由typeHandlers来完成。

MyBatis中有很多系统定义的typeHandler,会根据javaType和jdbcType来自行决定使用哪个类型处理器来处理这些数据的转换,这就是为什么前面我们的SQL映射配置文件中,不需要显式地配置如String类型和VARCHAR类型的转换一样,因为MyBatis会自动帮我们完成这些常用的数据转换。我们也可以自定义类型处理器,来自己实现一些功能更丰富的类型转换,例如这样一个场景,在JavaBean(持久化实体类)中,我们的出生日期字段是Date类型,但是在数据库中,存放日期的是VARCHAR类型,那么在插入或更新操作时,就要把Date类型转换为VARCHAR类型,查询获得的结果集也要把VARCHAR类型转换为Date类型才能完成映射,下面就来简单看看如何编写一个自定义typeHandler来完成VARCHAR数据类型和Date数据类型的转换吧,第一步,就是编写这个类型处理器的逻辑处理。

 

编写类型处理器

编写自定义类型处理器,通常是继承BaseTypeHandler类,它是一个标准基础类型处理器类,类的泛型就是指定要转换的Java类型,继承这个类后,就可以通过重写里面的方法,来自定义对数据转换的处理,就像自定义线程那样,我们也是通过重写线程工厂中的创建线程方法,newThread(Runnable r);在创建线程时可以做更多的事情。那好,根据上面的情景,在JavaBean中的出生日期字段是Date类型,而数据库中对应的出生日期字段却是VARCHAR类型,编写处理这一数据转换的typeHandler。

package com.mybatis.typeHandlers;
import java.sql.CallableStatement;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.BaseTypeHandler;

public class DateTypeHandler extends BaseTypeHandler<Date> {
	@Override
	// 在为SQL配置传入参数时执行的操作,可将参数传入数据库前对数据类型做处理
	public void setNonNullParameter(PreparedStatement ps, int i,  Date date, JdbcType jdbcType) throws SQLException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		ps.setString(i, sdf.format(date));
	}
	
	@Override
	// 根据字段名来获得结果
	public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
		// 在结果集中把varchar类型转换为Date类型
		String tmpDate = rs.getString(columnName);
		if(tmpDate != null) {
			return new Date(Long.parseLong(tmpDate));
		} else {
			return null;
		}
	}
	
	@Override
	//根据字段下标获得结果
	public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		String tmpDate = rs.getString(columnIndex);
		if(tmpDate != null) {
			return new Date(Long.parseLong(tmpDate));
		} else {
			return null;
		}
	}
	
	@Override
	public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		String tmpDate = cs.getString(columnIndex);
		if(tmpDate != null) {
			return new Date(Long.parseLong(tmpDate));
		} else {
			return null;
		}
	}

}

重写的第一个方法是setNonNullParameter(),它在SQL映射配置传入参数时候执行,可以在把参数传入数据库前,对数据做一些处理。第16行,我们做的就是简单地把持久化实体类中的Date类型,转换为数据库中VARCHAR类型。

      第二个方法,getNullableResult(),它在对数据库操作后,数据库返回结果集时执行,将结果集信息转换为相应的Java数据类型。第23行,首先根据字段名,获得对应的返回结果,如果结果不空,就把它转换成Date类型,最后返回出去。

      下面两个一样都是根据字段下标来获取返回结果,不同的是最后一个是供存储过程使用。好了,自定义的类型处理器我们已经编写好了,它能按照我们的要求,完成Date类型和VARCHAR类型的互相转化,接下来就是要把它配置到全局配置文件中。

 

配置类型处理器

在全局配置文件中,用<typeHandlers></typeHandlers>标签对来配置我们的自定义类型处理器:

<!-- 配置自定义类型处理器 -->
	<typeHandlers>
		<typeHandler handler="com.mybatis.typeHandlers.DateTypeHandler" 
		javaType="java.util.Date" jdbcType="VARCHAR"/>
	</typeHandlers>

假如我们有多个typeHandler,可以一个一个配置上去:

<!-- 配置自定义类型处理器 -->
	<typeHandlers>
		<typeHandler handler="com.mybatis.typeHandlers.DateTypeHandler" 
		javaType="java.util.Date" jdbcType="VARCHAR"/>
		<typeHandler handler="com.mybatis.typeHandlers.StringTypeHandler"/>
		<typeHandler handler="com.mybatis.typeHandlers.EnumTypeHandler"/>
	</typeHandlers>

也可以像配置typeAliases(为Java类型或自己编写的类设置别名)批量配置别名一样,使用package元素,指定包名,扫描包下的所有typeHandler,来批量配置类型处理器:

<!-- 配置自定义类型处理器 -->
	<typeHandlers>
		<package name="com.mybatis.typeHandlers"/>
	</typeHandlers>

在全局配置文件中注册好自定义类型处理器后,最后一步,到SQL映射配置文件中,指定哪一些字段会去使用这个自定义typeHandler。

 

指定typeHandler的使用字段

写好自定义的类型处理器,也在全局配置文件中注册了,最后一步,就是在SQL映射配置文件中,设置哪些字段需要用到这个自定义typeHandler。我们可以在resultMap结果集映射中配置好哪个字段对应的javaType和jdbcType,同时设置该字段的typeHandler:

<!-- id是resultMap的标识id -->
	<resultMap type="com.mybatis.po.User" id="userMap">
	<!-- id为配置主键,column为主键在数据库中的字段名,property为主键在Java类中的属性名 -->
	<id column="id" property="id"/>
	
	<!-- 定义普通属性的javaType和jdbcType -->
	<result column="birthday" property="birthday" typeHandler="com.mybatis.typeHandlers
	.DateTypeHandler"/>
	</resultMap>

也可以在具体的SQL语句标签对中对某一字段进行配置:

<insert id="insertUser" parameterType="user">
    	INSERT INTO USER(username, password, gender, birthday, email, province, city, age)
    	VALUE(#{username}, #{password}, #{gender}, #{birthday, jdbcType=DATE, typeHandler=com.mybatis
    	.typeHandlers.DateTypeHandler}, #{email}, #{province},
    	#{city}, #{age})
    </insert>

即使你在resultMap中对字段进行了javaType,jdbcType和类型处理器的映射配置,你也可以继续在具体标签里面对字段进行配置。

 

objectFactory配置文件

自定义typeHandler让我们可以自己设置JavaBean属性和数据库字段之间的数据类型转换,自定义objectFactory,则可以让我们在实例化JavaBean时,对实例化的对象做一些事情。

在自定义objectFactory前,先来了解这个objectFactory,它的作用是帮我们实例化数据库查询结果映射的对应Java目标类。我们知道,在SQL映射配置文件中,我们配置了SQL语句的返回结果集的类型,数据库操作完成后的返回结果集会被映射到resultMap中配置的对应目标类型,或是具体标签中配置的resultType标识的目标类型,如int,string或是用户自定义的JavaBean。objectFactory的任务就是在数据库返回结果集时,为我们创建映射的对应Java类。

MyBatis对于结果集的映射,有两种方法来创建目标类对象,第一种,当参数映射不存在时,调用目标类无参数的构造方法实例化对象(这就是为什么JavaBean持久化实体类中要有一个空的构造方法);当参数映射存在时,则调用目标类有参数的构造方法。重写objectFactory对象工厂的方法,我们就可以在对象被实例化出来前,对内部的属性做一些自定义修改,和自定义线程工厂ThreadFactory几乎一样。接下来就看看objectFactory中有哪些方法我们可以重写它。

 

重写三个方法

自定义objectFactory,编写自己的对象工厂,首先要继承DefaultObjectFactory类,接着重写里面的一些方法:

create()方法:create()方法有两个重载版本,分别对应无参数的构造方法和有参数的构造方法。

setProperties()方法:将property参数初始化到自定义对象工厂中,做为该对象工厂类的全局参数。

isCollection()方法:用来判断我们的目标类是不是一个集合,假如不是一个集合,就没办法接收数据库一次操作返回的多条结果。

来看个简单例子,编写一个User类的对象工厂,在数据库查询用户信息后,映射到对应的目标类对象时,会对用户的年龄字段age做修改:

package com.mybatis.test;

import java.util.List;
import java.util.Properties;

import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import com.mybatis.po.User;

public class UserObjectFactory extends DefaultObjectFactory {
	private static final long serialVersionUID = 1L;

	// 重写默认构造方法
	@Override
	public <T> T create(Class<T> type) {
		T ret = super.create(type);
		//判断加载的类的类型,然后执行自定义操作
		if(User.class.isAssignableFrom(type)) {
			User entity = (User)ret;
			entity.setAge(27);
		}
		return ret;
	}
	
	// 重写有参数的构造方法
	@Override
	public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes,
			List<Object>constructorArgs) {
		return super.create(type, constructorArgTypes, constructorArgs);
	}
	
	// 重写处理参数方法
	@Override
	public void setProperties(Properties properties) {
		super.setProperties(properties);
		properties.setProperty("1", "Lalisa Manoban");
		System.out.println(properties.getProperty("1"));
	}
}

在重写的create()方法中,第17行判断,如果实例化的类是我们要的User类,则把User类的实例对象中的年龄字段age设置为27。第33行的setProperties()则是把我们在全局配置文件中,配置对象工厂时附带配置的property参数 初始化到我们的自定义对象工厂中,在setProperties()方法中我们也可以修改这些参数。

 

配置自定义对象工厂

和类型处理器一样,写好我们的对象工厂后,要把它配置到全局配置文件中去:

<!-- 配置对象工厂 -->
	<objectFactory type="com.mybatis.test.UserObjectFactory">
		<property name="userName" value="zzzjustin_z"/>
	</objectFactory>

可以看到,property标签里我们可以设置一些参数,在对象工厂中我们重写了setProperties()方法,它会对这个参数userName做修改。

      所有东西配置好后,就来简单测试一下,在实例化一个User对象时,对象里面的年龄字段age是否会被会被设置为27:

public void TestSelect() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		User user = sqlSession.selectOne("SQLtest.findUserById", 1);
		StringBuffer result = new StringBuffer();
		result.append("用户名: " + user.getUsername() + "\r\n");
		result.append("密码: " + user.getPassword() + "\r\n");
		result.append("性别: " + user.getGender() + "\r\n");
		result.append("电子邮箱: " + user.getEmail() + "\r\n");
		result.append("省份: " + user.getProvince() + "\r\n");
		result.append("城市: " + user.getCity() + "\r\n");
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		result.append("出生日期: " + sdf.format(user.getBirthday()) + "\r\n");
		result.append("年龄:" + user.getAge() + "\r\n");
		
		System.out.println(result.toString());

		sqlSession.close();
	}

简单地查询id为1的用户的所有信息,把查询返回的结果映射到User对象中,再输出看看年龄是否被修改。在数据库中,id为1的用户年龄为20:

查询结果:

由查询结果可以看出,查询返回结果中,用户的年龄被修改为27,且有一句输出“Lalisa Manoban”,这正是我们重写的setProperties()方法中对property参数的修改和输出操作。

 

完整代码已上传GitHub:

https://github.com/justinzengtm/SSM-Frame/tree/master/MyBatis

发布了97 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/justinzengTM/article/details/95924679