mybatis框架執(zhí)行過程:
1、配置mybatis的配置文件,SqlMapConfig.xml(名稱不固定)
2、通過配置文件,加載mybatis運(yùn)行環(huán)境,創(chuàng)建SqlSessionFactory會(huì)話工廠
SqlSessionFactory在實(shí)際使用時(shí)按單例方式。
3、通過SqlSessionFactory創(chuàng)建SqlSession
SqlSession是一個(gè)面向用戶接口(提供操作數(shù)據(jù)庫方法),實(shí)現(xiàn)對(duì)象是線程不安全的,建議sqlSession應(yīng)用場(chǎng)合在方法體內(nèi)。
4、調(diào)用sqlSession的方法去操作數(shù)據(jù)。
如果需要提交事務(wù),需要執(zhí)行SqlSession的commit()方法。
5、釋放資源,關(guān)閉SqlSession
mapper代理開發(fā)方法(建議使用)
只需要程序員編寫mapper接口(就是dao接口)
程序員在編寫mapper.xml(映射文件)和mapper.java需要遵循一個(gè)開發(fā)規(guī)范:
1、mapper.xml中namespace就是mapper.java的類全路徑。
2、mapper.xml中statement的id和mapper.java中方法名一致。
3、mapper.xml中statement的parameterType指定輸入?yún)?shù)的類型和mapper.java的方法輸入 參數(shù)類型一致。
4、mapper.xml中statement的resultType指定輸出結(jié)果的類型和mapper.java的方法返回值類型一致。
本文內(nèi)容:
對(duì)訂單商品數(shù)據(jù)模型進(jìn)行分析。
高級(jí)映射:(了解)
實(shí)現(xiàn)一對(duì)一查詢、一對(duì)多、多對(duì)多查詢。
延遲加載
查詢緩存
一級(jí)緩存
二級(jí)緩存(了解mybatis二級(jí)緩存使用場(chǎng)景)
mybatis和spirng整合(掌握)
逆向工程(會(huì)用)
訂單商品數(shù)據(jù)模型
數(shù)據(jù)模型分析思路
1、每張表記錄的數(shù)據(jù)內(nèi)容
分模塊對(duì)每張表記錄的內(nèi)容進(jìn)行熟悉,相當(dāng)于你學(xué)習(xí)系統(tǒng)需求(功能)的過程。
2、每張表重要的字段設(shè)置
非空字段、外鍵字段
3、數(shù)據(jù)庫級(jí)別表與表之間的關(guān)系
外鍵關(guān)系
4、表與表之間的業(yè)務(wù)關(guān)系
在分析表與表之間的業(yè)務(wù)關(guān)系時(shí)一定要建立在某個(gè)業(yè)務(wù)意義基礎(chǔ)上去分析。
數(shù)據(jù)模型分析
用戶表user:
記錄了購買商品的用戶信息
訂單表:orders
記錄了用戶所創(chuàng)建的訂單(購買商品的訂單)
訂單明細(xì)表:orderdetail:
記錄了訂單的詳細(xì)信息即購買商品的信息
商品表:items
記錄了商品信息
表與表之間的業(yè)務(wù)關(guān)系:
在分析表與表之間的業(yè)務(wù)關(guān)系時(shí)需要建立在某個(gè)業(yè)務(wù)意義基礎(chǔ)上去分析。
先分析數(shù)據(jù)級(jí)別之間有關(guān)系的表之間的業(yè)務(wù)關(guān)系:
usre和orders:
user—->orders : 一個(gè)用戶可以創(chuàng)建多個(gè)訂單,一對(duì)多
orders—>user : 一個(gè)訂單只由一個(gè)用戶創(chuàng)建,一對(duì)一
orders和orderdetail:
orders –> orderdetail:一個(gè)訂單可以包括多個(gè)訂單明細(xì),因?yàn)橐粋€(gè)訂單可以購買多個(gè)商品,每個(gè)商品的購買信息在orderdetail記錄,一對(duì)多關(guān)系
orderdetail–> orders:一個(gè)訂單明細(xì)只能包括在一個(gè)訂單中,一對(duì)一
orderdetail和itesm:
orderdetail—> itesms:一個(gè)訂單明細(xì)只對(duì)應(yīng)一個(gè)商品信息,一對(duì)一
items–> orderdetail:一個(gè)商品可以包括在多個(gè)訂單明細(xì) ,一對(duì)多
再分析數(shù)據(jù)庫級(jí)別沒有關(guān)系的表之間是否有業(yè)務(wù)關(guān)系:
orders和items:
orders和items之間可以通過orderdetail表建立關(guān)系。
user 和 items : 通過其他表構(gòu)成了多對(duì)多關(guān)系
一對(duì)一查詢
需求 : 查詢訂單信息,關(guān)聯(lián)查詢創(chuàng)建訂單的用戶信息
使用resultType方式查詢
sql語句使用考慮
確定查詢的主表:訂單表
確定查詢的關(guān)聯(lián)表:用戶表
關(guān)聯(lián)查詢使用內(nèi)鏈接,還是外鏈接?
由于orders表中有一個(gè)外鍵(user_id),通過外鍵關(guān)聯(lián)查詢用戶表只能查詢出一條記錄,可以使用內(nèi)鏈接。
1
2
3
4
5
6
7
8
9
|
SELECT orders.*, USER.username, USER.sex, USER.address FROM orders, USER WHERE orders.user_id = user.id |
創(chuàng)建pojo(OrdersCustom.java)
將上邊sql查詢的結(jié)果映射到pojo中,pojo中必須包括所有查詢列名。
原始的Orders.java不能映射全部字段,需要新創(chuàng)建的pojo。
創(chuàng)建 一個(gè)pojo繼承包括查詢字段較多的po類。
OrdersMapperCustom.xml
OrdersMapperCustom.java
編寫測(cè)試類
選擇OrdersMapperCustom.java文件右鍵–>選擇New–>Others–> Junit Test Case–>選擇要測(cè)試的函數(shù)
在OrdersMapperCustomTest.java中編寫如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class OrdersMapperCustomTest { private SqlSessionFactory sqlSessionFactory; // 此方法是在執(zhí)行testFindUserById之前執(zhí)行 @Before public void setUp() throws Exception { // 創(chuàng)建sqlSessionFactory // mybatis配置文件 String resource = "SqlMapConfig.xml" ; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 創(chuàng)建會(huì)話工廠,傳入mybatis的配置文件信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 創(chuàng)建代理對(duì)象 OrdersMapperCustom ordersMapperCustom = sqlSession .getMapper(OrdersMapperCustom. class ); // 調(diào)用maper的方法 List<OrdersCustom> list = ordersMapperCustom.findOrdersUser(); System.out.println(list); sqlSession.close(); } |
使用resultMap方式查詢
sql語句 : 同resultType實(shí)現(xiàn)的sql
使用resultMap映射的思路
使用resultMap將查詢結(jié)果中的訂單信息映射到Orders對(duì)象中,在orders類中添加User屬性,將關(guān)聯(lián)查詢出來的用戶信息映射到orders對(duì)象中的user屬性中。
Orders類中添加user屬性
OrdersMapperCustom.xml
定義resultMap
tyep : 表示將整個(gè)查詢的結(jié)果映射到某個(gè)類中 eg:cn.itcast.mybatis.po.Orders
id:表示數(shù)據(jù)庫表中查詢列的唯一標(biāo)識(shí),訂單信息的中的唯一標(biāo)識(shí),如果有多個(gè)列組成唯一標(biāo)識(shí),配置多個(gè)id
column:數(shù)據(jù)庫表中訂單信息的唯一標(biāo)識(shí)列
property:訂單信息的唯一標(biāo)識(shí)列所映射到Orders中哪個(gè)屬性
association:用于映射關(guān)聯(lián)查詢單個(gè)對(duì)象的信息
property:要將關(guān)聯(lián)查詢的用戶信息映射到Orders中哪個(gè)屬性
javaType:映射到user的哪個(gè)屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<!-- 訂單查詢關(guān)聯(lián)用戶的resultMap 將整個(gè)查詢的結(jié)果映射到cn.itcast.mybatis.po.Orders中 --> <resultMap type= "cn.itcast.mybatis.po.Orders" id= "OrdersUserResultMap" > <!-- 配置映射的訂單信息 --> <!-- id:指定查詢列中的唯 一標(biāo)識(shí),訂單信息的中的唯 一標(biāo)識(shí),如果有多個(gè)列組成唯一標(biāo)識(shí),配置多個(gè)id column:訂單信息的唯 一標(biāo)識(shí) 列 property:訂單信息的唯 一標(biāo)識(shí) 列所映射到Orders中哪個(gè)屬性 --> <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/> <!-- 配置映射的關(guān)聯(lián)的用戶信息 --> <!-- association:用于映射關(guān)聯(lián)查詢單個(gè)對(duì)象的信息 property:要將關(guān)聯(lián)查詢的用戶信息映射到Orders中哪個(gè)屬性 --> <association property= "user" javaType= "cn.itcast.mybatis.po.User" > <!-- id:關(guān)聯(lián)查詢用戶的唯一標(biāo)識(shí) column:指定唯 一標(biāo)識(shí)用戶信息的列 javaType:映射到user的哪個(gè)屬性 --> <id column= "user_id" property= "id" /> <result column= "username" property= "username" /> <result column= "sex" property= "sex" /> <result column= "address" property= "address" /> </association> </resultMap> |
statement定義
OrdersMapperCustom.java
測(cè)試代碼
1
2
3
4
5
6
7
8
9
10
11
|
@Test public void testFindOrdersUserResultMap() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 創(chuàng)建代理對(duì)象 OrdersMapperCustom ordersMapperCustom = sqlSession .getMapper(OrdersMapperCustom. class ); // 調(diào)用maper的方法 List<Orders> list = ordersMapperCustom.findOrdersUserResultMap(); System.out.println(list); sqlSession.close(); } |
resultType和resultMap實(shí)現(xiàn)一對(duì)一查詢小結(jié)
resultType:使用resultType實(shí)現(xiàn)較為簡單,如果pojo中沒有包括查詢出來的列名,需要增加列名對(duì)應(yīng)的屬性,即可完成映射。
如果沒有查詢結(jié)果的特殊要求建議使用resultType。
resultMap:需要單獨(dú)定義resultMap,實(shí)現(xiàn)有點(diǎn)麻煩,如果對(duì)查詢結(jié)果有特殊的要求,使用resultMap可以完成將關(guān)聯(lián)查詢映射pojo的屬性中。
resultMap可以實(shí)現(xiàn)延遲加載,resultType無法實(shí)現(xiàn)延遲加載。
一對(duì)多查詢
需求 : 查詢訂單及訂單明細(xì)的信息。
sql語句
確定主查詢表:訂單表
確定關(guān)聯(lián)查詢表:訂單明細(xì)表
在一對(duì)一查詢基礎(chǔ)上添加訂單明細(xì)表關(guān)聯(lián)即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 |
分析 : 使用resultType將上邊的 查詢結(jié)果映射到pojo中,訂單信息的就是重復(fù)。
要求:對(duì)orders映射不能出現(xiàn)重復(fù)記錄。
在orders.java類中添加List<orderDetail> orderDetails屬性。
最終會(huì)將訂單信息映射到orders中,訂單所對(duì)應(yīng)的訂單明細(xì)映射到orders中的orderDetails屬性中。
映射成的orders記錄數(shù)為兩條(orders信息不重復(fù))
每個(gè)orders中的orderDetails屬性存儲(chǔ)了該訂單所對(duì)應(yīng)的訂單明細(xì)。
在Orders.java中添加list訂單明細(xì)屬性
OrdersMapperCustom.xml
resultMap定義
使用extends繼承,不用在中配置訂單信息和用戶信息的映射
collection:對(duì)關(guān)聯(lián)查詢到多條記錄映射到集合對(duì)象中
property:將關(guān)聯(lián)查詢到多條記錄映射到cn.itcast.mybatis.po.Orders哪個(gè)屬性
ofType:指定映射到list集合屬性中pojo的類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<!-- 訂單及訂單明細(xì)的resultMap 使用 extends 繼承,不用在中配置訂單信息和用戶信息的映射 --> <resultMap type= "cn.itcast.mybatis.po.Orders" id= "OrdersAndOrderDetailResultMap" extends = "OrdersUserResultMap" > <!-- 訂單信息 --> <!-- 用戶信息 --> <!-- 使用 extends 繼承,不用在中配置訂單信息和用戶信息的映射 --> <!-- 訂單明細(xì)信息 一個(gè)訂單關(guān)聯(lián)查詢出了多條明細(xì),要使用collection進(jìn)行映射 collection:對(duì)關(guān)聯(lián)查詢到多條記錄映射到集合對(duì)象中 property:將關(guān)聯(lián)查詢到多條記錄映射到cn.itcast.mybatis.po.Orders哪個(gè)屬性 ofType:指定映射到list集合屬性中pojo的類型 --> <collection property= "orderdetails" ofType= "cn.itcast.mybatis.po.Orderdetail" > <!-- id:訂單明細(xì)唯 一標(biāo)識(shí) property:要將訂單明細(xì)的唯一標(biāo)識(shí)映射到cn.itcast.mybatis.po.Orderdetail的哪個(gè)屬性 --> <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> |
OrdersMapperCustom.java
測(cè)試代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Test public void testFindOrdersAndOrderDetailResultMap() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 創(chuàng)建代理對(duì)象 OrdersMapperCustom ordersMapperCustom = sqlSession .getMapper(OrdersMapperCustom. class ); // 調(diào)用maper的方法 List<Orders> list = ordersMapperCustom .findOrdersAndOrderDetailResultMap(); System.out.println(list); sqlSession.close(); } |
小結(jié)
mybatis使用resultMap的collection對(duì)關(guān)聯(lián)查詢的多條記錄映射到一個(gè)list集合屬性中。
使用resultType實(shí)現(xiàn):
將訂單明細(xì)映射到orders中的orderdetails中,需要自己處理,使用雙重循環(huán)遍歷,去掉重復(fù)記錄,將訂單明細(xì)放在orderdetails中。
多對(duì)多查詢
需求 : 查詢用戶及用戶購買商品信息。
sql語句
查詢主表是:用戶表
關(guān)聯(lián)表:由于用戶和商品沒有直接關(guān)聯(lián),通過訂單和訂單明細(xì)進(jìn)行關(guān)聯(lián),所以關(guān)聯(lián)表:orders、orderdetail、items
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 |
映射思路
將用戶信息映射到user中。
在user類中添加訂單列表屬性List<Orders> orderslist,將用戶創(chuàng)建的訂單映射到orderslist
在Orders中添加訂單明細(xì)列表屬性List<OrderDetail>orderdetials,將訂單的明細(xì)映射到orderdetials
在OrderDetail中添加Items屬性,將訂單明細(xì)所對(duì)應(yīng)的商品映射到Items
OrdersMapperCustom.xml
resultMap定義
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<!-- 查詢用戶及購買的商品 --> <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" /> <!-- 訂單信息 一個(gè)用戶對(duì)應(yīng)多個(gè)訂單,使用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" /> <!-- 訂單明細(xì) 一個(gè)訂單包括 多個(gè)明細(xì)--> <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" /> <!-- 商品信息一個(gè)訂單明細(xì)對(duì)應(yīng)一個(gè)商品--> <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> |
OrdersMapperCustom.java
測(cè)試代碼:
1
2
3
4
5
6
7
8
9
10
11
|
@Test public void testFindUserAndItemsResultMap() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 創(chuàng)建代理對(duì)象 OrdersMapperCustom ordersMapperCustom = sqlSession .getMapper(OrdersMapperCustom. class ); // 調(diào)用maper的方法 List<User> list = ordersMapperCustom.findUserAndItemsResultMap(); System.out.println(list); sqlSession.close(); } |
多對(duì)多查詢總結(jié)
將查詢用戶購買的商品信息明細(xì)清單,(用戶名、用戶地址、購買商品名稱、購買商品時(shí)間、購買商品數(shù)量)
針對(duì)上邊的需求就使用resultType將查詢到的記錄映射到一個(gè)擴(kuò)展的pojo中,很簡單實(shí)現(xiàn)明細(xì)清單的功能。
一對(duì)多是多對(duì)多的特例,如下需求:
查詢用戶購買的商品信息,用戶和商品的關(guān)系是多對(duì)多關(guān)系。
需求1:
查詢字段:用戶賬號(hào)、用戶名稱、用戶性別、商品名稱、商品價(jià)格(最常見)
企業(yè)開發(fā)中常見明細(xì)列表,用戶購買商品明細(xì)列表,
使用resultType將上邊查詢列映射到pojo輸出。
需求2:
查詢字段:用戶賬號(hào)、用戶名稱、購買商品數(shù)量、商品明細(xì)(鼠標(biāo)移上顯示明細(xì))
使用resultMap將用戶購買的商品明細(xì)列表映射到user對(duì)象中。
總結(jié):
使用resultMap是針對(duì)那些對(duì)查詢結(jié)果映射有特殊要求的功能,比如特殊要求映射成list中包括多個(gè)list。
resultType與resultMap的總結(jié)
resultType:
作用:
將查詢結(jié)果按照sql列名pojo屬性名一致性映射到pojo中。
場(chǎng)合:
常見一些明細(xì)記錄的展示,比如用戶購買商品明細(xì),將關(guān)聯(lián)查詢信息全部展示在頁面時(shí),此時(shí)可直接使用resultType將每一條記錄映
射到pojo中,在前端頁面遍歷list(list中是pojo)即可。
resultMap:
使用association和collection完成一對(duì)一和一對(duì)多高級(jí)映射(對(duì)結(jié)果有特殊的映射要求)。
association:
作用:
將關(guān)聯(lián)查詢信息映射到一個(gè)pojo對(duì)象中。
場(chǎng)合:
為了方便查詢關(guān)聯(lián)信息可以使用association將關(guān)聯(lián)訂單信息映射為用戶對(duì)象的pojo屬性中,比如:查詢訂單及關(guān)聯(lián)用戶信息。
使用resultType無法將查詢結(jié)果映射到pojo對(duì)象的pojo屬性中,根據(jù)對(duì)結(jié)果集查詢遍歷的需要選擇使用resultType還是resultMap。
collection:
作用:
將關(guān)聯(lián)查詢信息映射到一個(gè)list集合中。
場(chǎng)合:
為了方便查詢遍歷關(guān)聯(lián)信息可以使用collection將關(guān)聯(lián)信息映射到list集合中,比如:查詢用戶權(quán)限范圍模塊及模塊下的菜單,可使用collection將模塊映射到模塊list中,將菜單列表映射到模塊對(duì)象的菜單list屬性中,這樣的作的目的也是方便對(duì)查詢結(jié)果集進(jìn)行遍歷查詢。
如果使用resultType無法將查詢結(jié)果映射到list集合中。
延遲加載
resultMap可以實(shí)現(xiàn)高級(jí)映射(使用association、collection實(shí)現(xiàn)一對(duì)一及一對(duì)多映射),association、collection具備延遲加載功能。
需求:
如果查詢訂單并且關(guān)聯(lián)查詢用戶信息。如果先查詢訂單信息即可滿足要求,當(dāng)我們需要查詢用戶信息時(shí)再查詢用戶信息。把對(duì)用戶信息的按需去查詢就是延遲加載。
延遲加載:先從單表查詢、需要時(shí)再從關(guān)聯(lián)表去關(guān)聯(lián)查詢,大大提高數(shù)據(jù)庫性能,因?yàn)椴樵儐伪硪汝P(guān)聯(lián)查詢多張表速度要快。
使用association實(shí)現(xiàn)延遲加載
需求 : 查詢訂單并且關(guān)聯(lián)查詢用戶信息
OrdresMapperCustom.xml
需要定義兩個(gè)mapper的方法對(duì)應(yīng)的statement。
1、只查詢訂單信息
1
|
SELECT * FROM orders |
在查詢訂單的statement中使用association去延遲加載(執(zhí)行)下邊的satatement(關(guān)聯(lián)查詢用戶信息)
2、關(guān)聯(lián)查詢用戶信息
通過上邊查詢到的訂單信息中user_id去關(guān)聯(lián)查詢用戶信息
使用UserMapper.xml中的findUserById
上邊先去執(zhí)行findOrdersUserLazyLoading,當(dāng)需要去查詢用戶的時(shí)候再去執(zhí)行findUserById,通過resultMap的定義將延遲加載執(zhí)行配置起來。
延遲加載resultMap
使用association中的select指定延遲加載去執(zhí)行的statement的id。
1
2
3
4
5
6
7
8
9
|
<!-- 延遲加載的resultMap --> <resultMap type= "cn.itcast.mybatis.po.Orders" id= "OrdersUserLazyLoadingResultMap" > <!--對(duì)訂單信息進(jìn)行映射配置 --> <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" /> <!-- 實(shí)現(xiàn)對(duì)用戶信息進(jìn)行延遲加載 |
select:指定延遲加載需要執(zhí)行的statement的id(是根據(jù)user_id查詢用戶信息的statement)
要使用userMapper.xml中findUserById完成根據(jù)用戶id(user_id)用戶信息的查詢,如果findUserById不在本mapper中需要前邊加namespace
column:訂單信息中關(guān)聯(lián)用戶信息查詢的列,是user_id
關(guān)聯(lián)查詢的sql理解為:
1
2
3
4
5
6
7
8
9
10
|
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" > <!-- 實(shí)現(xiàn)對(duì)用戶信息進(jìn)行延遲加載 --> </association> </resultMap> |
OrderesMapperCustom.java
測(cè)試思路:
1、執(zhí)行上邊mapper方法(findOrdersUserLazyLoading),內(nèi)部去調(diào)用cn.itcast.mybatis.mapper.OrdersMapperCustom中的findOrdersUserLazyLoading只查詢orders信息(單表)。
2、在程序中去遍歷上一步驟查詢出的List<Orders>,當(dāng)我們調(diào)用Orders中的getUser方法時(shí),開始進(jìn)行延遲加載。
3、延遲加載,去調(diào)用UserMapper.xml中findUserbyId這個(gè)方法獲取用戶信息。
延遲加載配置
mybatis默認(rèn)沒有開啟延遲加載,需要在SqlMapConfig.xml中setting配置。
在mybatis核心配置文件中配置:
設(shè)置項(xiàng) 描述 允許值 默認(rèn)值
lazyLoadingEnabled 全局性設(shè)置懶加載。如果設(shè)為‘false',則所有相關(guān)聯(lián)的都會(huì)被初始化加載。 true or false false
aggressiveLazyLoading 當(dāng)設(shè)置為‘true'的時(shí)候,懶加載的對(duì)象可能被任何懶屬性全部加載。否則,每個(gè)屬性都按需加載。 true or false true
在SqlMapConfig.xml中配置:
測(cè)試代碼
延遲加載思考
不使用mybatis提供的association及collection中的延遲加載功能,如何實(shí)現(xiàn)延遲加載?
實(shí)現(xiàn)方法如下:
定義兩個(gè)mapper方法:
1、查詢訂單列表
2、根據(jù)用戶id查詢用戶信息
實(shí)現(xiàn)思路:
先去查詢第一個(gè)mapper方法,獲取訂單信息列表
在程序中(service),按需去調(diào)用第二個(gè)mapper方法去查詢用戶信息。
總之:使用延遲加載方法,先去查詢簡單的sql(最好單表,也可以關(guān)聯(lián)查詢),再去按需要加載關(guān)聯(lián)查詢的其它信息。
查詢緩存
mybatis提供查詢緩存,用于減輕數(shù)據(jù)壓力,提高數(shù)據(jù)庫性能。
mybaits提供一級(jí)緩存,和二級(jí)緩存。
一級(jí)緩存是SqlSession級(jí)別的緩存。在操作數(shù)據(jù)庫時(shí)需要構(gòu)造 sqlSession對(duì)象,在對(duì)象中有一個(gè)數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲(chǔ)緩存數(shù)據(jù)。不同的sqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的。
二級(jí)緩存是mapper級(jí)別的緩存,多個(gè)SqlSession去操作同一個(gè)Mapper的sql語句,多個(gè)SqlSession可以共用二級(jí)緩存,二級(jí)緩存是跨SqlSession的。
為什么要用緩存?
如果緩存中有數(shù)據(jù)就不用從數(shù)據(jù)庫中獲取,大大提高系統(tǒng)性能。
一級(jí)緩存
一級(jí)緩存工作原理
第一次發(fā)起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,如果沒有,從數(shù)據(jù)庫查詢用戶信息。
得到用戶信息,將用戶信息存儲(chǔ)到一級(jí)緩存中。
如果sqlSession去執(zhí)行commit操作(執(zhí)行插入、更新、刪除),清空SqlSession中的一級(jí)緩存,這樣做的目的為了讓緩存中存儲(chǔ)的是最新的信息,避免臟讀。
第二次發(fā)起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。
一級(jí)緩存測(cè)試
mybatis默認(rèn)支持一級(jí)緩存,不需要在配置文件去配置。
按照上邊一級(jí)緩存原理步驟去測(cè)試。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//OrdersMapperCusntomTest.java @Test public void testCache1() throws Exception{ SqlSession sqlSession = sqlSessionFactory.openSession(); //創(chuàng)建代理對(duì)象 UserMapper userMapper = sqlSession.getMapper(UserMapper. class ); //下邊查詢使用一個(gè)SqlSession //第一次發(fā)起請(qǐng)求,查詢id為1的用戶 User user1 = userMapper.findUserById( 1 ); System.out.println(user1); // 如果sqlSession去執(zhí)行commit操作(執(zhí)行插入、更新、刪除),清空SqlSession中的一級(jí)緩存,這樣做的目的為了讓緩存中存儲(chǔ)的是最新的信息,避免臟讀。 //更新user1的信息 user1.setUsername( "測(cè)試用戶22" ); userMapper.updateUser(user1); //執(zhí)行commit操作去清空緩存 sqlSession.commit(); //第二次發(fā)起請(qǐng)求,查詢id為1的用戶 User user2 = userMapper.findUserById( 1 ); System.out.println(user2); sqlSession.close(); } |
一級(jí)緩存應(yīng)用
正式開發(fā),是將mybatis和spring進(jìn)行整合開發(fā),事務(wù)控制在service中。
一個(gè)service方法中包括 很多mapper方法調(diào)用。
1
2
3
4
5
6
|
service{ //開始執(zhí)行時(shí),開啟事務(wù),創(chuàng)建SqlSession對(duì)象 //第一次調(diào)用mapper的方法findUserById(1) //第二次調(diào)用mapper的方法findUserById(1),從一級(jí)緩存中取數(shù)據(jù) //方法結(jié)束,sqlSession關(guān)閉 } |
如果是執(zhí)行兩次service調(diào)用查詢相同 的用戶信息,不走一級(jí)緩存,因?yàn)閟ession方法結(jié)束,sqlSession就關(guān)閉,一級(jí)緩存就清空。
二級(jí)緩存
原理
首先開啟mybatis的二級(jí)緩存。
sqlSession1去查詢用戶id為1的用戶信息,查詢到用戶信息會(huì)將查詢數(shù)據(jù)存儲(chǔ)到二級(jí)緩存中。
如果SqlSession3去執(zhí)行相同 mapper下sql,執(zhí)行commit提交,清空該 mapper下的二級(jí)緩存區(qū)域的數(shù)據(jù)。
sqlSession2去查詢用戶id為1的用戶信息,去緩存中找是否存在數(shù)據(jù),如果存在直接從緩存中取出數(shù)據(jù)。
二級(jí)緩存與一級(jí)緩存區(qū)別,二級(jí)緩存的范圍更大,多個(gè)sqlSession可以共享一個(gè)UserMapper的二級(jí)緩存區(qū)域。
UserMapper有一個(gè)二級(jí)緩存區(qū)域(按namespace分) ,其它mapper也有自己的二級(jí)緩存區(qū)域(按namespace分)。
每一個(gè)namespace的mapper都有一個(gè)二緩存區(qū)域,兩個(gè)mapper的namespace如果相同,這兩個(gè)mapper執(zhí)行sql查詢到數(shù)據(jù)將存在相同的二級(jí)緩存區(qū)域中。
開啟二級(jí)緩存
mybaits的二級(jí)緩存是mapper范圍級(jí)別,除了在SqlMapConfig.xml設(shè)置二級(jí)緩存的總開關(guān),還要在具體的mapper.xml中開啟二級(jí)緩存。
在核心配置文件SqlMapConfig.xml中加入
1
|
<setting name= "cacheEnabled" value= "true" /> |
描述 允許值 默認(rèn)值
cacheEnabled 對(duì)在此配置文件下的所有cache 進(jìn)行全局性開/關(guān)設(shè)置。 true or false
在UserMapper.xml中開啟二級(jí)緩存,UserMapper.xml下的sql執(zhí)行完成會(huì)存儲(chǔ)到它的緩存區(qū)域(HashMap)。
調(diào)用pojo類實(shí)現(xiàn)序列化接口
為了將緩存數(shù)據(jù)取出執(zhí)行反序列化操作,因?yàn)槎?jí)緩存數(shù)據(jù)存儲(chǔ)介質(zhì)多種多樣,不一樣在內(nèi)存。
二級(jí)緩存測(cè)試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Test public void testCache2() throws Exception { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // 創(chuàng)建代理對(duì)象 UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); // 第一次發(fā)起請(qǐng)求,查詢id為1的用戶 User user1 = userMapper1.findUserById( 1 ); System.out.println(user1); //這里執(zhí)行關(guān)閉操作,將sqlsession中的數(shù)據(jù)寫到二級(jí)緩存區(qū)域 sqlSession1.close(); //使用sqlSession3執(zhí)行commit()操作 UserMapper userMapper3 = sqlSession3.getMapper(UserMapper. class ); User user = userMapper3.findUserById( 1 ); user.setUsername( "張明明" ); userMapper3.updateUser(user); //執(zhí)行提交,清空UserMapper下邊的二級(jí)緩存 sqlSession3.commit(); sqlSession3.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); // 第二次發(fā)起請(qǐng)求,查詢id為1的用戶 User user2 = userMapper2.findUserById( 1 ); System.out.println(user2); sqlSession2.close(); } |
useCache配置
在statement中設(shè)置useCache=false可以禁用當(dāng)前select語句的二級(jí)緩存,即每次查詢都會(huì)發(fā)出sql去查詢,默認(rèn)情況是true,即該sql使用二級(jí)緩存。
1
|
< select id= "findOrderListResultMap" resultMap= "ordersUserMap" useCache= "false" > |
總結(jié):針對(duì)每次查詢都需要最新的數(shù)據(jù)sql,要設(shè)置成useCache=false,禁用二級(jí)緩存。
刷新緩存
就是清空緩存
在mapper的同一個(gè)namespace中,如果有其它insert、update、delete操作數(shù)據(jù)后需要刷新緩存,如果不執(zhí)行刷新緩存會(huì)出現(xiàn)臟讀。
設(shè)置statement配置中的flushCache=”true” 屬性,默認(rèn)情況下為true即刷新緩存,如果改成false則不會(huì)刷新。使用緩存時(shí)如果手動(dòng)修改數(shù)據(jù)庫表中的查詢數(shù)據(jù)會(huì)出現(xiàn)臟讀。
1
|
< insert id= "insertUser" parameterType= "cn.itcast.mybatis.po.User" flushCache= "true" > |
總結(jié):一般下執(zhí)行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數(shù)據(jù)庫臟讀。
mybatis整合ehcache
ehcache是一個(gè)分布式緩存框架。
分布緩存
我們系統(tǒng)為了提高系統(tǒng)并發(fā),性能、一般對(duì)系統(tǒng)進(jìn)行分布式部署(集群部署方式)
不使用分布緩存,緩存的數(shù)據(jù)在各各服務(wù)單獨(dú)存儲(chǔ),不方便系統(tǒng) 開發(fā)。所以要使用分布式緩存對(duì)緩存數(shù)據(jù)進(jìn)行集中管理。
mybatis無法實(shí)現(xiàn)分布式緩存,需要和其它分布式緩存框架進(jìn)行整合。
整合ehcache方法(掌握)
mybatis提供了一個(gè)cache接口,如果要實(shí)現(xiàn)自己的緩存邏輯,實(shí)現(xiàn)cache接口開發(fā)即可。
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個(gè)cache接口的實(shí)現(xiàn)類。
mybatis默認(rèn)實(shí)現(xiàn)cache類是:
加入ehcache包
整合ehcache
配置mapper中cache中的type為ehcache對(duì)cache接口的實(shí)現(xiàn)類型。
加入ehcache的配置文件(在classpath下配置ehcache.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<ehcache xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation= "../config/ehcache.xsd" > <diskStore path= "F:\develop\ehcache" /> <defaultCache maxElementsInMemory= "1000" maxElementsOnDisk= "10000000" eternal= "false" overflowToDisk= "false" timeToIdleSeconds= "120" timeToLiveSeconds= "120" diskExpiryThreadIntervalSeconds= "120" memoryStoreEvictionPolicy= "LRU" > </defaultCache> </ehcache> |
屬性說明:
diskStore:指定數(shù)據(jù)在磁盤中的存儲(chǔ)位置。
defaultCache:當(dāng)借助CacheManager.add(“demoCache”)創(chuàng)建Cache時(shí),EhCache便會(huì)采用<defalutCache/>指定的的管理策略
以下屬性是必須的:
maxElementsInMemory - 在內(nèi)存中緩存的element的最大數(shù)目
maxElementsOnDisk - 在磁盤上緩存的element的最大數(shù)目,若是0表示無窮大
eternal - 設(shè)定緩存的elements是否永遠(yuǎn)不過期。如果為true,則緩存的數(shù)據(jù)始終有效,如果為false那么還要根據(jù)timeToIdleSeconds,timeToLiveSeconds判斷
overflowToDisk - 設(shè)定當(dāng)內(nèi)存緩存溢出的時(shí)候是否將過期的element緩存到磁盤上
以下屬性是可選的:
timeToIdleSeconds - 當(dāng)緩存在EhCache中的數(shù)據(jù)前后兩次訪問的時(shí)間超過timeToIdleSeconds的屬性取值時(shí),這些數(shù)據(jù)便會(huì)刪除,默認(rèn)值是0,也就是可閑置時(shí)間無窮大
timeToLiveSeconds - 緩存element的有效生命期,默認(rèn)是0.,也就是element存活時(shí)間無窮大
diskSpoolBufferSizeMB 這個(gè)參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小.默認(rèn)是30MB.每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū).
diskPersistent - 在VM重啟的時(shí)候是否啟用磁盤保存EhCache中的數(shù)據(jù),默認(rèn)是false。
diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運(yùn)行間隔,默認(rèn)是120秒。每個(gè)120s,相應(yīng)的線程會(huì)進(jìn)行一次EhCache中數(shù)據(jù)的清理工作
memoryStoreEvictionPolicy - 當(dāng)內(nèi)存緩存達(dá)到最大,有新的element加入的時(shí)候, 移除緩存中element的策略。默認(rèn)是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進(jìn)先出)
二級(jí)應(yīng)用場(chǎng)景
對(duì)于訪問多的查詢請(qǐng)求且用戶對(duì)查詢結(jié)果實(shí)時(shí)性要求不高,此時(shí)可采用mybatis二級(jí)緩存技術(shù)降低數(shù)據(jù)庫訪問量,提高訪問速度,業(yè)務(wù)場(chǎng)景比如:耗時(shí)較高的統(tǒng)計(jì)分析sql、電話賬單查詢sql等。
實(shí)現(xiàn)方法如下:通過設(shè)置刷新間隔時(shí)間,由mybatis每隔一段時(shí)間自動(dòng)清空緩存,根據(jù)數(shù)據(jù)變化頻率設(shè)置緩存刷新間隔flushInterval,比如設(shè)置為30分鐘、60分鐘、24小時(shí)等,根據(jù)需求而定。
二級(jí)緩存局限性
mybatis二級(jí)緩存對(duì)細(xì)粒度的數(shù)據(jù)級(jí)別的緩存實(shí)現(xiàn)不好,比如如下需求:對(duì)商品信息進(jìn)行緩存,由于商品信息查詢?cè)L問量大,但是要求用戶每次都能查詢最新的商品信息,此時(shí)如果使用mybatis的二級(jí)緩存就無法實(shí)現(xiàn)當(dāng)一個(gè)商品變化時(shí)只刷新該商品的緩存信息而不刷新其它商品的信息,因?yàn)閙ybaits的二級(jí)緩存區(qū)域以mapper為單位劃分,當(dāng)一個(gè)商品信息變化會(huì)將所有商品信息的緩存數(shù)據(jù)全部清空。解決此類問題需要在業(yè)務(wù)層根據(jù)需求對(duì)數(shù)據(jù)有針對(duì)性緩存。
原文鏈接:http://blog.csdn.net/lutianfeiml/article/details/51781318