Mybatis 查詢語句結果集總結

DaleZhang發表於2018-08-03

簡單查詢-resultType

資料準備

表 Student

欄位 註釋
SNO 學號
SNAME 學生名字
SSEX 性別
SBIRITHDAY 生日
CLASS 班級
<!--建表語句:-->
create table TEST.STUDENT
(
  SNO       varchar(3) not null,
  SNAME     varchar(4) not null,
  SSEX      varchar(2) not null,
  SBIRTHDAY datetime   null,
  CLASS     varchar(5) null
)
複製程式碼
<!--Bean 檔案-->
public class Student {
    private String SNO;
    private String SNAME;
    private String SSEX;
    private Date SBIRTHDAY;
    private String CLASS;
    
    <!--get 和 set 方法-->
    ...
}

複製程式碼

例子

按照返回資料型別大致分為基礎資料型別,JavaBean 和 Map。其中雖然返回的結果行數有單條也有多條,對應的介面返回型別是集合或者單個物件,但是在 xml 對映檔案中,resultType 的值是相同的。

  1. 指定欄位-基礎資料型別

介面類:

<!--單條結果集-->
String querySingleStudent();
<!--多條結果集-->
List<String> queryAllStudent();
複製程式碼

Mapper 檔案:

<!--單條結果集-->
<select id="querySingleStudent" resultType="string">
    SELECT SNAME FROM TEST.STUDENT LIMIT 1
</select>
<!--多條結果集-->
<select id="queryAllStudent" resultType="string">
    SELECT SNAME FROM TEST.STUDENT
</select>
複製程式碼
  1. Map,一般為 hashMap

介面類:

<!--單條結果集-->
Map<String, Object> queryStudentMap();
<!--多條結果集-->
List<Map<String, Object>> queryAllStudentMap();
複製程式碼

Mapper 檔案:

<!--單條結果集-->
<select id="queryStudentMap" resultType="hashmap">
    SELECT SNAME FROM TEST.STUDENT LIMIT 1
</select>
<!--多條結果集-->
<select id="queryAllStudentMap" resultType="hashmap">
    SELECT SNAME FROM TEST.STUDENT
</select>
複製程式碼

其中:

  • hashmap 為簡寫,也可以使用 java.util.HashMap 全稱
  • 預設情況下,結果集中值為 null 時, 不會增加對映物件的 setter 方法, (map 物件時為 put)。該行為可以在 mybatis-config.xml 配置檔案中設定 <setting name="callSettersOnNulls" value="true"/> 覆蓋預設設定。
  1. JavaBean

介面類:

<!--單條結果集-->
Student querySingleStudentBean();
<!--多條結果集-->
List<Student> queryAllStudentBean();
複製程式碼

Mapper 檔案:

<!--單條結果集-->
<select id="queryStudentMap" resultType="student">
    SELECT SNAME FROM TEST.STUDENT LIMIT 1
</select>
<!--多條結果集-->
<select id="queryAllStudentMap" resultType="student">
    SELECT SNAME FROM TEST.STUDENT
</select>
複製程式碼
  • resultType="student" 為 Student.java 的別名,也可以是全限定名。別名在 mybatis-config.xml 配置檔案中設定:
<typeAliases>
    <typeAlias type="com.bean.Student" alias="student"/>
    ...
</typeAliases>
複製程式碼

但是如果 JavaBean 檔案很多,不想一個個指定,也可以使用 package 標籤 設定mybatis自動掃描,別名即為類名的小寫。

<typeAliases>
    <package name="包名"/>
</typeAliases>
複製程式碼

複雜查詢 resultMap

對於一般的查詢語句,resultType 足夠了。對於多表查詢等情況,就要請出 resultMap 了。

資料庫欄位和 java 資料型別對映關係

資料庫欄位型別 jdbcType 和 java 資料型別 並不是一一對應的關係,而且不同資料庫型別也不盡相同。而 mybatis 將 TypeHandler 作為兩者之間的對映關係。大部分情況下都是沒有問題的,但是並非能覆蓋所有的情況,特殊情況下可以使用 resultMap 自定義這種對映關係。

舉個例子,資料庫 LongVarchar 欄位型別對應 java 中的 String 型別。但是在 DB2 資料庫中,查詢的 LongVarchar 型別的欄位,在 mybatis 中被識別成 jdbcType 為 BLOB。有兩種解決方法,第一種是在 SQL 中對該欄位使用 CAST 轉換為 VARCHAR(長度)型別。另一種是使用 resultMap:

<resultMap id="resultMapDemo" type="" autoMapping="true">
    <result property="" column="" jdbcType="VARCHAR" />
</resultMap>

<select id="demoID" resultMap="resultMapDemo">
...
<select>
複製程式碼
  1. <select>標籤中使用 resultMap 指定返回集合。注意 resultMap 和 resultType 不能同時使用
  2. <resultMap> 標籤
    • id 和 select 標籤指定對映關係
    • type 和 resultType 一樣為返回型別的全限定名或者別名
    • autoMapping 自動對映關係,在這裡目的只是修改一個欄位,其他自動採用自動完成對映關係
  3. <result> 標籤
    • property 為 java 變數名
    • column 為資料庫欄位名
    • jdbcType 這裡指定為 VARCHAR

id

欄位的對映關係的標籤即有,也有,在 mybatis 文件中指出不使用id,會造成效能下降,因此將主鍵欄位使用 id 標籤是推薦的做法。但是如果不存在主鍵呢,當你在 ResultMap 只提供了部分欄位而不是全部欄位,即使使用了 autoMapping 屬性,那麼 mybatis 會按照你提供的欄位名進行去重。那麼在使用 resultMap 的時候,最優選擇是:

  1. 如果表存在主鍵,就使用id標籤指定
  2. 如果表不存在主鍵,要麼不配置欄位的對映關係,使用 autoMapping 屬性自動對映;或者不使用 autoMapping 將所有欄位羅列。

多表關聯查詢

在 resultType 的例子中都只涉及到一張表,如果涉及多張表關聯查詢呢。我們可以簡單的將所有列對映到 hashmap 的鍵值上。

但是 HashMap 不是一個很好的領域模型。 你的程式更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通 Java 物件)作為領域模型。

因此這裡均採用 JavaBean 作為領域模型。增加一個成績表 Score

欄位 註釋
SNO 學號
CNO 課程編號
DEGREE 成績
<!--建表語句-->
create table SCORE
(
  SNO    varchar(3)     not null,
  CNO    varchar(5)     not null,
  DEGREE decimal(10, 1) not null
)
複製程式碼
<!--Bean 檔案-->
public class Score {
    private String SNO;
    private String CNO;
    private Double DEGREE;
    <!--get 和 set 方法-->
    ...
}
複製程式碼

一對一關係

這裡的一對多關係是兩個表欄位一一對應,一個學生的某門課的成績是唯一確定的。 在一一對應的情況下要在 resultMap 中使用 標籤。

在 Student.java 中增加欄位 Score

<!--Student.java-->
private Score score;
public Score getScore() {
    return score;
}
public void setScore(Score score) {
    this.score = score;
}
複製程式碼

有兩種使用情況,第一種為巢狀查詢,即前一個 SQL 查詢結果集中的欄位作為引數傳遞給下一個 SQL。第二種情況為巢狀結果集,即兩個表做關聯查詢,將結果集對映到多個 JavaBean 檔案。

  • 巢狀查詢
<resultMap id="allstudentResultMap" type="student">
    <!--指定第二個 SELECT 語句,和傳遞的欄位-->
    <association property="score" column="sno" select="queryScore" />
</resultMap>

<!--第一個 SQL -->
<select id="queryAllStudent" resultMap="allstudentResultMap">
    select SNO,SNAME
    from test.STUDENT
</select>

<!--第二個 SQL-->
<select id="queryScore" resultType="score">
    select degree from test.SCORE
    where sno = #{sno}
</select>
複製程式碼

在標籤中

  1. property 指向了 Student.java 中新增的 score 欄位。
  2. column 指定了作為引數傳遞給下一個查詢SQL的欄位,需要注意的是對於傳遞單個欄位的情況,mybatis 只是簡單的將 #{引數} 替換為佔位符 ?, 然後執行 resultSet.getString(columnName),沒有進行引數匹配,因此第二個 SQL 中 #{} 中寫任何字元都可以;如果需要傳遞多個欄位,使用 column = " {prop1=col1,prop2=col2} ",這種情況下會以引數物件的形式來傳遞。
  3. select 指定了下一個 SELECT 語句

另外需要注意的是這種巢狀查詢對於大型結果集和列名並友好,存在 N+1 的問題,因為下一條 SQL 會執行 N 次去迴圈查詢,使用關聯查詢更合適。再者也可以開啟 mybatis 的懶查詢功能,巢狀的 SQL 不是一口氣順序執行完,而是在使用的時候才會執行下一條 SQL。例如執行student.getScore().getSNO()才會執行queryScore的 SQL。預設情況下沒有開啟,需要在配置檔案中設定

設定引數 描述 預設值
lazyLoadingEnabled 延遲載入的全域性開關,特定關聯關係中可通過設定 fetchType 屬性來覆蓋該項的開關狀態 false
aggressiveLazyLoading 當開啟時,任何方法的呼叫都會載入該物件的所有屬性。否則,每個屬性會按需載入 false (true in ≤3.4.1)
<!--mybatis-config.xml-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
複製程式碼

也可以在標籤中設定 fetchType = “lazy” 開啟懶載入,會覆蓋全域性的引數設定。

  • 巢狀結果集

對於多表關聯查詢,一般在 SQL 中使用別名來避免欄位名的重複。mybatis 要做的是將別名正確的對映到 JavaBean 屬性上。

<!--巢狀結果-->
<resultMap id="associationDemoMap" type="student" autoMapping="true">
    <association property="score" javaType="score">
        <result property="SNO" column="SC_SNO"/>
    </association>
</resultMap>

<select id="queryStudentScore" resultMap="associationDemoMap">
    SELECT SNAME, SSEX, CLASS, ST.SNO, SC.SNO AS SC_SNO
    FROM test.student st INNER JOIN test.score sc 
    ON st.sno = sc.sno
    where CNO = '3-105';
</select>
複製程式碼

通過設定標籤指定了表列名和屬性之間的對映關係。但這樣如果欄位很多,會需要一一指定,標籤提供了columnPrefix屬性,指定別名的字首,這樣可以重用resultMap

<resultMap id="associationDemoMap" type="student" autoMapping="true">
    <!--columnPrefix 指定別名的字首-->
    <association property="score" resultMap="anotherMap" columnPrefix="SC_" />
</resultMap>

<!--方便重用-->
<resultMap id="anotherMap" type="score" autoMapping="true">
</resultMap>
複製程式碼

一對多關係

除了一對一的關係,還有一對多的關係,比如這裡一個學生Student 對應多門課的成績。 一對多對應的情況下要在 resultMap 中使用 標籤。首先需要調整 JavaBean 檔案中兩個表之間的關係。

<!--Student.java-->
private List<Score> score;
public List<Score> getScore() {
    return score;
}
public void setScore(List<Score> score) {
    this.score = score;
}
複製程式碼

以巢狀結果集為例

<resultMap id="collectionDemoMap" type="student" autoMapping="true">
    <!--多出了 ofType 屬性-->
    <collection property="score" ofType="score">
        <result property="SNO" column="SC_SNO"/>
    </collection>
</resultMap>

<select id="queryStudentScore" resultMap="collectionDemoMap">
    SELECT SNAME,SSEX,CLASS,ST.SNO,SC.SNO AS SC_SNO
    FROM test.student st INNER JOIN test.score sc
    ON st.sno = sc.sno
</select>
複製程式碼

注意到相比 association 多了一個屬性ofType,是用來表示 List 集合中的型別的。其他屬性的用法同 association 是一樣的。

相關文章