MyBatis 核心配置講解(上)

王有志發表於2024-04-21

大家好,我是王有志,一個分享硬核 Java 技術的互金摸魚俠。

前兩篇的文章中我們分別介紹了 MyBatisMyBaits 的應用組成,到這裡基礎篇的內容就結束了。

從今天開始,我們正式進入 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 技術的金融摸魚俠王有志,我們下次再見!

相關文章