mybatis深入淺出--進階知識實戰
1資料庫表分析
資料模型分析思路
1、每張表記錄的資料内容
分子產品對每張表記錄的内容進行熟悉,相當 于你學習系統 需求(功能)的過程。
2、每張表重要的字段設定
非空字段、外鍵字段
3、資料庫級别表與表之間的關系
外鍵關系
4、表與表之間的業務關系
在分析表與表之間的業務關系時一定要建立 在某個業務意義基礎上去分析。
使用者表user:
記錄了購買商品的使用者資訊
訂單表:orders
記錄了使用者所建立的訂單(購買商品的訂單)
訂單明細表:orderdetail:
記錄了訂單的詳細資訊即購買商品的資訊
商品表:items
記錄了商品資訊
2 一對一查詢
-
resultType
sql
SELECT
orders.*,
USER.username,
USER.sex,
USER.address
FROM
orders,
USER
WHERE orders.user_id = user.id
pojo
将上邊sql查詢的結果映射到pojo中,pojo中必須包括所有查詢列名。
原始的Orders.java不能映射全部字段,需要新建立的pojo。
建立 一個pojo繼承包括查詢字段較多的po類。
mapper.xml
mapper.java
2 resultMap
使用resultMap将查詢結果中的訂單資訊映射到Orders對象中,在orders類中添加User屬性,将關聯查詢出來的使用者資訊映射到orders對象中的user屬性中。
pojo
mapper.xml
<!-- 訂單查詢關聯使用者的resultMap
将整個查詢的結果映射到cn.itcast.mybatis.po.Orders中
-->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserResultMap">
<!-- 配置映射的訂單資訊 -->
<!-- id:指定查詢列中的唯 一辨別,訂單資訊的中的唯 一辨別,如果有多個列組成唯一辨別,配置多個id
column:訂單資訊的唯 一辨別 列
property:訂單資訊的唯 一辨別 列所映射到Orders中哪個屬性
-->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property=note/>
<!-- 配置映射的關聯的使用者資訊 -->
<!-- association:用于映射關聯查詢單個對象的資訊
property:要将關聯查詢的使用者資訊映射到Orders中哪個屬性
-->
<association property="user" javaType="cn.itcast.mybatis.po.User">
<!-- id:關聯查詢使用者的唯 一辨別
column:指定唯 一辨別使用者資訊的列
javaType:映射到user的哪個屬性
-->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
mapper.java
3resultType和resultMap實作一對一查詢小結
實作一對一查詢:
resultType:使用resultType實作較為簡單,如果pojo中沒有包括查詢出來的列名,需要增加列名對應的屬性,即可完成映射。
如果沒有查詢結果的特殊要求建議使用resultType。
resultMap:需要單獨定義resultMap,實作有點麻煩,如果對查詢結果有特殊的要求,使用resultMap可以完成将關聯查詢映射pojo的屬性中。
resultMap可以實作延遲加載,resultType無法實作延遲加載。
3 一對多查詢
1 sql
SELECT
orders.*,
USER.username,
USER.sex,
USER.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
orderdetail.orders_id
FROM
orders,
USER,
orderdetail
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id
2存在的問題
使用resultType将上邊的 查詢結果映射到pojo中,訂單資訊的就是重複。
要求:
對orders映射不能出現重複記錄。
在orders.java類中添加List<orderDetail> orderDetails屬性。
最終會将訂單資訊映射到orders中,訂單所對應的訂單明細映射到orders中的orderDetails屬性中。
映射成的orders記錄數為兩條(orders資訊不重複)
每個orders中的orderDetails屬性存儲了該 訂單所對應的訂單明細。
3 po
4 mapper.xml
5 resultMap定義
<!-- 訂單及訂單明細的resultMap
使用extends繼承,不用在中配置訂單資訊和使用者資訊的映射
-->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersAndOrderDetailResultMap" extends="OrdersUserResultMap">
<!-- 訂單資訊 -->
<!-- 使用者資訊 -->
<!-- 使用extends繼承,不用在中配置訂單資訊和使用者資訊的映射 -->
<!-- 訂單明細資訊
一個訂單關聯查詢出了多條明細,要使用collection進行映射
collection:對關聯查詢到多條記錄映射到集合對象中
property:将關聯查詢到多條記錄映射到cn.itcast.mybatis.po.Orders哪個屬性
ofType:指定映射到list集合屬性中pojo的類型
-->
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<!-- id:訂單明細唯 一辨別
property:要将訂單明細的唯 一辨別 映射到cn.itcast.mybatis.po.Orderdetail的哪個屬性
-->
<id column="orderdetail_id" property="id"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<result column="orders_id" property="ordersId"/>
</collection>
</resultMap>
6 mapper.java
7 小結
mybatis使用resultMap的collection對關聯查詢的多條記錄映射到一個list集合屬性中。
使用resultType實作:
将訂單明細映射到orders中的orderdetails中,需要自己處理,使用雙重循環周遊,去掉重複記錄,将訂單明細放在orderdetails中。
4 多對多查詢
1 需求
查詢使用者及使用者購買商品資訊
查詢主表是:使用者表
關聯表:由于使用者和商品沒有直接關聯,通過訂單和訂單明細進行關聯,是以關聯表:
orders、orderdetail、items
2 sql
SELECT
orders.*,
USER.username,
USER.sex,
USER.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
orderdetail.orders_id,
items.name items_name,
items.detail items_detail,
items.price items_price
FROM
orders,
USER,
orderdetail,
items
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id
3 映射思路
将使用者資訊映射到user中。
在user類中添加訂單清單屬性List<Orders> orderslist,将使用者建立的訂單映射到orderslist
在Orders中添加訂單明細清單屬性List<OrderDetail>orderdetials,将訂單的明細映射到orderdetials
在OrderDetail中添加Items屬性,将訂單明細所對應的商品映射到Items
4 mapper.xml
5 resultMap定義
<!-- 查詢使用者及購買的商品 -->
<resultMap type="cn.itcast.mybatis.po.User" id="UserAndItemsResultMap">
<!-- 使用者資訊 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<!-- 訂單資訊
一個使用者對應多個訂單,使用collection映射
-->
<collection property="ordersList" ofType="cn.itcast.mybatis.po.Orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 訂單明細
一個訂單包括 多個明細
-->
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<result column="orders_id" property="ordersId"/>
<!-- 商品資訊
一個訂單明細對應一個商品
-->
<association property="items" javaType="cn.itcast.mybatis.po.Items">
<id column="items_id" property="id"/>
<result column="items_name" property="name"/>
<result column="items_detail" property="detail"/>
<result column="items_price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
6 mapper.java
5 小結
使用resultMap是針對那些對查詢結果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多個list。
resultType:
作用:
将查詢結果按照sql列名pojo屬性名一緻性映射到pojo中。
場合:
常見一些明細記錄的展示,比如使用者購買商品明細,将關聯查詢資訊全部展示在頁面時,此時可直接使用resultType将每一條記錄映射到pojo中,在前端頁面周遊list(list中是pojo)即可。
resultMap:
使用association和collection完成一對一和一對多進階映射(對結果有特殊的映射要求)。
6 延遲加載
1 什麼是延遲加載
resultMap可以實作進階映射(使用association、collection實作一對一及一對多映射),association、collection具備延遲加載功能。
需求:
如果查詢訂單并且關聯查詢使用者資訊。如果先查詢訂單資訊即可滿足要求,當我們需要查詢使用者資訊時再查詢使用者資訊。把對使用者資訊的按需去查詢就是延遲加載。
延遲加載:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高 資料庫性能,因為查詢單表要比關聯查詢多張表速度要快。
2 使用association實作延遲加載
1 需求
查詢訂單并且關聯查詢使用者資訊
2 mapper.xml
需要定義兩個mapper的方法對應的statement。
1、隻查詢訂單資訊
SELECT * FROM orders
在查詢訂單的statement中使用association去延遲加載(執行)下邊的satatement(關聯查詢使用者資訊)
2、關聯查詢使用者資訊
通過上邊查詢到的訂單資訊中user_id去關聯查詢使用者資訊
使用UserMapper.xml中的findUserById
上邊先去執行findOrdersUserLazyLoading,當需要去查詢使用者的時候再去執行findUserById,通過resultMap的定義将延遲加載執行配置起來。
3 延遲加載resultMap
使用association中的select指定延遲加載去執行的statement的id。
<!-- 延遲加載的resultMap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserLazyLoadingResultMap">
<!--對訂單資訊進行映射配置 -->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 實作對使用者資訊進行延遲加載
select:指定延遲加載需要執行的statement的id(是根據user_id查詢使用者資訊的statement)
要使用userMapper.xml中findUserById完成根據使用者id(user_id)使用者資訊的查詢,如果findUserById不在本mapper中需要前邊加namespace
column:訂單資訊中關聯使用者資訊查詢的列,是user_id
關聯查詢的sql了解為:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username,
(SELECT sex FROM USER WHERE orders.user_id = user.id)sex
FROM orders
-->
<association property="user" javaType="cn.itcast.mybatis.po.User"
select="cn.itcast.mybatis.mapper.UserMapper.findUserById" column="user_id">
<!-- 實作對使用者資訊進行延遲加載 -->
</association>
</resultMap>
4 mapper.java
5 測試
1、執行上邊mapper方法(findOrdersUserLazyLoading),内部去調用cn.itcast.mybatis.mapper.OrdersMapperCustom中的findOrdersUserLazyLoading隻查詢orders資訊(單表)。
2、在程式中去周遊上一步驟查詢出的List<Orders>,當我們調用Orders中的getUser方法時,開始進行延遲加載。
3、延遲加載,去調用UserMapper.xml中findUserbyId這個方法擷取使用者資訊。
6 延遲加載配置
mybatis預設沒有開啟延遲加載,需要在SqlMapConfig.xml中setting配置。
在mybatis核心配置檔案中配置:
lazyLoadingEnabled、aggressiveLazyLoading
設定項 | 描述 | 允許值 | 預設值 |
lazyLoadingEnabled | 全局性設定懶加載。如果設為‘false’,則所有相關聯的都會被初始化加載。 | true | false | false |
aggressiveLazyLoading | 當設定為‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。 | true | false | true |
在SqlMapConfig.xml中配置:
7 測試代碼
7 查詢緩存
1 什麼是查詢緩存
mybatis提供查詢緩存,用于減輕資料壓力,提高資料庫性能。
mybaits提供一級緩存,和二級緩存。
一級緩存是SqlSession級别的緩存。在操作資料庫時需要構造 sqlSession對象,在對象中有一個資料結構(HashMap)用于存儲緩存資料。不同的sqlSession之間的緩存資料區域(HashMap)是互相不影響的。
二級緩存是mapper級别的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
為什麼要用緩存?
如果緩存中有資料就不用從資料庫中擷取,大大提高系統性能。
2 一級緩存
1 一級緩存工作原理
第一次發起查詢使用者id為1的使用者資訊,先去找緩存中是否有id為1的使用者資訊,如果沒有,從資料庫查詢使用者資訊。
得到使用者資訊,将使用者資訊存儲到一級緩存中。
如果sqlSession去執行commit操作(執行插入、更新、删除),清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的資訊,避免髒讀。
第二次發起查詢使用者id為1的使用者資訊,先去找緩存中是否有id為1的使用者資訊,緩存中有,直接從緩存中擷取使用者資訊。
2一級緩存測試
mybatis預設支援一級緩存,不需要在配置檔案去配置。
按照上邊一級緩存原理步驟去測試。
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//建立代理對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下邊查詢使用一個SqlSession
//第一次發起請求,查詢id為1的使用者
User user1 = userMapper.findUserById(1);
System.out.println(user1);
// 如果sqlSession去執行commit操作(執行插入、更新、删除),清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的資訊,避免髒讀。
//更新user1的資訊
user1.setUsername("測試使用者22");
userMapper.updateUser(user1);
//執行commit操作去清空緩存
sqlSession.commit();
//第二次發起請求,查詢id為1的使用者
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
3一級緩存應用
正式開發,是将mybatis和spring進行整合開發,事務控制在service中。
一個service方法中包括 很多mapper方法調用。
service{
//開始執行時,開啟事務,建立SqlSession對象
//第一次調用mapper的方法findUserById(1)
//第二次調用mapper的方法findUserById(1),從一級緩存中取資料
//方法結束,sqlSession關閉
}
如果是執行兩次service調用查詢相同 的使用者資訊,不走一級緩存,因為session方法結束,sqlSession就關閉,一級緩存就清空。
3 二級緩存
1 圖解
首先開啟mybatis的二級緩存。
sqlSession1去查詢使用者id為1的使用者資訊,查詢到使用者資訊會将查詢資料存儲到二級緩存中。
如果SqlSession3去執行相同 mapper下sql,執行commit送出,清空該 mapper下的二級緩存區域的資料。
sqlSession2去查詢使用者id為1的使用者資訊,去緩存中找是否存在資料,如果存在直接從緩存中取出資料。
二級緩存與一級緩存差別,二級緩存的範圍更大,多個sqlSession可以共享一個UserMapper的二級緩存區域。
UserMapper有一個二級緩存區域(按namespace分) ,其它mapper也有自己的二級緩存區域(按namespace分)。
每一個namespace的mapper都有一個二緩存區域,兩個mapper的namespace如果相同,這兩個mapper執行sql查詢到資料将存在相同 的二級緩存區域中。
2 開啟二級緩存
mybaits的二級緩存是mapper範圍級别,除了在SqlMapConfig.xml設定二級緩存的總開關,還要在具體的mapper.xml中開啟二級緩存。
在核心配置檔案SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 | 允許值 | 預設值 | |
cacheEnabled | 對在此配置檔案下的所有cache 進行全局性開/關設定。 | true false | true |
在UserMapper.xml中開啟二緩存,UserMapper.xml下的sql執行完成會存儲到它的緩存區域(HashMap)。
3 調用pojo類實作序列化接口
為了将緩存資料取出執行反序列化操作,因為二級緩存資料存儲媒體多種多樣,不一樣在記憶體。
4 測試方法
// 二級緩存測試
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
// 建立代理對象
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次發起請求,查詢id為1的使用者
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//這裡執行關閉操作,将sqlsession中的資料寫到二級緩存區域
sqlSession1.close();
//使用sqlSession3執行commit()操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.findUserById(1);
user.setUsername("張明明");
userMapper3.updateUser(user);
//執行送出,清空UserMapper下邊的二級緩存
sqlSession3.commit();
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次發起請求,查詢id為1的使用者
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
5 useCache配置
在statement中設定useCache=false可以禁用目前select語句的二級緩存,即每次查詢都會發出sql去查詢,預設情況是true,即該sql使用二級緩存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
總結:針對每次查詢都需要最新的資料sql,要設定成useCache=false,禁用二級緩存。
6 重新整理緩存(就是清空緩存)
在mapper的同一個namespace中,如果有其它insert、update、delete操作資料後需要重新整理緩存,如果不執行重新整理緩存會出現髒讀。
設定statement配置中的flushCache="true" 屬性,預設情況下為true即重新整理緩存,如果改成false則不會重新整理。使用緩存時如果手動修改資料庫表中的查詢資料會出現髒讀。
如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
總結:一般下執行完commit操作都需要重新整理緩存,flushCache=true表示重新整理緩存,這樣可以避免資料庫髒讀。
4 mybatis整合ehcache
ehcache是一個分布式緩存架構。
-
分布緩存
我們系統為了提高系統并發,性能、一般對系統進行分布式部署(叢集部署方式)
不使用分布緩存,緩存的資料在各各服務單獨存儲,不友善系統 開發。是以要使用分布式緩存對緩存資料進行集中管理。
mybatis無法實作分布式緩存,需要和其它分布式緩存架構進行整合。
2整合方法(掌握)
mybatis提供了一個cache接口,如果要實作自己的緩存邏輯,實作cache接口開發即可。
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個cache接口的實作類。
mybatis預設實作cache類是:
3 加入ehcache包
4 整合ehcache
配置mapper中cache中的type為ehcache對cache接口的實作類型。
5 加入ehcache的配置檔案
在classpath下配置ehcache.xml
5二級應用場景
對于通路多的查詢請求且使用者對查詢結果實時性要求不高,此時可采用mybatis二級緩存技術降低資料庫通路量,提高通路速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
實作方法如下:通過設定重新整理間隔時間,由mybatis每隔一段時間自動清空緩存,根據資料變化頻率設定緩存重新整理間隔flushInterval,比如設定為30分鐘、60分鐘、24小時等,根據需求而定。
6二級緩存局限性
mybatis二級緩存對細粒度的資料級别的緩存實作不好,比如如下需求:對商品資訊進行緩存,由于商品資訊查詢通路量大,但是要求使用者每次都能查詢最新的商品資訊,此時如果使用mybatis的二級緩存就無法實作當一個商品變化時隻重新整理該商品的緩存資訊而不重新整理其它商品的資訊,因為mybaits的二級緩存區域以mapper為機關劃分,當一個商品資訊變化會将所有商品資訊的緩存資料全部清空。解決此類問題需要在業務層根據需求對資料有針對性緩存。