在JDBC中,需要通過PreparedStatement對(duì)象中設(shè)置那些已經(jīng)預(yù)編譯過的SQL語句的參數(shù),執(zhí)行SQL后,會(huì)通過ResultSet對(duì)象獲取得到數(shù)據(jù)庫的數(shù)據(jù)不跟,而這些MyBatis是根據(jù)數(shù)據(jù)的類型通過typeHandler來實(shí)現(xiàn)的,在typeHandler中米碰,分為jdbcType和javaType躬拢,其中jdbcType用于定義數(shù)據(jù)庫類型,而javaType用于定義java類型见间,那么typeHandler的作用就是承擔(dān)jdbcType和javaType之間的相互轉(zhuǎn)換聊闯,
在很多中情況下,我們并不需要去配置typeHandler米诉,jdbctype,javatype菱蔬。因?yàn)镸yBatis會(huì)探測(cè)應(yīng)該使用什么類型的typeHandler進(jìn)行配置,但是有些場(chǎng)景就無法探測(cè)到史侣,對(duì)于那些需要使用自定義的枚舉的場(chǎng)景拴泌,或者數(shù)據(jù)庫使用特殊數(shù)據(jù)類型的場(chǎng)景,可以使用自定義的typeHandler去處理類型之間的轉(zhuǎn)換問題惊橱。
源碼分析
在MyBatis中typeHandler都要實(shí)現(xiàn)接口org.apache.ibatis.type.TypeHandler,首先讓我們先看看這個(gè)接口的定義
/**
* @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;
}
這里我們稍微說明一下他們的定義
- 其中T是泛型蚪腐,專指javaType,比如我們需要String的時(shí)候税朴,那么實(shí)現(xiàn)類可以寫為implements TypeHandler<String>
- setParameter方法回季,是使用typeHandler通過PreparedStatement對(duì)象設(shè)置SQL參數(shù)的時(shí)候使用的具體方法家制,其中i是參數(shù)在SQL的下標(biāo),parameter是參數(shù)泡一,jdbcType是數(shù)據(jù)庫類型
- 其中有三個(gè)getResult的方法颤殴,它的作用是從JDBC結(jié)果集中獲取數(shù)據(jù)進(jìn)行轉(zhuǎn)換,要么使用列名鼻忠,(columnname)要么使用下標(biāo)(columnIndex)獲取數(shù)據(jù)庫的數(shù)據(jù)涵但,其中最后一個(gè)getResult方法是存儲(chǔ)過程使用的。
TypeHandler源碼實(shí)現(xiàn)
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
protected Configuration configuration;
public void setConfiguration(Configuration c) {
this.configuration = c;
}
@Override
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 {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different configuration property. " +
"Cause: " + e, e);
}
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result;
try {
result = getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
T result;
try {
result = getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set. Cause: " + e, e);
}
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
T result;
try {
result = getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement. Cause: " + e, e);
}
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;
}
簡單分析一下BaseTypeHandler的源碼
- BaseTypeHandler是一個(gè)抽象類帖蔓,它實(shí)現(xiàn)了TypeHandler的所有接口矮瘟,需要子類去實(shí)現(xiàn)它自定義的4個(gè)抽象方法
- setParameter方法中,當(dāng)parameter和jdbcType都為空時(shí)將拋出異常塑娇,如果能明確jdbcType澈侠,將會(huì)進(jìn)行空設(shè)置(PreparedStatement的setNull方法);如果paramter不為空钝吮,將使用setNonNullParameter去設(shè)置參數(shù)(該方法需要子類去實(shí)現(xiàn))
- getResult方法埋涧,非空結(jié)果集是通過getNullableResult方法去獲取的板辽,該方法需要子類去實(shí)現(xiàn)奇瘦,同樣是針對(duì)下標(biāo)、列名以及存儲(chǔ)過程三種的實(shí)現(xiàn)
- getNullableParameter方法用于存儲(chǔ)過程
Mybatis使用最多的是typeHandler之一是——StringTypeHandler.它用于字符串轉(zhuǎn)換劲弦、
/**
* @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);
}
}
顯然它實(shí)現(xiàn)了BaseTypeHandler的4個(gè)抽象方法耳标,代碼也非常簡單。
在這里邑跪,MyBatis把javaType和jdbcType相互轉(zhuǎn)換次坡,那么他們是如何進(jìn)行注冊(cè)的呢?在MyBatis中采用org.apache.ibatis.type.TypeHandlerRegistry類對(duì)象的register方法進(jìn)行注冊(cè)
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
}
這樣就實(shí)現(xiàn)用代碼的形式注冊(cè)typeHandler画畅,注意砸琅,自定義的typeHandler一般不會(huì)使用代碼注冊(cè),而是通過配置或者掃描轴踱。0
自定義typeHandler
從系統(tǒng)定義的typeHandler可以知道症脂,要實(shí)現(xiàn)typeHandler就需要去實(shí)現(xiàn)接口typeHandler,或者繼承BaseTypeHandler(實(shí)際上BassseTypeHandler實(shí)現(xiàn)了typehandler的接口)
package com.learn.ssm.chapter4.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;
public class MyTypeHandler implements TypeHandler<String> {
Logger logger=Logger.getLogger(MyTypeHandler.class);
// 這個(gè)就是一個(gè)日志的對(duì)象淫僻,記錄和這個(gè)類對(duì)象進(jìn)行的一切操作诱篷,記錄用戶的操作
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
// TODO Auto-generated method stub
String result=rs.getString(columnName);
logger.info("讀取string參數(shù)1【"+result+"】");
return result;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
String result=rs.getString(columnIndex);
logger.info("讀取string參數(shù)2【"+result+"】");
return result;
}
@Override
public String getResult(CallableStatement cs, int columnIndex)
throws SQLException {
// TODO Auto-generated method stub
String result=cs.getString(columnIndex);
logger.info("讀取string參數(shù)3【"+result+"】");
return result;
}
@Override
public void setParameter(PreparedStatement ps, int i, String parameter,
JdbcType jdbcType) throws SQLException {
// TODO Auto-generated method stub
logger.info("設(shè)置string參數(shù)["+parameter+"]");
ps.setString(i, parameter);
}
}
定義的typeHandler泛型為String,顯然我們要把數(shù)據(jù)庫的數(shù)據(jù)類型轉(zhuǎn)換為String型雳灵,然后實(shí)現(xiàn)設(shè)置參數(shù)和獲取結(jié)果集方法
配置typeHandler,配置文件在mybatis-config.xml
<!-- 配置typehandler -->
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="string" handler="com.learn.ssm.chapter4.typehandler.MyTypeHandler"
/>
<!-- <package name="com.learn.ssm.chapter4.typehandler" /> -->
</typeHandlers>
配置完成后系統(tǒng)才會(huì)讀取它棕所,這樣注冊(cè)后,當(dāng)jdbcType和javaType能與MyTypeHandler對(duì)應(yīng)的時(shí)候悯辙,它就會(huì)啟動(dòng)MyTypeHandler.有時(shí)候還可以顯示啟用typeHandler琳省,一般而言啟用這個(gè)typeHandler有 兩種方式迎吵。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.ssm.chapter3.mapper.RoleMapper">
<!-- namespace所對(duì)應(yīng)的是一個(gè)接口的全限定名,于是MyBatis上下文就可以通過它找到對(duì)應(yīng)的接口 -->
<resultMap id="roleMapper" type="role">
<result property="id" column="id" />
<result property="roleName" column="role_name" jdbcType="VARCHAR"
javaType="string" />
<result property="note" column="note"
typeHandler="com.learn.ssm.chapter4.typehandler.MyTypeHandler" />
</resultMap>
<select id="getRole" parameterType="long" resultMap="roleMapper">
select id, role_name, note from t_role where id = #{id}
</select>
<select id="findRoles" parameterType="string" resultMap="roleMapper">
select id, role_name, note from t_role
where role_name like concat('%', #{roleName, jdbcType=VARCHAR,
javaType=string}, '%')
</select>
<!--第一種:用配置中一樣的jdbcType和javaType-->
<select id="findRoles2" parameterType="string" resultMap="roleMapper">
select id, role_name, note from t_role
where note like concat('%', #{note,
typeHandler=com.learn.ssm.chapter4.typehandler.MyTypeHandler}, '%')
</select>
<!--第二種:直接用具體的實(shí)現(xiàn)類-->
</mapper>
注意要么指定了與自定義typeHandler一致的jdbcType和javaType岛啸,要么直接使用typeHandler指定具體的實(shí)現(xiàn)類钓觉,在一些因?yàn)閿?shù)據(jù)庫返回為空導(dǎo)致無法斷定采用哪個(gè)typeHandler來處理,而又沒有注冊(cè)對(duì)應(yīng)的javaType的typeHandler時(shí)坚踩,MyBatis無法知道使用哪個(gè)typeHandler
- 補(bǔ)充 resultMap是Mybatis最強(qiáng)大的元素荡灾,它可以將查詢到的復(fù)雜數(shù)據(jù)(比如查詢到幾個(gè)表中數(shù)據(jù))映射到一個(gè)結(jié)果集當(dāng)中
<!--column不做限制,可以為任意表的字段瞬铸,而property須為type 定義的pojo屬性-->
<resultMap id="唯一的標(biāo)識(shí)" type="映射的pojo對(duì)象">
<id column="表的主鍵字段批幌,或者可以為查詢語句中的別名字段" jdbcType="字段類型" property="映射pojo對(duì)象的主鍵屬性" />
<result column="表的一個(gè)字段(可以為任意表的一個(gè)字段)" jdbcType="字段類型" property="映射到pojo對(duì)象的一個(gè)屬性(須為type定義的pojo對(duì)象中的一個(gè)屬性)"/>
<association property="pojo的一個(gè)對(duì)象屬性" javaType="pojo關(guān)聯(lián)的pojo對(duì)象">
<id column="關(guān)聯(lián)pojo對(duì)象對(duì)應(yīng)表的主鍵字段" jdbcType="字段類型" property="關(guān)聯(lián)pojo對(duì)象的主席屬性"/>
<result column="任意表的字段" jdbcType="字段類型" property="關(guān)聯(lián)pojo對(duì)象的屬性"/>
</association>
<!-- 集合中的property須為oftype定義的pojo對(duì)象的屬性-->
<collection property="pojo的集合屬性" ofType="集合中的pojo對(duì)象">
<id column="集合中pojo對(duì)象對(duì)應(yīng)的表的主鍵字段" jdbcType="字段類型" property="集合中pojo對(duì)象的主鍵屬性" />
<result column="可以為任意表的字段" jdbcType="字段類型" property="集合中的pojo對(duì)象的屬性" />
</collection>
</resultMap>
運(yùn)行
package com.learn.ssm.chapter4.main;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import com.learn.ssm.chapter3.mapper.RoleMapper;
import com.learn.ssm.chapter3.pojo.Role;
import com.learn.ssm.chapter3.utils.SqlSessionFactoryUtils;
public class chapter4Main {
public static void main(String[] args) {
testRoleMapper();
// testTypeHandler();
}
private static void testRoleMapper() {
Logger log = Logger.getLogger(chapter4Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.openSqlSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
log.info(role.getRoleName());
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}