mybatis的高級結果映射可以很輕松的幫助我們處理一對一, 一對多的數據關系。
1 數據准備
1.1 數據庫
創建以下的名為 mybatis 的數據庫, 並在其下創建4個表。
在此就不貼出來建表的 SQL 語句了 , 感興趣的可以去我的 Github:mybatis-mapping 中獲取。
1.2 實體類, 接口和XML
使用 mybatis-代碼生成器 生成相應的實體類, 接口和XML。
以上為生成的項目結構。
2 一對一映射
創建的表中, author 和 blog 就是一對一的關系。
我們希望找到一個blog, 然后就會把它的作者信息也關聯出來。
2.1 resultType 方式一
注意:resultType方式要求獲取到的列和成員變量名一致。
2.1.1 創建對象
創建一個類BlogCustom
, 該類繼承於 Blog
類, 在該類上添加 Author
對象作為成員變量
2.1.2 創建接口方法和XML 語句
BlogBO selectBoById(int id);
/**
* 根據博客的 id 獲取博客及作者的信息
* @param id
* @return
*/
BlogCustom selectCutomById(int id);
對應的 XML 語句:
<select id="selectCutomById" parameterType="java.lang.Integer" resultType="com.homejim.mybatis.entity.BlogCustom">
SELECT
b.id,
b.title,
b.author_id AS authorId,
a.id AS "author.id",
a.username AS "author.username",
a.password AS "author.password" ,
a.email AS "author.email"
FROM blog b LEFT JOIN author a ON b.author_id=a.id
where b.id = #{id,jdbcType=INTEGER}
</select>
通過 author.username 這種方式可以設定 author
對象中的 username 屬性。
2.1.3 測試
@Test
public void testSelectCustomById() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogCustom blogCustom = blogMapper.selectCutomById(1);
System.out.println(ToStringBuilder.reflectionToString(blogCustom, ToStringStyle.MULTI_LINE_STYLE));
}
運行后的結果
有時候, 我們獲取的另外的屬性不多, 則我們也可以選擇下面的方式二。
2.2 resultType 方式二
2.2.1 創建對象
創建一個類BlogBO
, 該類繼承於 Blog
類, 在該類上添加 Author
中的成員變量作為該類自己的成員變量。
2.2.2 創建接口方法和XML 語句
接口方法
/**
* 根據博客的 id 獲取博客及作者的信息
* @param id
* @return
*/
BlogBO selectBoById(int id);
XML 對應的 SQL , 其中 resultType 是上面定義的實體對象。
<select id="selectBoById" parameterType="java.lang.Integer" resultType="com.homejim.mybatis.entity.BlogBO">
select
b.id, b.title, b.author_id as authorId, a.id as userId, a.username, a.email
from blog b left join author a on b.author_id=a.id
where b.id = #{id,jdbcType=INTEGER}
</select>
2.2.3 測試
創建一個測試例子。
@Test
public void testSelectBoById() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogBO blogBO = blogMapper.selectBoById(1);
System.out.println(ToStringBuilder.reflectionToString(blogBO, ToStringStyle.MULTI_LINE_STYLE));
}
測試結果
2.3 resultMap 方式
2.3.1 創建對象
創建一個類BlogCustom
, 該類繼承於 Blog
類, 在該類上添加 Author
對象作為成員變量
2.3.2 創建對應 resultMap
對應創建一個 resultMap
<resultMap id="BOResultMap" type="com.homejim.mybatis.entity.BlogCustom" extends="BaseResultMap">
<result column="username" jdbcType="VARCHAR" property="author.username" />
<result column="user_id" jdbcType="VARCHAR" property="author.id" />
<result column="email" jdbcType="VARCHAR" property="author.email" />
</resultMap>
2.3.3 創建接口方法和XML 語句
/**
* 根據博客的 id 獲取博客及作者的信息, resultMap 方式
* @param id
* @return
*/
BlogCustom selectCutomByIdMap(int id);
SQL 語句得出的列名與 BOResultMap 一致。
<select id="selectCutomByIdMap" parameterType="java.lang.Integer" resultMap="BOResultMap">
SELECT
b.id,
b.title,
b.author_id AS authorId,
a.id AS "user_id",
a.username,
a.email
FROM blog b LEFT JOIN author a ON b.author_id=a.id
where b.id = #{id,jdbcType=INTEGER}
</select>
2.3.4 測試
/**
* resultMap 方式一測試
*/
@Test
public void testSelectCustomByIdMap() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogCustom blogCustom = blogMapper.selectCutomByIdMap(1);
System.out.println(ToStringBuilder.reflectionToString(blogCustom, ToStringStyle.MULTI_LINE_STYLE));
}
得出的結果
其實該類型也可以配置對應 BlogBO
類對應的方式, 在此不做過多的講解。
2.4 resultMap + association 方式
2.4.1 創建對象
創建一個類BlogCustom
, 該類繼承於 Blog
類, 在該類上添加 Author
對象作為成員變量
2.4.2 創建 resultMap
<resultMap id="CustomResultMap" type="com.homejim.mybatis.entity.BlogCustom" extends="BaseResultMap">
<association property="author" javaType="com.homejim.mybatis.entity.Author">
<id column="user_id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="email" jdbcType="VARCHAR" property="email" />
</association>
</resultMap>
或者, 可以引用別的 Mapper 中的結果集。
<resultMap id="CustomResultMap" type="com.homejim.mybatis.entity.BlogCustom" extends="BaseResultMap">
<association property="author" javaType="com.homejim.mybatis.entity.Author" resultMap="com.homejim.mybatis.mapper.AuthorMapper.BaseResultMap">
</association>
</resultMap>
2.4.3 創建接口方法和XML 語句
/**
* 根據博客的 id 獲取博客及作者的信息, resultMap + association方式
* @param id
* @return
*/
BlogCustom selectCutomByIdAMap(int id);
SQL 語句
<select id="selectCutomByIdAMap" parameterType="java.lang.Integer" resultMap="CustomResultMap">
SELECT
b.id,
b.title,
b.author_id AS authorId,
a.id AS "user_id",
a.username,
a.email
FROM blog b LEFT JOIN author a ON b.author_id=a.id
where b.id = #{id,jdbcType=INTEGER}
</select>
2.4.4 測試
/**
* resultMap + association 方式測試
*/
@Test
public void testSelectCustomByIdAMap() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogCustom blogCustom = blogMapper.selectCutomByIdAMap(1);
System.out.println(ToStringBuilder.reflectionToString(blogCustom, ToStringStyle.MULTI_LINE_STYLE));
}
結果
2.5 resultMap + association 嵌套查詢
以上幾種方法都是通過 left join 的 SQL 語句獲取多個對象的結果。 還可以經過簡單的 SQL 語句, 多次查詢而轉化為我們需要的結果。
2.5.1
2.5.1 創建對象
創建一個類BlogCustom
, 該類繼承於 Blog
類, 在該類上添加 Author
對象作為成員變量
2.5.2 創建 resultMap
<resultMap id="blogAuthorMap" type="com.homejim.mybatis.entity.BlogCustom" extends="BaseResultMap">
<association property="author" column="author_id" select="com.homejim.mybatis.mapper.AuthorMapper.selectById" fetchType="eager" />
</resultMap>
該結果集與之前的不同了, association 標簽中使用了 select 屬性。
select: 另一個查詢的 id, mybatis 會額外執行這個查詢來獲取嵌套的結果
column: 列名(別名), 將主查詢中的列作為嵌套查詢的參數, 多個參數使用逗號(英文)分隔開。
fetchType: 數據加載的方式, 可選擇為 lazy(延遲加載) 或者 eager(積極加載), 該配置會覆蓋全局的 lazyLoadingEnabled 配置。
2.5.3 創建接口方法和XML 語句
/**
* 根據博客的 id 獲取博客及作者的信息, resultMap + association嵌套方式
* @param id
* @return
*/
BlogCustom selectBlogAndAuthorByIdSelect(int id);
獲取的數據是博客和用戶的信息, 以下的 SQL 只是獲取博客信息, 用戶信息通過 com.homejim.mybatis.mapper.AuthorMapper.selectById
獲取。
<select id="selectBlogAndAuthorByIdSelect" parameterType="java.lang.Integer" resultMap="blogAuthorMap">
SELECT
b.id,
b.title,
b.author_id
FROM blog b
where b.id = #{id,jdbcType=INTEGER}
</select>
com.homejim.mybatis.mapper.AuthorMapper.selectById
是一個全限定名, 即 AuthorMapper
下的 selectById 方法
/**
* 嵌套查詢使用的方法
* @param id
* @return
*/
Author selectById(Integer id);
對應的 SQL
<select id="selectById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from author
where id = #{id}
</select>
2.5.4 測試
使用時, 調用 selectBlogAndAuthorByIdSelect
方法即可。
/**
* resultMap + association 嵌套查詢方式測試
*/
@Test
public void testSelectBlogAndAuthorByIdSelect() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogCustom blogCustom = blogMapper.selectBlogAndAuthorByIdSelect(1);
System.out.println(ToStringBuilder.reflectionToString(blogCustom, ToStringStyle.MULTI_LINE_STYLE));
Assert.assertNotNull(blogCustom);
Assert.assertNotNull(blogCustom.getAuthor());
}
輸出, 會發送兩次 SQL 語句。
可以看到, 上面的結果示意圖中, 發送了兩次 SQL 。
2.5.5 延遲加載
如果是一個對象中只是包含一兩個對象, 使用上面的方式還好。 但是如果包含有很多, 那要一次性發送很多次 SQL, 性能上就會很有影響。延遲加載可以解決此類的問題。
延遲加載就是說,只有在調用內部的對象時, 才會把獲取該對象的 SQL 發送出去。
更改結果集
<resultMap id="blogAuthorMapLazy" type="com.homejim.mybatis.entity.BlogCustom" extends="BaseResultMap">
<association fetchType="lazy" property="author" column="author_id" select="com.homejim.mybatis.mapper.AuthorMapper.selectById" />
</resultMap>
將上面的查詢中的結果集更改 resultMap="blogAuthorMapLazy"
<select id="selectBlogAndAuthorByIdSelect" parameterType="java.lang.Integer" resultMap="blogAuthorMapLazy"> <!--resultMap="blogAuthorMap"-->
SELECT
b.id,
b.title,
b.author_id
FROM blog b
where b.id = #{id,jdbcType=INTEGER}
</select>
更改延遲加載總開關
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
測試
注意: 延遲加載是在
SqlSession
的聲明周期內的, 如果超出該聲明周期, 如 spring 中, 只能在 Service 層使用延遲加載的對象, 如果返回Controller層在獲取延遲加載屬性, 則會拋出異常。
有時候, 我們配置了延遲加載, 但是卻想要一次性加載, 怎么辦?
有一個配置屬性可以幫我們解決 lazyLoadTriggerMethods, 它的默認配置如下:
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
就是說我們使用上面配置中的任何一個方法(上面的是默認的, 我們可以不配置), 就可以加載屬性啦。
測試
/**
* resultMap + association 嵌套查詢方式測試(延遲加載不延遲lazyLoadTriggerMethods)
*/
@Test
public void testSelectBlogAndAuthorByIdSelectTrigger() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
BlogCustom blogCustom = blogMapper.selectBlogAndAuthorByIdSelect(1);
blogCustom.equals(null);
Assert.assertNotNull(blogCustom);
sqlSession.close();
System.out.println("開始使用author對象");
Assert.assertNotNull(blogCustom.getAuthor());
}
結果
3. 代碼
本來還要寫的一對多, 鑒別器的, 但由於篇幅的原因, 后續繼續吧。
一起學 mybatis
你想不想來學習 mybatis? 學習其使用和源碼呢?那么, 在博客園關注我吧!!
我自己打算把這個源碼系列更新完畢, 同時會更新相應的注釋。快去 star 吧!!