大家好,我是王有志,一個分享硬核 Java 技術的互金摸魚俠。
前兩篇的文章中我們分別介紹了 MyBatis 和 MyBaits 的應用組成,到這裡基礎篇的內容就結束了。
從今天開始,我們正式進入 MyBatis 學習的第二階段:MyBatis 的應用部分。這個階段從 MyBatis 應用程式的核心配置檔案 mybatis-config.xml 開始入手,逐步推進到對映器(Mapper.xml),動態 SQL 語句的編寫以及 MyBatis 與 Spring 和 Spring Boot 的整合。
在講解 MyBatis 的核心配置檔案時,以及未來講解 MyBatis 的對映器(Mapper.xml)時,會少量涉及到文件型別定義(即 DTD,Document Type Definition)的內容,由於很多小夥伴可能不太熟悉 DTD,因此我會在文末的部分透過一張圖來簡單介紹下 DTD。
Tips:
- 目前 DTD 正在逐漸被功能更強,標準化程度更高的 XSD(XML Schema Definition)取代,因此我們只要簡單瞭解 DTD 即可;
- 點選這裡可以直接下載 MyBatis 核心配置檔案的 DTD。
MyBaits 的核心配置檔案
我們開啟 mybatis-3-configs.dtd 可以看到,該文件最開始定義了 MyBatis 配置中的 configuration 元素及包含的子元素:
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
configuration 元素是 mybatis-config.xml 的根元素,本身並未定義任何屬性,只定義了 11 個子元素,這裡需要注意,子元素定義的順序也是它們在 MyBatis 核心配置檔案 mybatis-config.xml 中使用的順序。
接下來我們按照 DTD 中定義的元素順序,逐個講解元素的用法。由於數量比較多,我會分為兩期和大家分享,本期分享 configuration 的前 5 個子元素:properties,settings,typeAliases,typeHandlers 和 objectFactory。
properties 元素(配置)
properties 元素用於宣告配置,在 DTD 中的定義如下:
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
properties 定義了兩個屬性:resource 屬性和 url 屬性,以及一個子元素 property。
resource 屬性和 url 屬性
properties 元素提供了兩個屬性:resource 和 url,透過它們允許透過其它配置檔案或網路中獲取配置。
屬性 | 是否必填 | 說明 |
---|---|---|
resource | 非必填 | 與屬性 url 互斥 |
url | 非必填 | 與屬性 resource 互斥 |
Tips:屬性 resource 與 url 互斥的原因,我會在屬性與子標籤的優先順序中提到。
使用它們需要提前準備好配置檔案,或能夠透過網路獲取配置,我這裡以使用配置檔案為例。首選我們準備一個配置檔案 mysql-config.properties,將它放在 resources 目錄下,具體內容如下:
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=123456
接著我們修改 MyBatis 入門中示例的 mybatis-config.xml,如下:
<configuration>
<properties resource="mysql-config.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
property 元素
property 元素在 DTD 中的定義如下
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
透過 property 元素可以在 mybatis-config.xml 中宣告配置。
屬性 | 是否必填 | 說明 |
---|---|---|
name | 是 | 無 |
value | 是 | 無 |
在 properties 元素中使用 property 元素無需額外的準備,直接寫在 mybatis-config.xml 中即可,如下:
<configuration>
<properties>
<property name="mysql.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="mysql.username" value="root"/>
<property name="mysql.password" value="123456"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
Tips:在 mybatis-3-config.dtd 中,property 標籤是一個通用元素,會作為很多元素的子元素出現,例如:objectFactory 元素,databaseIdProvider 元素等。
屬性與子元素的混合使用
properties 元素支援屬性與子元素的混合使用。我們稍微修改下外部配置檔案 mysql-config.properties,刪除 mysql.driver 和 mysql.url,如下:
mysql.username=root
mysql.password=123456
接著修改 mybatis-config.xml,刪除透過 property 元素配置的 mysql.username 和 mysql.password,並且引入外部配置檔案 mysql-config.properties,如下:
<configuration>
<properties resource="mysql-config.properties">
<property name="mysql.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
透過測試,你會發現 MyBatis 應用依舊可以良好的運轉。不過這時你可能會產生疑惑,如果我不刪除外部配置檔案 mysql-config.properties 中的配置,也不刪除 property 元素中的配置,到底會是誰說的算?
屬性與子元素的優先順序
先說結論,在 properties 元素中,透過屬性 resource 和 url 獲取的配置優先順序高於 property 元素的配置,如果同時使用屬性和子元素,且存在相同配置名的配置,最終生效的配置是透過屬性獲取到的配置。
這裡涉及到一些原始碼,不過非常簡單,我們在 MyBatis 應用的組成中提到過,MyBatis 中的 mybatis-config.xml 由 XMLConfigBuilder 負責解析,我們能夠非常容易的在 XMLConfigBuilder 中找到解析 properties 的相關的原始碼:
private void propertiesElement(XNode context) throws Exception {
if (context == null) {
return;
}
// 獲取子元素的配置
Properties defaults = context.getChildrenAsProperties();
// 獲取properties的屬性
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// 校驗resource與url是否同時存在
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 獲取透過properties屬性的配置
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 獲取透過configuration的配置
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
原始碼中的第 7 行呼叫了XNode#getChildrenAsProperties
並生成了 Properties 物件,作為預設的配置物件 defaults。
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
接著執行到第 10 行和第 11 行,分別從 properties 元素獲取屬性 resource 和 url 的值,並在 14~16 行校驗了兩者不能同時存在,否則會丟擲異常 BuilderException,這也是 properties 元素的屬性 resource 與 url 互斥的原因。
接著是 19~21 行,從屬性 resource 或 url 中獲取配置,並透過Properties#putAll
方法新增到預設的配置物件 defaults 中。Properties 是 Java 提供的工具類,底層使用的容器是 ConcurrentHashMap,到這裡就能夠解釋為什麼透過屬性 resource 和 url 獲取到配置的優先順序高於透過子元素 property 獲取到的配置了。
最後是 26~29 行的內容,這裡是獲取 Configuration 物件中的配置,並新增到預設配置物件 defaults 中,那麼 Configuration 物件中的配置是如何來的呢?
還記得我們構建 SqlSessionFactory 時呼叫的SqlSessionFactoryBuilder#build
方法嗎?它有非常多的過載方法:
public SqlSessionFactory build(Reader reader);
public SqlSessionFactory build(Configuration config);
public SqlSessionFactory build(Reader reader, Properties properties);
過載方法中允許我們透過 Java 編碼的方式來設定 Properties,這時設定的 Properties 物件會載入到 Configuration 物件中,例如:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
Properties properties = new Properties();
properties.setProperty("mysql.password", "123456");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, properties);
這時生效的配置資訊是透過程式碼設定的 Properties。
綜合上面的內容,我們可以得到 MyBatis 中配置優先順序的完整結論:優先順序最高的是透過程式碼設定的配置,其次是透過 Properties 元素的屬性 resource 和 url 設定的配置,優先順序最低的是透過 Properties 元素的Property 子元素設定的配置。
settings 元素(設定)
settings 元素用於調整 MyBatis 中的各項設定,它在 DTD 中的定義如下:
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
settings 元素沒有屬性,只有一個 setting 子元素,也是典型的 K-V 形式。這也就是說,如果想要調整 MyBatis 應用的設定,必須要使用 setting 子元素。
settings 元素本身沒有什麼特別的,重要的是它提供了非常對的 MyBatis 的配置功能,如下圖:
Tips:以上部分擷取自MyBatis中文網:設定(settings)。
typeAliases 元素(別名)
tyoeAliases 元素用於為 Java 型別設定別名,它在 DTD 中的定義如下:
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
typeAliases 元素本身沒有任何屬性,只有兩個子元素:typeAlias 元素和 package 元素。
typeAlias 元素
typeAlias 元素用於設定單個 Java 型別的別名。
屬性 | 是否必填 | 說明 |
---|---|---|
type | 是 | 使用 Java 型別的全限名 |
alias | 否 | 自定義的別名 |
我們看到,alias 屬性是非必填的,如果沒有填寫的話,MyBatis 會如何處理呢?MyBatis 會預設使用將首字母小寫後 Java 型別的名字作為別名,例如:
<configuration>
<!-- 省略其它配置 -->
<typeAliases>
<typeAlias type="com.wyz.entity.UserDO"/>
</typeAliases>
<!--省略其它配置 -->
</configuration>
此時 UserDO 在 MyBatis 中的別名是 userDO, 配置別名後我們就可以直接在對映器(Mapper.xml)中使用:
<mapper namespace="com.wyz.mapper.UserMapper">
<select id="selectAll" resultType="userDO" >
select user_id, name, age, gender, id_type, id_number from user
</select>
</mapper>
package 元素
package 元素用於設定包下所有 Java 型別的別名。
屬性 | 是否必填 | 說明 |
---|---|---|
name | 是 | 包名 |
如果只使用 package 元素的話,MyBatis 同樣會使用將首字母小寫後的 Java 型別的名字作為別名。但 MyBatis 也提供了@Alias
註解,方便為每個型別起別名,例如:
package com.wyz.entity;
@Alias("user")
public class UserDO implements Serializable {
// 省略屬性
}
接著在 mybatis-config.xml 中配置:
<configuration>
<!-- 省略其它配置 -->
<typeAliases>
<package name="com.wyz.entity"/>
</typeAliases>
<!--省略其它配置 -->
</configuration>
此時,我們就可以在對映器(Mapper.xml)中使用 user 作為 com.wyz.entity.UserDO 的別名了。
MyBatis 內建的別名
上面是自定義別名的部分,實際上 MyBatis 已經為常見的 Java 型別定義了別名,例如:java.lang.String 在 MyBatis 中的別名是“string”,java.lang.Integer 在 MyBatis 中的別名是“int”。
你可能會有疑惑,那基本資料型別 int 有別名嗎?
答案是有的,MyBatis 為 Java 中的每個基礎資料型別都定義了別名,與它們的包裝型別的別名有所差異,我們一起來看一下:
基礎資料型別 | 別名 | 包裝型別 | 別名 |
---|---|---|---|
byte | _byte | Byte | byte |
char | _char/_character | Character | char/character |
long | _long | Long | long |
int | _int/_integer | Integer | int/integer |
short | _short | Short | short |
double | _double | Double | double |
float | _float | Float | float |
boolean | _boolean | Boolean | boolean |
基礎資料型別“痛失真名”~~
當然,以上只是一部分 MyBatis 為 Java 型別定義的別名,更多常見 Java 型別的別名定義可以參考 MyBatis 的原始碼 TypeAliasRegistry 類。
Tips:通常我不使用 typeAliases 標籤,因為使用全限名能夠更加方便的在對映器(Mapper.xml)中查詢到對應的 Java 型別。
typeHandlers 元素(型別處理器)
typeHandlers 元素用於定義型別處理器,即 Java 型別與資料庫型別的相互轉換的處理器,它在 DTD 中的定義如下:
<!ELEMENT typeHandlers (typeHandler*,package*)>
typeHandlers 元素沒有屬性,只定義了兩個子元素:typeHandler 元素和 package 元素。
typeHandler 元素
typeHandler 元素在 DTD 中的定義如下:
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
typeHandler 元素中定義了 3 個屬性:
屬性 | 是否必填 | 說明 |
---|---|---|
javaType | 否 | Java 型別 |
jdbcType | 否 | JDBC 型別 |
handler | 是 | 型別處理器全限名 |
typeHandler 元素,可以定義 Java 型別與 JDBC 型別互相轉換的型別處理器,如:
<configuration>
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="java.lang.String" handler="org.apache.ibatis.type.StringTypeHandler"/>
</typeHandlers>
</configuration>
StringTypeHandler 是 MyBatis 內建的型別處理器,MyBatiis 已經為我們內建了非常多的型別處理器,完全能夠滿足日常專案中的使用了。
Tips:
- 關於 MyBatis 內建處理器,下面會在 MyBatis 的內建處理器的部分提到;
- 關於 typeHandler 元素中 jdbcType 屬性和 javaType 屬性非必填的問題,會在自定義型別處理器的部分提到。
package 元素
在 typeHandlers 元素下使用 package 元素,會載入 package 元素中指定包名下所有符合條件的型別處理器。例如:
<configuration>
<typeHandlers>
<package name="com.wyz.customize.handler.type"/>
</typeHandlers>
</configuration>
如上的定義,會載入 com.wyz.customize.handler.type 中所有符合條件的型別處理器。但是 package 元素只能指定包名,MyBatis 該如何識別型別處理器是哪些 Java 型別與資料庫型別之間的轉換呢?
別急,我們接著往下看。
自定義型別處理器
現在,我們已經知道了如何在 mybatis-config.xml 中配置型別處理器了,接下來我們就定義自己的型別處理器。
MyBatis 中提供了兩種定義型別處理器的方法:
- 實現 TypeHandler 介面;
- 繼承抽象類 BaseTypeHandler。
其中抽象類 BaseTypeHandler 中已經做了非常多的通用實現,採用繼承 BaseTypeHandler 的方法可以省去大量的編碼工作,因此我這裡在實現自定義型別處理器的時候選擇了繼承 BaseTypeHandler。
我們定義一個針對於 String 與 Varchar 的型別處理器,實現一個簡單且無意義的需求:針對於引數和結果集,我們對 String 型別的欄位新增字尾“_wyz”,我將這個型別處理器命名為 NewStringTypeHandler,程式碼如下:
package com.wyz.customize.handler.type;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
public class NewStringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter + "_wyz");
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName) + "_wyz";
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex) + "_wyz";
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex) + "_wyz";
}
}
接下來,我們在 mybatis-config.xml 中配置這個型別處理器:
<configuration>
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="java.lang.String" handler="com.wyz.customize.handler.type.NewStringTypeHandler"/> </typeHandlers>
</configuration>
最後我們透過測試可以看到,所有查詢語句的響應結果中 VARCHAR 型別的欄位都新增了字尾“_wyz”,而所有插入或修改語句在提交後,資料庫中儲存的所有 VARCHAR 型別的欄位也被新增上了字尾“_wyz”。
前面我們已經提到了,typeHandler 元素中的屬性 jdbcType 與 javaType 是非必填的,如果我們不設定 jdbcType 與 javaType 的話會發生什麼?答案是自定義型別處理器依舊會被註冊到 MyBatis 的型別處理器中,但是我們無法使用(涉及到原始碼,這部分我們放在 MyBatis 的原始碼分析篇中再聊)。
那麼為什麼 typeHandler 元素中這兩個屬性是非必填的呢?因為 MyBatis 還提供了兩個註解@MappedTypes
和@MappedJdbcTypes
,允許我們在自定義型別處理器上定義 Java 型別與資料庫型別,如下:
@MappedTypes({String.class})
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class NewStringTypeHandler extends BaseTypeHandler<String> {
// 省略程式碼
}
這樣,我們即便不在 typeHandler 元素中設定屬性 jdbcType 與 javaType,MyBatis 也同樣能夠正確的註冊自定義型別處理器。
再來看 package 元素,雖然沒有元素中沒有設定 jdbcType 與 javaType 的屬性,但是我們想到 package 元素也可以透過@MappedTypes
和@MappedJdbcTypes
註解,來指定 Java 型別與資料庫型別的轉換方式,這裡我們就不再寫相關的程式碼了。
MyBatis 內建的型別處理器
最後我們來看 MyBatis 中內建的型別處理器,在 MyBatis 3.5.15 版本中,MyBatis 為 80 種型別定義了 51 個型別處理器,我們可以透過檢視 BaseTypeHandler 的實現類來檢視 MyBatis 的內建處理器的數量。
為什麼型別與型別處理器的數量不一致呢?
是因為 MyBatis 為了相容 Java 中的基礎型別與包裝型別,以及不同資料庫之間相同型別的叫法不同,需要為每種型別都註冊型別處理器,但他們可以共用同一個型別處理器,例如,在 MyBatis 註冊內建處理器的 TypeHandlerRegistry 的構造方法中為布林型別註冊型別處理器時:
public TypeHandlerRegistry(Configuration configuration) {
// 省略程式碼
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
// 省略程式碼
}
這裡我就不一一展示了,感興趣的小夥伴可以自行檢視 TypeHandlerRegistry 構造方法的原始碼。
objectFactory 元素(物件工廠)
objectFactory 元素用於定義物件工廠,物件工廠用於建立結果集的對映物件,它在 DTD 中的定義如下:
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
objectFactory 元素本身並沒有任何屬性,只定義了一個子元素 property,用於定義 objectFactory 的引數。透過 property 元素配置的屬性,會在 ObjectFactory 初始化之後透過ObjectFactory#setProperties
方法傳遞到物件工廠中。
預設情況下(即不配置自定義物件工廠),MyBatis 會使用物件工廠的唯一實現類 DefaultObjectFactory,它的實現非常簡單,僅僅是透過反射呼叫結果集物件的無參構造器完成了物件的例項化,除此之外,並沒有額外的處理。
自定義物件工廠
同自定義型別處理器一樣,MyBatis 提供了兩種自定義物件工廠的方式:
- 實現 ObjectFactory 介面;
- 繼承 DefaultObjectFactory。
同樣的,我們可以選擇繼承 DefaultObjectFactory 來完成自定義物件工廠,並實現相應的方法即可。接下來,我們寫一個比較“荒誕”的需求,因為我實在是沒想到太好的自定義物件工廠的實際案例。
這個需求是這樣的,為資料庫中 user 表的查詢結果的 VARCHAR 型別欄位新增預設值“wyz”,即 UserDO 例項物件中 String 型別的欄位為 NULL 時賦預設值“wyz”。當然實現這個需求我們有好幾種方式可以選擇,如:透過為資料庫中的欄位設定預設值,或者為 Java 物件的欄位設定預設值來完成。不過這裡為了展示 MyBatis 的自定義物件工廠的使用方法,我透過自定義工廠來實現這個需求。
首先,我們為這個自定義工廠命名為 CustomizeObjectFactory,並在 MyBatis 的核心配置 mybatis-config.xml 中配置這個自定義物件工廠,例如:
<configuration>
<!-- 省略配置 -->
<objectFactory type="com.wyz.customize.factory.object.CustomizeObjectFactory">
<property name="default" value="wyz"/>
</objectFactory>
<!-- 省略配置 -->
</configuration>
其中子元素 property 中定義了我們將要使用的預設值。
接著我們來完成這個自定義物件工廠,透過繼承 DefaultObjectFactory 並實現相應的方法,程式碼如下:
package com.wyz.customize.factory.object;
import com.alibaba.fastjson2.JSON;
import com.wyz.entity.UserDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;
public class CustomizeObjectFactory extends DefaultObjectFactory {
String defaultValue;
@Override
public void setProperties(Properties properties) {
defaultValue = properties.getProperty("default");
}
@Override
public <T> T create(Class<T> type) {
T t = super.create(type);
if(type.equals(UserDO.class)) {
Field[] fields = type.getDeclaredFields();
for(Field field : fields) {
if(field.getType().equals(String.class)) {
field.setAccessible(true);
try {
field.set(t, defaultValue);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
return t;
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
@Override
protected Class<?> resolveInterface(Class<?> type) {
return super.resolveInterface(type);
}
@Override
public <T> boolean isCollection(Class<T> type) {
return super.isCollection(type);
}
}
可以看到,我重寫了ObjectFactory#setProperties
方法,並在該方法中為類成員變數 defaultValue 賦值,前面我們提到過,ObjectFactory#setProperties
方法的作用時間是在物件工廠初始化完成之後。
接著我重寫了其中一個ObjectFactory#create
方法,透過super.create
呼叫父類 DefaultObjectFactory 實現的ObjectFactory#create
方法完成預設的例項化後,我們判斷其型別為 UserDO,並透過反射對 UserDO 例項物件中 String 型別的欄位賦預設值。
我們先把資料庫中的某些欄位修改為 NULL,如:
接著,執行我們的查詢全部使用者的測試案例,可以看到輸出結果中,使用者名稱為小明的使用者,他的性別賦上了預設值“wyz”。
通常來說,我們在應用程式中,MyBatis 提供的 DefaultObjectFactory 已經能夠滿足絕大部分的應用場景了,我們不需要實現自定義物件工廠。
附錄:DTD 簡介
這部分並不是完整的 DTD 教程,如果想要完成的學習 DTD ,請移步相關教程,本文只對 mybatis-3-confg.dtd 中出現的相關語法做一個簡單的介紹。
DTD(Document Type Definition)即文件型別定義,這裡我引用維基百科中關於 DTD 的定義:
XML檔案的文件型別定義(Document Type Definition)可以看成一個或者多個XML檔案的模板,在這裡可以定義XML檔案中的元素、元素的屬性、元素的排列方式、元素包含的內容等等。
DTD 主要用於 XML 文件的結構定義和約束,主要功能包括:
- 定義 XML 文件中的可使用的元素,元素間的順序,元素的巢狀關係和元素的屬性等;
- 用於驗證 XML 文件的結構,在 XML 檔案中引入 DTD,解析器會根據 DTD 的定義驗證文件的結構;
- 約定透過 XML 進行資料交換的標準格式,保證資料交換的正確性。
我們來擷取 mybatis-3-config.dtd 的部分內容:
<!ELEMENT configuration (properties?, settings?)>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
透過一張圖來解釋下上面 DTD 的內容:
關於 DTD 的內容就簡單介紹到這裡,已經足夠應付 MyBatis 的 DTD 了。
本文為稀土掘金技術社群首發簽約文章,30天內禁止轉載,30天后未獲授權禁止轉載,侵權必究!
好了,今天的內容就到這裡了,如果本文對你有幫助的話,希望多多點贊支援,如果文章中出現任何錯誤,還請批評指正。最後歡迎大家關注分享硬核 Java 技術的金融摸魚俠王有志,我們下次再見!