MyBatis自定义类型处理器(typeHandler)
我们执行sql
语句通过PreparedStatement
语句实现,PreparedStatement
会设置?
值,类型处理器帮PreparedStatement
找到对应的set
方法,到底是选择setInt
、setString
或setDate...
注意:类型处理器默认可以处理基本的数据类型以及对应的包装类,uitl.Date、sql.Date
等,但无法默认处理自定义类型。
当MyBatis
将一个Java
对象作为输入参数执行INSERT
语句操作时,它会创建一个PreparedStatement
对象,并且使用setXXX()
方法对占位符设置相应的参数值。这里,XXX
可以是Int
,String
,Date
等Java
对象属性类型的任意一个。示例如下:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB)
VALUES(#{studId},#{name},#{email},#{dob})
</insert>
为了执行这个语句,MyBatis
将采取以下一系列动作:
创建一个有占位符的PreparedStatement接口,如下:
PreparedStatement ps = connection.prepareStatement("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(?,?,?,?)");
检查Student
对象的属性studId
的类型,然后使用合适setXXX()
方法去设置参数值。这里studId
是Integer
类型,所以会使用setInt()
方法:
ps.setInt(1,student.getStudId());
类似地,对于name
和email
属性都是String
类型的,MyBatis
使用setString()
方法设置参数。
至于dob
属性, MyBatis
会使用setDate()
方法设置dob
处占位符位置的值。MyBaits
会将java.util.Date
类型转换为java.sql.Timestamp
并设值:
ps.setTimestamp(4, new Timestamp((student.getDob()).getTime()));
但MyBatis
是怎么知道对于Integer
类型属性使用setInt()
和String
类型属性使用setString()
方法呢?其实MyBatis
是通过使用类型处理器typeHandlers
来决定这么做的。
MyBatis
对于以下的类型使用内建的类型处理器:所有的基本数据类型、基本类型的包装类型、byte[]
、java.util.Date
、java.sql.Date
、java,sql.Time
、java.sql.Timestamp
、java枚举类型
等。所以当MyBatis
发现属性的类型属于上述类型,他会使用对应的类型处理器将值设置到PreparedStatement
中,同样地,当SQL
结果集封装成java
类对象的时候,也有类似的过程。
那如果有一个自定义的类型,怎么存储存储到数据库呢?示例如下:假设表STUDENTS
有一个 PHONE
字段,类型为 VARCHAR2(15)
,而 Student
类有一个自定义类型PhoneNumber
,这个类型包括三个属性:国家编号、区号、电话号码。
public class PhoneNumber{
private String countryCode;
private String stateCode;
private String number;
public PhoneNumber(){
}
public PhoneNumber(String countryCode, String stateCode, String number) {
this.countryCode = countryCode;
this.stateCode = stateCode;
this.number = number;
}
public String getAsString() {
return countryCode + "-" + stateCode + "-" + number;
}
// Setters and getters
}
public class Student{
private Integer id;
private String name;
private String email;
private PhoneNumber phone;
// Setters and getters
}
StudentMapper.xml
配置如下:
<insert id="insertStudent" parameter Type="Student">
insert into students(name,email,phone)
values(#{name},#{email},#{phone})
</insert>
这个xml
文件里面,参数对象中的属性phone
的值需要传递给#{phone}
,而参数对象的属性phone
是 PhoneNumber
类型。但是,MyBatis
并不知道该怎样来处理这个类型的对象。为了让MyBatis
明白怎样处理这个自定义的Java
对象类型,如PhoneNumber
,我们可以创建一个自定义的类型处理器,MyBatis
提供了抽象类BaseTypeHandler<T>
,我们可以继承此类创建自定义类型处理器。代码如下:
package com.au.typehandlers;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.briup.pojo.PhoneNumber;
public class PhoneTypeHandler extends BaseTypeHandler<PhoneNumber>{
//遇到PhoneNumber参数的时候应该如何在ps中设置值
@Override
public void setNonNullParameter(PreparedStatement ps, int i, PhoneNumber parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.getAsString());
}
//查询中遇到PhoneNumber类型的应该如何封装(使用列名封装)
@Override
public PhoneNumber getNullableResult(ResultSet rs, String columnName) throws SQLException {
return new PhoneNumber(rs.getString(columnName));
}
//查询中遇到PhoneNumber类型的应该如何封装(使用列的下标)
@Override
public PhoneNumber getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return new PhoneNumber(rs.getString(columnIndex));
}
//CallableStatement使用中遇到了PhoneNumber类型的应该如何封装
@Override
public PhoneNumber getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return new PhoneNumber(cs.getString(columnIndex));
}
}
注意:使用ps.setString()
和rs.getString()
方法是因为在数据库的表中phone
列是VARCHAR
类型。
最后需要在mybatis-config.xml
中注册这个类型处理器:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="application.properties" />
<typeHandlers>
<typeHandler handler="com.au.typehandlers.PhoneTypeHandler" />
</typeHandlers>
</configuration>
注册PhoneTypeHandler
后,MyBatis
就能够将Phone
类型的对象值存储到VARCHAR
类型的列上。