一筐咧、簡(jiǎn)介
??不管是參數(shù)綁定误褪,還是結(jié)果映射亭螟,mybatis 都要完成 Java 類(lèi)型和 Jdbc 類(lèi)型數(shù)據(jù)的相互轉(zhuǎn)換,在框架中為了統(tǒng)一類(lèi)型轉(zhuǎn)換處理器的行為榆俺,定義了 TypeHandler
接口售躁,并實(shí)現(xiàn)了多種不同的類(lèi)型處理器。
二谴仙、TypeHandler 接口
??TypeHandler
為所有類(lèi)型轉(zhuǎn)換器都要實(shí)現(xiàn)的接口迂求,定義了 #setParameter()
方法來(lái)為SQL綁定參數(shù),定義了重載的 #getResult()
方法來(lái)將結(jié)果映射為Java對(duì)象晃跺,接口定義如下:
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 就表示 Java 類(lèi)型揩局,各個(gè)特定的轉(zhuǎn)換器實(shí)現(xiàn)類(lèi)根據(jù)轉(zhuǎn)換的 Java 類(lèi)型指定其具體類(lèi)型。
??#setParameter()
方法完成 javaType 到 jdbcType 的轉(zhuǎn)換掀虎。PreparedStatement ps
表示一個(gè)預(yù)編譯的SQL語(yǔ)句對(duì)象凌盯,int i
指定了綁定該語(yǔ)句中的參數(shù)的位置,T parameter
為要被轉(zhuǎn)換的 Java 對(duì)象烹玉,JdbcType jdbcType
為目的轉(zhuǎn)化類(lèi)型驰怎,當(dāng)傳入的 Java 對(duì)象是空時(shí),參數(shù)綁定需要調(diào)用 PreparedStatement .setNull()
方法二打,該方法的第二個(gè)參數(shù)即為 Jdbc 類(lèi)型县忌。
??重載的三個(gè) #getResult()
方法分別根據(jù)數(shù)據(jù)列名、列索引來(lái)將結(jié)果集中的數(shù)據(jù)轉(zhuǎn)化為 Java 類(lèi)型继效,完成 jdbcType 到 javaType 的轉(zhuǎn)換症杏。
public Role getRole(Long id) {
Connection connection = getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = connection.prepareStatement("select id, role_name, note from t_role where id = ?");
ps.setLong(1, id);
rs = ps.executeQuery();
while (rs.next()) {
Long roleId = rs.getLong("id");
String roleName = rs.getString("role_name");
String note = rs.getString("note");
Role role = new Role();
role.setId(id);
role.setRoleName(roleName);
role.setNote(note);
return role;
}
} catch (Exception e) {
Logger.getLogger(JdbcExample.class.getName()).log(Level.SEVERE, null, e);
} finally {
this.close(rs, ps, connection);
}
return null;
}
??再次將 反射器模塊 開(kāi)頭的這段JDBC代碼拿出來(lái)回顧一下,前面的 ps.setLong(1, id)
就是將 javaType 轉(zhuǎn)換為 jdbcType瑞信,而后面的 rs.getString("role_name")
就是將jdbcType 轉(zhuǎn)換為 javaType厉颤,可以看出不管是 PreparedStatement 還是 ResultSet 的方法名都帶有類(lèi)型名,所以下面介紹的方法轉(zhuǎn)換器的實(shí)現(xiàn)中要根據(jù)處理的不同目標(biāo)類(lèi)型凡简,調(diào)用不同的 getType/setType
方法逼友。
【注意】如果將一張數(shù)據(jù)表中的一行數(shù)據(jù)映射成一個(gè)Java對(duì)象精肃,則Java對(duì)象中的每個(gè)成員屬性跟表中的一行的每個(gè)列映射,上述的類(lèi)型轉(zhuǎn)換器帜乞,實(shí)際上是完成一個(gè)屬性跟一個(gè)數(shù)據(jù)庫(kù)字段列的映射司抱,因?yàn)镴ava對(duì)象有多個(gè)成員,數(shù)據(jù)表有多個(gè)字段挖函,所以實(shí)際映射一行數(shù)據(jù)的時(shí)候状植,要有多個(gè)類(lèi)型轉(zhuǎn)換器參與處理。上述的Java對(duì)象怨喘,實(shí)際是指一個(gè)成員屬性對(duì)應(yīng)的對(duì)象津畸。
三、BaseTypeHandler
??BaseTypeHandler
是一個(gè)承上啟下的抽象類(lèi)必怜,它實(shí)現(xiàn)了 TypeHandler
接口肉拓,又被所有具體的轉(zhuǎn)換器類(lèi)所繼承,因?yàn)镾QL綁定參數(shù)時(shí)可能傳入一個(gè)空的參數(shù)梳庆,結(jié)果映射時(shí)也可能獲取到一個(gè)空的數(shù)據(jù)暖途,這里需要一點(diǎn)特別的處理,這些都放在 BaseTypeHandler
中統(tǒng)一實(shí)現(xiàn)膏执,簡(jiǎn)化定義類(lèi)型轉(zhuǎn)換器的工作驻售。
??BaseTypeHandler
封裝了 Configuration
對(duì)象作為成員,該對(duì)象與 mybatis 全局配置中的配置信息映射更米,因?yàn)樵?mybatis-config.xml 用戶(hù)可以自定義類(lèi)型轉(zhuǎn)換器處理一些特殊的類(lèi)型欺栗,比如枚舉。
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
protected Configuration configuration;
public void setConfiguration(Configuration c) {
this.configuration = c;
}
// other code
}
??BaseTypeHandler
還繼承了一個(gè)帶泛型的父類(lèi) TypeReference
征峦,該類(lèi)主要用來(lái)解析泛型的具體類(lèi)型迟几,后面會(huì)介紹。
1栏笆、void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
【功能】使用 Java 類(lèi)型對(duì)象為 SQL 綁定參數(shù)类腮。
【源碼與注解】
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// (1)當(dāng)Java對(duì)象為空時(shí),調(diào)用 PreparedStatement.setNull() 綁定參數(shù)
if (parameter == null) {
// 上述方法需要確定JdbcType蛉加,所以如果jdbcType也為空蚜枢,該方法無(wú)法執(zhí)行,拋出異常
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) {
// 傳入的JdbcType類(lèi)型不合適针饥,拋出異常
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 {
// (2)調(diào)用類(lèi)中定義的抽象方法祟偷,該方法在繼承了BaseTypeHandler的子類(lèi)中有具體且不同的實(shí)現(xiàn)
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);
}
}
}
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
【解析】
- (1)當(dāng)Java對(duì)象為空時(shí),調(diào)用
PreparedStatement.setNull()
綁定參數(shù)打厘,如果傳入的 jdbcType 也為空則拋出異常,如果方法執(zhí)行拋出異常贺辰,證明傳入的 jdbcType 不合適户盯。 - (2)調(diào)用類(lèi)中定義的抽象方法嵌施,該方法在繼承了BaseTypeHandler的子類(lèi)中有具體且不同的實(shí)現(xiàn)。
2莽鸭、T getResult(ResultSet rs, String columnName)
【功能】根據(jù)列名對(duì) SQL 執(zhí)行結(jié)果 ResultSet 對(duì)象進(jìn)行映射成 Java 對(duì)象吗伤。
【源碼與注解】
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result;
try {
// 調(diào)用子類(lèi)中實(shí)現(xiàn)的抽象方法獲取結(jié)果值
result = getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
// 判斷映射的結(jié)果集是否為空
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
【解析】
??這里代碼第一眼看有點(diǎn)費(fèi)解,為什么先嘗試從結(jié)果集中獲取結(jié)果硫眨,再來(lái)判斷結(jié)果集是否空呢足淆?從 ResetSet.wasNull()
的注釋中可以看出,需要調(diào)用 get 方法礁阁,才能使用該方法巧号,所以這里要先嘗試先調(diào)用 #getNullableResult()
獲取結(jié)果,該方法的具體實(shí)現(xiàn)由各具體的轉(zhuǎn)換器子類(lèi)實(shí)現(xiàn)姥闭。
??其他兩個(gè)重名的重載方法跟上述方法類(lèi)似丹鸿,不再贅述。
四棚品、各種類(lèi)型轉(zhuǎn)換器
??TypeHandler
有很多類(lèi)型轉(zhuǎn)化器靠欢,下面分類(lèi)說(shuō)明。
1铜跑、基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的包裝類(lèi)型轉(zhuǎn)換處理器
??Java 中八種基本數(shù)據(jù)類(lèi)型分別是 short门怪、int、long锅纺、float掷空、double、boolean伞广、byte拣帽、char
,對(duì)應(yīng)的包裝類(lèi)型分別為 Short嚼锄、Integer减拭、Long、Float区丑、Double拧粪、Boolean、Byte沧侥、Character
可霎,對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換器別分為 ShortTypeHandler、IntegerTypeHandler宴杀、LongTypeHandler癣朗、FloatTypeHandler、DoubleTypeHandler旺罢、BooleanTypeHandler旷余、ByteTypeHandler绢记、CharacterTypeHandler
,這幾個(gè)轉(zhuǎn)換器的實(shí)現(xiàn)都是類(lèi)似的正卧,只是調(diào)用 PreparedStatement/ResultSet/CallableStatement 對(duì)應(yīng)的 set/get 方法有差異蠢熄,以 IntegerTypeHandler 為例,源碼如下:
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getInt(columnName);
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getInt(columnIndex);
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getInt(columnIndex);
}
}
??其他的 TypeHandler 比如 ShortTypeHandler 就是將 #getInt
改成 #getShort
炉旷,將 #setInt
改成 #setShort
签孔,其他類(lèi)型轉(zhuǎn)換器以此類(lèi)推。
2窘行、字節(jié)饥追、字符串及相關(guān)數(shù)組對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換處理器
??在介紹這些轉(zhuǎn)換處理器之前,有必要先了解關(guān)于字符抽高、字符串相關(guān)的通用 SQL 類(lèi)型判耕,列表如下:
類(lèi)型 | 用途 |
---|---|
char(n)/character(n) |
字符/字符串。固定長(zhǎng)度n |
varchar(n)/character varying(n) |
字符/字符串翘骂”谙ǎ可變長(zhǎng)度,最大長(zhǎng)度n |
blob |
二進(jìn)制形式的長(zhǎng)文本數(shù)據(jù) |
clob/text |
以字節(jié)為單位保存文本大對(duì)象 |
nclob |
以 unicode 字符為單位保存文本大對(duì)象 |
?? 不同數(shù)據(jù)庫(kù)數(shù)據(jù)類(lèi)型的定義略有不同碳竟,不過(guò)大同小異草丧,一般都有定長(zhǎng)和變長(zhǎng)字符類(lèi)型可選,兩者的區(qū)別在于莹桅,定長(zhǎng)類(lèi)型的數(shù)據(jù)存儲(chǔ)前數(shù)據(jù)庫(kù)就分配一塊固定的存儲(chǔ)空間了昌执,而變長(zhǎng)是實(shí)際用到多少分配多少。
??mysql 和 oracle 都有 blob
的數(shù)據(jù)類(lèi)型诈泼,用來(lái)存儲(chǔ)二進(jìn)制形式的長(zhǎng)文本數(shù)據(jù)懂拾,最大存儲(chǔ)空間不同數(shù)據(jù)庫(kù)設(shè)計(jì)略有差異,為了滿(mǎn)足不同大小的數(shù)據(jù)庫(kù)存儲(chǔ)的需求铐达,mysql 還有 tinyblob
岖赋、mediumblob
、longblob
的數(shù)據(jù)類(lèi)型瓮孙,oracle 還有 bfile
滿(mǎn)足超大數(shù)據(jù)塊的存儲(chǔ)需求唐断。
??對(duì)于存儲(chǔ)文本形式的大數(shù)據(jù)塊,mysql 對(duì)應(yīng)的數(shù)據(jù)類(lèi)型為 tinytext
杭抠、text
脸甘、mediumtext
、longtext
偏灿,而 oracle 則設(shè)計(jì)了 clob
丹诀、nclob
,clob
使用數(shù)據(jù)庫(kù)編碼的字符的定長(zhǎng)字節(jié)存儲(chǔ)文本,nclob
使用 unicode 編碼的字符的定長(zhǎng)字節(jié)存儲(chǔ)文本忿墅。
(1)與 blob 相關(guān)的類(lèi)型轉(zhuǎn)換處理器
BlobTypeHandler / BlobByteObjectArrayTypeHandler / BlobInputStreamTypeHandler
public class BlobTypeHandler extends BaseTypeHandler<byte[]> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType)
throws SQLException {
// 將字節(jié)數(shù)組轉(zhuǎn)化為流對(duì)象
ByteArrayInputStream bis = new ByteArrayInputStream(parameter);
ps.setBinaryStream(i, bis, parameter.length);
}
@Override
public byte[] getNullableResult(ResultSet rs, String columnName)
throws SQLException {
Blob blob = rs.getBlob(columnName);
byte[] returnValue = null;
if (null != blob) {
returnValue = blob.getBytes(1, (int) blob.length());
}
return returnValue;
}
// 另外兩個(gè)方法類(lèi)似
【解析】
??參數(shù)綁定時(shí)扁藕,先將字節(jié)數(shù)組轉(zhuǎn)化為流對(duì)象,再綁定參數(shù)疚脐,這樣數(shù)據(jù)庫(kù)在接收數(shù)據(jù)時(shí)是從流對(duì)象中讀取,直到到達(dá)文件末尾邢疙,性能更佳棍弄;結(jié)果映射都是從結(jié)果集中獲取到一個(gè) Blob
對(duì)象,再?gòu)脑搶?duì)象中讀取字節(jié)數(shù)組疟游。
??BlobByteObjectArrayTypeHandler
跟 BlobTypeHandler 的處理基本一樣呼畸,只不過(guò)處理的java對(duì)象類(lèi)型為 Byte[]
,所以處理之前將 Byte[] 轉(zhuǎn)換為 byte[]颁虐,結(jié)果映射時(shí)反過(guò)來(lái)將 byte[] 轉(zhuǎn)換為 Byte[]蛮原。
??BlobInputStreamTypeHandler
處理的java數(shù)據(jù)類(lèi)型是 InputStream
,不過(guò)用的不是 #setBinaryStream()
另绩,而是 #setBlob()
儒陨,這里有點(diǎn)疑惑,看 Javadoc 的解釋是 #setBlob()
方法會(huì)通知驅(qū)動(dòng)發(fā)送 Blob
形式的參數(shù)值給服務(wù)端笋籽;而 #setBinaryStream()
方法驅(qū)動(dòng)處理時(shí)會(huì)有額外的工作決定發(fā)送 LONGVARBINARY
還是 Blob
形式的參數(shù)值給服務(wù)端蹦漠。至于 LONGVARBINARY 和 Blob 有什么區(qū)別?這些類(lèi)型在 Java 端可能就都是字節(jié)數(shù)組车海,但是在數(shù)據(jù)庫(kù)驅(qū)動(dòng)中應(yīng)該進(jìn)行了封裝處理轉(zhuǎn)化為不同的數(shù)據(jù)類(lèi)型笛园,但是我們無(wú)法得知是怎么處理的,因?yàn)轵?qū)動(dòng)一般只對(duì)外提供接口侍芝。
(2)與 clob 相關(guān)的類(lèi)型轉(zhuǎn)換處理器
ClobTypeHandler / NClobTypeHandler / ClobReaderTypeHandler
public class ClobTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
StringReader reader = new StringReader(parameter);
ps.setCharacterStream(i, reader, parameter.length());
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String value = "";
Clob clob = rs.getClob(columnName);
if (clob != null) {
int size = (int) clob.length();
value = clob.getSubString(1, size);
}
return value;
}
// 另外兩個(gè)方法類(lèi)似
【解析】
??參數(shù)綁定時(shí)研铆,先將字符串轉(zhuǎn)化為 Reader 對(duì)象,再綁定參數(shù)州叠,該對(duì)象為給定的字符數(shù)棵红。 當(dāng)將非常大的 UNICODE 值輸入到 LONGVARCHAR 參數(shù)時(shí),通過(guò) java.io.Reader 對(duì)象發(fā)送它可能更實(shí)際留量。 數(shù)據(jù)將根據(jù)需要從流中讀取窄赋,直到到達(dá)文件末尾。 JDBC 驅(qū)動(dòng)程序?qū)?zhí)行從 UNICODE 到數(shù)據(jù)庫(kù)char格式的所有必要轉(zhuǎn)換楼熄。
??NClobTypeHandler
的實(shí)現(xiàn)跟 ClobTypeHandler 完全一樣忆绰,不知道為什么要定義這樣一個(gè)多余的類(lèi)型轉(zhuǎn)換處理器?
??ClobReaderTypeHandler
處理的java數(shù)據(jù)類(lèi)型是 Reader
可岂,不過(guò)用的不是 #setCharacterStream()
错敢,而是 #setClob()
,原理跟上面一樣。
(3)與字節(jié)相關(guān)的類(lèi)型轉(zhuǎn)換處理器
ByteTypeHandler / ByteArrayTypeHandler / ByteObjectArrayTypeHandler
??代碼跟上面類(lèi)似稚茅,不贅述纸淮,這里會(huì)用到一個(gè)用具類(lèi),用于 byte[]
與 Byte[]
之間的互相轉(zhuǎn)換亚享,源碼如下:
class ByteArrayUtils {
private ByteArrayUtils() {
// Prevent Instantiation
}
static byte[] convertToPrimitiveArray(Byte[] objects) {
final byte[] bytes = new byte[objects.length];
for (int i = 0; i < objects.length; i++) {
bytes[i] = objects[i];
}
return bytes;
}
static Byte[] convertToObjectArray(byte[] bytes) {
final Byte[] objects = new Byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
objects[i] = bytes[i];
}
return objects;
}
}
(4)與字符串相關(guān)的類(lèi)型轉(zhuǎn)換處理器
StringTypeHandler / NStringTypeHandler
??兩個(gè)類(lèi)的實(shí)現(xiàn)完全一樣咽块,java類(lèi)型是 String,調(diào)用 PreparedStatement 的 setString欺税、getString
方法處理侈沪。
3、日期時(shí)間相關(guān)的類(lèi)型轉(zhuǎn)換處理器
??先了解一下日期時(shí)間相關(guān)通用 SQL 數(shù)據(jù)類(lèi)型晚凿,列表如下:
類(lèi)型 | 格式 | 用途 |
---|---|---|
DATE |
YYYY-MM-DD | 日期 |
TIME |
HH:MM:SS | 時(shí)間值 |
YEAR |
YYYY | 年份值 |
DATETIME |
YYYY-MM-DD HH:MM:SS | 混合日期和時(shí)間值 |
TIMESTAMP |
YYYY-MM-DD HH:MM:SS | 混合日期和時(shí)間值亭罪,時(shí)間戳 |
(1)日期相關(guān)類(lèi)型轉(zhuǎn)換處理器
DateTypeHandler / DateOnlyTypeHandler / SqlDateTypeHandler
public class DateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
throws SQLException {
// 將 java.util.Date 轉(zhuǎn)化為 java.sql.Timestamp,再設(shè)置到 ps 中
ps.setTimestamp(i, new Timestamp((parameter).getTime()));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName)
throws SQLException {
// 從結(jié)果集中獲得 Timestamp 的值
Timestamp sqlTimestamp = rs.getTimestamp(columnName);
// 將 Timestamp 轉(zhuǎn)化為 Date 類(lèi)型
if (sqlTimestamp != null) {
return new Date(sqlTimestamp.getTime());
}
return null;
}
// 另外兩個(gè)方法類(lèi)似
【解析】
??DateTypeHandler 將 java.util.Date
的 JavaType 轉(zhuǎn)化為 timestamp
的 JdbcType歼秽,綁定參數(shù)時(shí)先轉(zhuǎn)化為 java.sql.Timestamp
应役,再設(shè)置到 ps 中,結(jié)果映射反過(guò)來(lái)燥筷。
??DateOnlyTypeHandler 是 java.util.Date
和 java.sql.Date
之間的轉(zhuǎn)換箩祥,處理類(lèi)似。
??SqlDateTypeHandler 更簡(jiǎn)單了荆责,傳入返回的參數(shù)類(lèi)型就是 java.sql.Date
無(wú)需做額外的類(lèi)型轉(zhuǎn)換滥比。
(2)時(shí)間相關(guān)類(lèi)型轉(zhuǎn)換處理器
TimeOnlyTypeHandler / SqlTimeTypeHandler / SqlTimestampTypeHandler
-
TimeOnlyTypeHandler
:javaType 是java.util.Date
,jdbcType 是time
做院,中間通過(guò)java.sql.Time
轉(zhuǎn)換盲泛。 -
SqlTimeTypeHandler
:javaType 是java.sql.Time
,jdbcType 是time
键耕。 -
SqlTimestampTypeHandler
:javaType 是java.sql.Timestamp
寺滚,jdbcType 是timestamp
。
4屈雄、枚舉相關(guān)的類(lèi)型轉(zhuǎn)換處理器
??MyBatis 內(nèi)置了兩種枚舉類(lèi)型轉(zhuǎn)換處理器 EnumTypeHandler
村视、EnumOrdinalTypeHandler
。
EnumTypeHandler
【功能】將枚舉轉(zhuǎn)化為對(duì)應(yīng)的枚舉類(lèi)型字符串酒奶,再綁定參數(shù)蚁孔;結(jié)果映射時(shí)拿到代表枚舉的字符串,再轉(zhuǎn)化為枚舉類(lèi)型惋嚎。
【源碼與注解】
public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private Class<E> type;
public EnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
// 將 Enum 轉(zhuǎn)換成 String 類(lèi)型
if (jdbcType == null) {
ps.setString(i, parameter.name());
} else {
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 獲得 String 的值
String s = rs.getString(columnName);
// 將 String 轉(zhuǎn)換成 Enum 類(lèi)型
return s == null ? null : Enum.valueOf(type, s);
}
// 另外兩個(gè)重載方法處理類(lèi)似
}
【解析】
??假設(shè)有一個(gè)這樣的枚舉類(lèi)杠氢,定義如下:
public enum SexEnum {
FEMALE(1, "女"),
MALE(0, "男");
private int code;
private String name;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
??上面 parameter.name()
得到的就是一個(gè) "FEMALE"
或 "MALE"
的字符串,存入數(shù)據(jù)表列中也是存儲(chǔ)這么一個(gè)值另伍,結(jié)果映射時(shí)也是用這么一個(gè)字符串調(diào)用 Enum.valueOf()
轉(zhuǎn)化為對(duì)應(yīng)的枚舉類(lèi)型返回鼻百。
EnumOrdinalTypeHandler
【功能】根據(jù)枚舉類(lèi)型獲取在枚舉數(shù)組中的索引位置,再將其作為一個(gè)整數(shù)存儲(chǔ)在數(shù)據(jù)庫(kù)中。
【源碼與注解】
public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private Class<E> type;
private final E[] enums;
public EnumOrdinalTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
// 將 Enum 轉(zhuǎn)換成 int 類(lèi)型
ps.setInt(i, parameter.ordinal());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 獲得 int 的值
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
} else {
try {
// 將 int 轉(zhuǎn)換成 Enum 類(lèi)型
return enums[i];
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
【解析】
??還是上面的枚舉類(lèi)為例子温艇,SexEnum.FEMALE.ordinal()
得到的是 0因悲,保存到數(shù)據(jù)庫(kù)中的就是一個(gè)整數(shù),結(jié)果映射反過(guò)來(lái)勺爱。
5晃琳、對(duì)象類(lèi)型轉(zhuǎn)換處理器
ObjectTypeHandler
【功能】數(shù)據(jù)庫(kù)驅(qū)動(dòng)自動(dòng)根據(jù)傳入的Java對(duì)象類(lèi)型和數(shù)據(jù)表字段類(lèi)型,自動(dòng)轉(zhuǎn)換琐鲁。
【源碼】
public class ObjectTypeHandler extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getObject(columnName);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getObject(columnIndex);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getObject(columnIndex);
}
}
【解析】
??如果數(shù)據(jù)類(lèi)型不明確蝎土,可以調(diào)用 setObject、getObject
方法處理绣否,如果知道數(shù)據(jù)類(lèi)型,優(yōu)先使用指明處理類(lèi)型的方法挡毅。
6蒜撮、UnknownTypeHandler
??如果轉(zhuǎn)換時(shí),參數(shù)綁定傳入的參數(shù)類(lèi)型或者結(jié)果集數(shù)據(jù)類(lèi)型不清晰跪呈,則調(diào)用 UnknownTypeHandler
先處理解析出具體的類(lèi)型段磨,再調(diào)用對(duì)應(yīng)的 TypeHandler 處理。
【源碼分析】
public class UnknownTypeHandler extends BaseTypeHandler<Object> {
// 靜態(tài)變量耗绿,單例共享
private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
// TypeHandler 注冊(cè)表
private TypeHandlerRegistry typeHandlerRegistry;
public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) {
this.typeHandlerRegistry = typeHandlerRegistry;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
// 根據(jù)傳入的參數(shù)和jdbcType獲得對(duì)應(yīng) TypeHandler
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
// 使用處理器綁定參數(shù)
handler.setParameter(ps, i, parameter, jdbcType);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
// 根據(jù)結(jié)果集和列名獲得對(duì)應(yīng)的處理器
TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
// 使用處理器處理結(jié)果映射
return handler.getResult(rs, columnName);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
// 根據(jù)結(jié)果集和列名獲得對(duì)應(yīng)的處理器
TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex);
// 從注冊(cè)表中可能找不到合適的處理器
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
// 使用處理器處理結(jié)果映射
return handler.getResult(rs, columnIndex);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getObject(columnIndex);
}
private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<? extends Object> handler;
// 若參數(shù)值為空苹支,則使用默認(rèn)的 ObjectTypeHandler
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
// 若參數(shù)非空,根據(jù)參數(shù)類(lèi)型從注冊(cè)表中獲取處理器
} else {
handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
// 若沒(méi)有獲得明確類(lèi)型的處理器误阻,則使用默認(rèn)的 ObjectTypeHandler
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
try {
// 獲得結(jié)果集中的元數(shù)據(jù)信息债蜜,并將所有的數(shù)據(jù)列名和位置索引的映射關(guān)系保存在 columnIndexLookup 中
Map<String,Integer> columnIndexLookup;
columnIndexLookup = new HashMap<String,Integer>();
ResultSetMetaData rsmd = rs.getMetaData();
int count = rsmd.getColumnCount();
for (int i=1; i <= count; i++) {
String name = rsmd.getColumnName(i);
columnIndexLookup.put(name,i);
}
// 從 columnIndexLookup 中找到列名對(duì)應(yīng)的位置索引
Integer columnIndex = columnIndexLookup.get(column);
TypeHandler<?> handler = null;
if (columnIndex != null) {
handler = resolveTypeHandler(rsmd, columnIndex);
}
// 若沒(méi)有找到列名對(duì)應(yīng)的索引,則使用默認(rèn)的 ObjectTypeHandler
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
return handler;
} catch (SQLException e) {
throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e);
}
}
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
TypeHandler<?> handler = null;
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
// 根據(jù) JdbcType 和 JavaType 從注冊(cè)表中調(diào)用對(duì)應(yīng)的方法獲得處理器
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
return handler;
}
private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
try {
// 從 ResultSetMetaData 中究反,獲得字段類(lèi)型的 JdbcType
return JdbcType.forCode(rsmd.getColumnType(columnIndex));
} catch (Exception e) {
return null;
}
}
private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
try {
// 從 ResultSetMetaData 中寻定,獲得字段類(lèi)型的 JavaType
return Resources.classForName(rsmd.getColumnClassName(columnIndex));
} catch (Exception e) {
return null;
}
}
}