MybatisDay02(批量generator、二級快取、延遲載入)
阿新 • • 發佈:2018-12-06
mybatis 高階對映 查詢快取 spring集合 mybatis是什麼? mbatis是一個持久層框架,mybatis是一個不完全的ORM框架,sql語句需要程式設計師自己去編寫,但是mybatis有對映(輸入引數對映、輸出結果對映) mybatis入門門檻不高,學習成本低,讓程式設計師把精力放在sql語句上,對sql語句優化非常方便, 適用於需求變化較多的專案,比如網際網路專案。 mybatis框架執行過程: 1、配置mybatis的配置檔案,SqlMapConfig.xml(名稱不固定) 2、通過配置檔案,載入mybatis執行環境,建立SqlSessionFactory會話工廠 SqlSessionFactory在實際使用時按單例方式 3、通過SqlSessionFactory建立SqlSession SqlSession是一個面向使用者介面(提供操作資料庫方法),實現物件是執行緒不安全的, 建議sqlSession應用場合在方法體內。 4、呼叫sqlSession的方法操作資料 如果需要提交事務,需要執行SqlSession的commit()方法。 5、釋放資源,關閉SqlSession mybatis開發dao的方法 1、原始dao的方法 需要程式設計師編寫dao介面和實現類 需要在dao實現類中注入一個SqlSessionFactory工廠 2、mapper代理開發方法 只需要程式設計師編寫mapper介面(就是dao介面) 程式設計師編寫mapper.xml(對映檔案)和mapper.java需要遵循一個開發規範: 1、mapper.xml中namespace就是mapper.java的類全路徑 2、mapper.xml中statement的id和mapper.java中方法名一致 3、mapper.xml中statement的parameterType指定輸入引數的型別和mapper.java的方法輸入引數型別一致。 4、mapper.xml中statement的resultType指定輸出結果的型別和mapper.java的方法返回值型別一致 SqlMapConfig.xml配置檔案:可以配置properties屬性、別名、mapper載入 輸入對映: parameterType:指定輸入引數型別可以簡單型別、pojo、hashmap。。 對於綜合查詢,建議parameterType使用包裝的pojo,有利於系統的擴充套件 輸出對映: resultType: 查詢到的列名和resultType指定的pojo的屬性名一致,才能對映成功 resultMap: 可以通過resultMap完成一些高階對映 如果查詢到的列名和對映的pojo的屬性名不一致時,通過resultMap設定列名和屬性名之間的對應關係(對映關係)。 可以完成對映 高階對映: 將關聯查詢的列對映到一個pojo屬性中。(一對一) 將關聯查詢的列對映到一個List<pojo>中。(一對多) 動態sql:(重點) if判斷 where foreach sql片段 對訂單商品資料模型進行分析 高階對映: 實現一對一查詢、一對多、多對多查詢 延遲載入 查詢快取: 一級快取 二級快取 mybatis和spring整合 逆向工程 1、訂單商品資料模型
1.1、資料模型分析思路
1、每張表記錄的資料內容型
分模組對每張表記錄的內容進行熟悉,相當於學習系統需求(功能)的過程
2、每張表重要的欄位設定
非空欄位、外來鍵欄位
3、資料庫級別表與表之間的關係
外來鍵關係
4、表與表之間的業務關係
在分析表與表之間的業務關係時一定要建立在某個業務意義基礎上去分析
表與表之間的業務關係: 在分析表與表之間的業務關係時需要建立在某個業務基礎上去分析。 先分析資料級別之間有關係的表之間的業務關係: user和orders: user-》orders:一個使用者可以建立多個訂單,一對多 orders-》user:一個訂單隻由一個使用者建立,一對一 orders和orderdetail: orders-》orderdetail:一個訂單可以包括多個訂單明細,因為一個訂單可以購買多個商品, 每個商品的購買資訊在orderdetail記錄,一對多關係 orderdetail-》orders:一個訂單明細只能包括在一個訂單中,一對一 orderdetail和items: orderdetail-》items:一個訂單明細只對應一個商品資訊,一對一 items-》orderdetail:一個商品可以包括在多個訂單明細,一對多 再分析資料庫級別沒有關係的表之間是否有業務關係: orders和items: orders和items之間可以通過orderdetail表建立關係 由業務關係決定一對多還是一對一 2、一對一查詢 2.1、需求 查詢訂單資訊,關聯查詢 2.2、resultType 2.2.1、sql語句 確定查詢的主表:訂單表 確定查詢的關聯表:使用者表 關聯查詢使用內連線?還是外連線? 由於orders表中有一個外來鍵(user_id)通過外來鍵關聯查詢使用者表只能查詢出一條記錄,可以使用內連線 SELECT orders.*,user.`username`,user.`sex`,user.`address` FROM orders,USER WHERE orders.`user_id` = user.`id` 2.2.2、建立pojo 將上邊sql查詢的結果對映到pojo中,pojo中必須包括所有查詢列名 原始的Orders.java不能對映全部欄位,需要新建立的pojo 建立一個pojo繼承包括查詢欄位較多的pojo //訂單的擴充套件類 //通過此類對映訂單和使用者查詢的結果,讓此類繼承包括欄位較多的pojo類(然後擴充套件少的類 public class OrderCustom extends Order{ /** * 新增使用者屬性 * User.username * User.sex * User/address */ private String username; private String sex; private String address; 2.2.3、mapper.xml <mapper namespace="my.mybatis.mapper.OrdersMapperCustom"> <select id="findOrderUser" resultType="my.mybatis.po.OrderCustom"> SELECT orders.*,user.`username`,user.`sex`,user.`address` FROM orders,USER WHERE orders.`user_id` = user.`id` </select> 2.2.4、mapper.java public interface OrdersMapperCustom { //使用者資訊綜合查詢 public List<OrderCustom> findOrderUser() throws Exception; } 2.2.5、測試 public class OrdersMapperCustomTest { private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws IOException { String resource = "SqlMapConfig.xml"; InputStream resourceAsStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); } @Test public void testFindOrderUser() throws Exception { SqlSession openSession = sqlSessionFactory.openSession(); //建立代理物件 OrdersMapperCustom mapper = openSession.getMapper(OrdersMapperCustom.class); //呼叫mapper方法 List<OrderCustom> findOrderUser = mapper.findOrderUser(); System.out.println(findOrderUser); openSession.close(); } } 2.3、resultMap 2.3.1、sql語句 同resultType實現的sql 2.3.2、使用resultMap對映的思路 使用resultMap將查詢結果中的訂單資訊對映到Orders物件()中,在orders類中新增User屬性 2.3.3、需要Orders類中新增user屬性 public class Orders { private Integer id; private Integer user_id; private Integer number; private Date createtime; private String note; *** private User user; 2.3.4、mapper.xml 2.3.4.1、定義resultMap <!-- 定義resultMap 將SELECT id id_,username username_ FROM USER 和User類中的屬性作一個對映關係 type:resultMap最終對映的java物件型別,可以使用別名 id:對resultMap的唯一標識 --> <resultMap type="orders" id="ordersUserResultMap"> <!-- 配置對映的訂單資訊 id表示查詢結果集中唯一標識,訂單資訊中的唯一標識,如果有多個列組成唯一標識,配置多個id cloumn:訂單資訊的唯一標識列 property:訂單資訊的唯一標識 列鎖對映到Orders中那個屬性 最終resultMap對column和property作一個對映關係(對應關係) --> <id column="id" property="id"/> <result column="user_id" property="user_id"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 配置對映的關聯的使用者資訊 --> <!-- association:用於對映關聯查詢單個物件的資訊 property:要將關聯查詢的使用者資訊對映到Orders哪個屬性 --> <association property="user" javaType="user"> <!-- id:關聯查詢使用者的唯一標識 column:指定唯一標識使用者資訊的列 javaType:對映到user的那個屬性 --> <id column="user_id" property="id"/> <id column="username" property="username"/> <id column="sex" property="sex"/> <id column="address" property="address"/> </association> </resultMap> 2.3.4.2、statement定義 <select id="findOrdersByResultMap" resultMap="ordersUserResultMap"> SELECT orders.*,user.`username`,user.`sex`,user.`address` FROM orders,USER WHERE orders.`user_id` = user.`id` </select> 2.3.5、mapper.java //查詢訂單關聯查詢使用者使用resultMap public List<OrderCustom> findOrdersByResultMap() throws Exception; 2.4、resultType和resultMap實現一對一查詢小結 實現一對一查詢 resultType:使用resultType較為簡單,如果pojo中沒有包括查詢出來的列名,需要增加列名對應的屬性,即可完成對映。 如果沒有查詢結果的特殊要求建議使用resultType resultMap:需要單獨定義resultMap,實現有點麻煩,如果有對查詢有特殊的要求, 使用resultMap可以完成將關聯查詢對映pojo的屬性中 resultMap可以實現延遲載入,resultType無法實現延遲載入 3、一對多查詢 3.1、需求 查詢訂單及訂單明細的資訊 3.2、sql語句 確定主查詢表:訂單表 確定關聯查詢表:訂單明細表 在一對一查詢基礎上新增訂單明細表關聯即可 SELECT orders.*,user.`username`,user.`sex`,user.`address`,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` 3.3、分析 使用resultType將上邊的查詢結果對映到pojo中,訂單資訊有重複
要求:
對orders對映不能出現重複記錄
在orders.java類中新增List<orderDetail> orderDetails屬性
最終會將訂單資訊對映到orders中,訂單所對應的訂單明細對映到orders中的orderDetail屬性
對映成的orders記錄數為兩條(orders資訊不重複) 每個orders中的orderDetails屬性儲存了該訂單所對應的訂單明細 3.4、在orders中新增list訂單明細屬性 public class Orders { private Integer id; private Integer user_id; private Integer number; private Date createtime; private String note; //使用者資訊 private User user; //訂單明細 private List<Orderdetail> orderdetails; 3.5、mapper.xml <!-- 查詢訂單關聯查詢使用者及訂單明細,使用resultmap --> <select id="findOrdersAndOrderdetailResultMap" resultMap="OrdersAndOrderdetailResultMap"> SELECT orders.*, user.`username`, user.`sex`, user.`address`, orderdetail.`items_id` orederdetail_id, orderdetail.items_num, orderdetail.`orders_id` FROM orders,USER,orderdetail WHERE orders.`user_id` = user.`id` AND orderdetail.`orders_id` = orders.`id` </select> 3.6、resultMap定義 <!-- 訂單及訂單明細的resultMap 使用extends繼承,不用在此配置訂單資訊和使用者資訊的對映 --> <resultMap type="orders" id="OrdersAndOrderdetailResultMap" extends="ordersUserResultMap"> <!-- 訂單資訊 --> <!-- 使用者資訊 --> <!-- 使用extends繼承,不用再其中配置訂單資訊和使用者資訊的對映 --> <!-- 訂單明細資訊 一個訂單查了出了多條明細,要使用collection進行對映 collection:對關聯查詢到多條記錄對映到集合物件中 property:將關聯查詢到的多條記錄對映到orders的orderdetails屬性 ofType:指定對映到集合屬性中pojo的型別 --> <collection property="orderdetails" ofType="my.mybatis.po.Orderdetail" > <!-- id:訂單明細的唯一標識 property:要將訂單明細的唯一標識對映到my.mybatis.po.Orderdetail的哪個屬性 --> <id column="orederdetail_id" property="id"></id> <result column="items_id" property="items_id"></result> <result column="items_num" property="items_num"></result> <result column="orders_id" property="orders_id"></result> </collection> </resultMap> 3.7、mapper.java //查詢訂單關聯查詢使用者使用resultMap public List<OrderCustom> findOrdersAndOrderdetailResultMap() throws Exception; 3.8、小結 mybatis使用resultMap的collection對關聯查詢的多條記錄對映到一個list集合屬性中 使用resultType實現 將訂單明細對映到orders中的orderdetails中,需要自己處理,使用雙重迴圈遍歷,將訂單明細放在orderdetails中。 resultType再寫個pojo類 或者在繼承了一個類中寫上其他所需的欄位 去重 for (int i = 0; i < findOrderUser.size()-1; i++) { for (int j = findOrderUser.size()-1; j >i; j--) { if(findOrderUser.get(i).getOrders_id().equals(findOrderUser.get(j).getOrders_id())) findOrderUser.remove(j); } } 4、多對多查詢 4.1、需求 查詢使用者及使用者購買商品資訊 4.2、sql語句 查詢主表是:使用者表 關聯表:由於使用者和商品沒有直接關聯,通過訂單和訂單明細進行關聯,所以關聯表: orders、orderdetail、items SELECT orders.*, user.`username`, user.`sex`, user.`address`, orderdetail.`id` orederdetail_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` 4.3、對映思路 將使用者資訊對映到user中 在user類中新增訂單列表屬性List<Orders> orderslist.將使用者建立的訂單對映到orderslist 在Orders中新增訂單明細列表屬性 List<Orderdetail> orderdetails.將訂單明細對映到orderdetails 在Orderdetail中新增items屬性,將訂單明細鎖對應的商品對映到items 4.4、mapper.xml <!-- 查詢訂單關聯查詢使用者及訂單明細,使用resultmap --> <select id="findOrdersAndOrderdetailAndItemsResultMap" resultMap="OrdersAndOrderdetailAndItemsResultMap"> SELECT orders.*, user.`username`, user.`sex`, user.`address`, orderdetail.`id` orederdetail_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` </select> 4.5、resultMap定義 <!-- 查詢使用者及購買的產品 --> <resultMap type="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="my.mybatis.po.Orders"> <id column="id" property="id"/> <result column="user_id" property="user_id"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 訂單明細 一個訂單包括多個明細 --> <collection property="orderdetails" ofType="my.mybatis.po.Orderdetail"> <id column="orederdetail_id" property="id"></id> <result column="items_id" property="items_id"></result> <result column="items_num" property="items_num"></result> <result column="orders_id" property="orders_id"></result> <!-- 商品資訊 一個訂單明細對應一個商品 --> <association property="items" javaType="my.mybatis.po.Items"> <id column="items_id" property="id"></id> <result column="items_name" property="name"/> <result column="items_detail" property="detail"/> <result column="items_price" property="price"/> </association> </collection> </collection> </resultMap> 4.6、mapper.java //查詢使用者購買商品資訊 public List<User> findOrdersAndOrderdetailAndItemsResultMap() throws Exception; 4.7、多對多查詢總結 將查詢使用者購買的商品資訊明細清單(使用者名稱、使用者地址、購買商品名稱、購買商品時間、購買商品數量) 針對上邊的需求就使用resultType將查詢到的記錄對映到一個擴充套件的pojo中,很簡單實現明細清單的功能 一對多是多對多的特例,如下需求 查詢使用者購買的商品資訊,使用者和商品的關係是多對多 需求一: 查詢欄位:使用者帳號、使用者名稱稱、使用者性別、商品名稱、商品價格(最常見) 企業開發中常見明細列表,使用者購買商品明細列表 使用resultType將上邊查詢列表對映到pojo輸出 需求2: 查詢欄位:使用者帳號、使用者名稱稱、購買商品數量、商品明細(滑鼠移上顯示明細) 使用resultMap將使用者購買的商品明細列表對映到user物件中 總結: 使用resultMap是針對那些對查詢結果對映有特殊要求的功能,比如特殊要求對映成list中包括多個list 5、resultMap總結 resultType: 作用: 將查詢結果按照sql列名pojo屬性名一致性對映到pojo中 場合: 常見一些明細記錄的展示,比如使用者購買商品明細,將關聯查詢資訊全部展示在頁面時, 此時可直接使用resultType將每一條記錄對映到pojo中,在前端頁面遍歷list(list中是pojo)即可。 resultMap: 使用association和collection完成一對一和一對多高階對映 association: 作用: 將關聯查詢資訊對映到一個pojo物件中 場合: 為了方便查詢關聯資訊可以使用association將關聯訂單資訊對映為使用者物件的pojo屬性中, 比如:查詢訂單及關聯使用者資訊。 使用resultType無法將查詢結果對映到pojo物件的pojo屬性中,根據對結果集查詢遍歷的需要選擇使用resultType還是resultMap collection: 作用: 將關聯查詢資訊對映到一個list集合中。 場合: 為了方便查詢遍歷關聯資訊可以使用collection將關聯資訊對映到list集合中,比如:查詢使用者許可權範圍模組及模組下的選單, 可使用collection將模組對映到模組list中,將選單列表對映到模組物件的選單list屬性中, 這樣做的目的也是方便對查詢結果集進行遍歷查詢 如果使用resultType無法將查詢結果對映到list集合中。 6、延遲載入 6.1、什麼是延遲載入 resultMap可以實現高階對映(使用association、collection實現一對一及一對多對映),association、collection具備延遲載入功能。 需求: 如果查詢訂單並且關聯查詢使用者資訊。如果先查詢訂單資訊即可滿足要求,當我們需要查詢使用者資訊時再查詢使用者資訊,把對使用者資訊的按需去查詢就是延遲載入 延遲載入:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高資料庫效能,因為查詢單表要比關聯查詢多張錶速度要快。 6.2、使用association實現延遲載入 6.2.1、需求 查詢訂單並且關聯查詢使用者資訊 6.2.2、mapper.xml 需要定義兩個mapper的方法對應的statement 1、只查詢訂單資訊 SELECT * FROM orders; 在查詢訂單的statement中使用association去延遲載入(執行)下邊的statement; 2、關聯查詢使用者資訊 通過上邊查詢到的訂單資訊中user_id去關聯查詢使用者資訊 使用UserMapper.xml中的findUset 6.2.3、延遲載入resultMap 使用association中的select指定延遲載入去執行的statement的id <!-- 延遲載入的resultMap --> <resultMap type="orders" id="OrderUserLazyLoadingResultMap"> <!-- 對訂單資訊進行對映配置 --> <id column="id" property="id"></id> <result column="user_id" property="user_id"/> <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="user" select="my.mybatis.mapper.UserMapper.findUserList" column="user_id"> <!-- 實現對使用者資訊進行延遲載入 --> </association> </resultMap> 6.2.4、mapper.java //查詢訂單關聯查詢使用者, public List<Orders> findOrderUserLazyLoading() throws Exception; 6.2.5、測試 6.2.5.1、測試思路: 1、執行上邊mapper方法(findOrdersUserLazyLoading),內部去呼叫my.mybatis.mapper.OrdersMapperCustom中的indOrdersUserLazyLoading值查詢orders資訊(單表) 2、在程式中去變數上一步驟查詢出的List<Orders>,當我們呼叫Orders中的getUser方法時,開始進行延遲載入 3、延遲載入,去呼叫UserMapper.xml中findUserById這個方法獲取使用者資訊 6.2.5.2、延遲載入配置 在mybatis核心配置檔案中配置: lazyLoadingEnabled、aggressiveLazyLoading 設定項 描述 允許值 預設值 lazyLoadingEnabled 全域性性設定懶載入,如果設定為false,則所有相關聯的都會被初始化記載 true|false false aggressiveLazyLoading 當設定為true的時候,懶載入的物件可能被任何屬性全部載入。否則,每個屬性都按需載入 true|false true 在SqlMapConfig.xml中配置 <!-- 全域性配置引數 --> <settings> <!-- 開啟延遲載入的開關 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 將積極載入改為小計載入即按需要載入 --> <setting name="aggressiveLazyLoading" value="false"/> </settings> 6.2.5.3、測試程式碼 //查詢訂單關聯查詢使用者,使用者資訊使用延遲載入 @Test public void testFindOrderUserLazyLoading() throws Exception { SqlSession openSession = sqlSessionFactory.openSession(); //建立代理物件 OrdersMapperCustom mapper = openSession.getMapper(OrdersMapperCustom.class); //呼叫mapper方法 查詢訂單資訊(單表) List<Orders> list = mapper.findOrderUserLazyLoading(); //遍歷上邊的訂單列表 for(Orders orders:list) { //執行getUser()去查詢使用者資訊,這裡實現按需載入 User user = orders.getUser(); System.out.println(user); } openSession.close(); } 6.2.6、延遲載入思考 不使用mybatis提供的association及collection中的延遲載入功能,如何實現延遲載入? 實現方法如下: 定義兩個mapper方法: 1、查詢訂單列表 2、根據使用者id查詢使用者資訊 實現思路: 先查詢第一個mapper方法,獲取訂單資訊列表 在程式中(service)按需去呼叫第二個mapper方法去查詢使用者資訊 總之: 使用延遲載入方法,先去查詢簡單的sql(最好單表,也可以關聯查詢),再去按需要載入關聯查詢的其他資訊 7、查詢快取 7.1、什麼是查詢快取 mybatis提供查詢快取,使用者減輕資料壓力,提高資料庫效能 mybatis提供一級快取,和二級快取
一級快取是SqlSession級別的快取,在操作資料庫時需要構造sqlsession物件,
在物件中有一個數據結構(HashMap)用於儲存快取資料。不同的sqlSession之間的快取資料區域(HashMap)是互相不影響的
二級快取是mapper級別的快取,多個SqlSession去操作同一個Mapper的sql語句,
多個sqlSession可以共用二級快取,二級快取是跨SqlSession的。
為什麼要用快取?
如果快取中有資料就不用從資料庫中獲取,大大提高系統性能
7.2、一級快取
7.2.1、一級快取工作原理
根據id查詢使用者的一級快取圖解
一級快取區域是根據sqllSession為單位劃分的
每次查詢會先從快取區域找,如果找不到從資料庫察徐某。查詢到資料將資料寫入快取。
Mybatis內部儲存快取使用一個HashMap,key為hashCode+sqlId+sql語句。value為從查詢出來對映生成的java物件
第一次發起查詢使用者id為1的使用者資訊,先去找快取中是否有id為1的使用者資訊,如果沒有,從資料庫查詢使用者資訊
得到使用者資訊,將使用者資訊儲存到一級快取中
如果SqlSession去執行commit操作(執行插入、更新、刪除)清空SqlSession中的一級快取,
這樣做的目的是為了讓快取中儲存的是最新的資訊,避免髒讀
第二次發起查詢使用者id為1的使用者資訊,先去找快取中是否有id為1的使用者資訊,快取中有,直接從快取中獲取使用者資訊
7.2.2、一級快取測試
mybatis預設支援一級快取,不需要在配置檔案去配置
7.3、二級快取
7.3.1、原理
首先開啟mybatis的二級快取
sqlSession1去查詢使用者id為1的使用者資訊,查詢到使用者資訊會將查詢資料儲存到二級快取中
sqlSession2去查詢使用者id為1的使用者資訊,去快取中找是否存在資料,如果存在直接從快取中取出資料
二級快取與一級快取區別,二級快取的範圍更大,多個sqlSession可以共享一個UserMapper二級快取區域
UserMapper有一個二級快取區域(按namespace分)其他mapper也有自己的二級快取區域(按namespace分)
每一個namespace的mapper都有一個二級快取區域,兩個mapper的namespace如果相同,這兩個mapper執行sql查詢到資料庫將存在相同的二級快取區域中
7.3.2、開啟二級快取
mybatis的二級快取是mapper範圍級別,除了在SqlMapConfig.xml設定二級快取的總開關,還要在具體的mapper.xml中開啟二級快取
在核心配置檔案SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 允許值 預設值
cacheEnabled 對在此配置檔案下的所有cache進行全域性性開/關設定 true false true
在UserMapper.xml中開啟二級快取,UserMapper.xml下的sql執行完成會儲存到它的快取區域(HashMap)
<mapper namespace="my.mybatis.mapper.UserMapper">
<!-- 開啟本mapper的namespace下的二級快取 -->
<cache/>
7.3.3、呼叫pojo類實現序列化介面
public class User implements Serializable {
//屬性名和資料庫表的欄位相對應
private int id ;
private String username;//使用者姓名
private String sex;//性別
private Date birthday;//生日
private String address;//地址
為了將快取資料取出執行反序列化操作,因為二級快取資料儲存介質多種多樣,不一樣在記憶體
7.3.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);
//查詢使用一個SqlSession
//第一次發起請求,查詢id為1的使用者
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//這裡執行關閉操作,將sqlSession1中的資料寫到二級快取區域
sqlSession1.close();
//使用sqlSession3執行commit()操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user3 = userMapper3.findUserById(1);
//更新user3的資訊
user3.setUsername("小光");
userMapper3.updateUser(user3);
//執行提交,清空USerMapper下邊的二級快取
sqlSession3.commit();
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次發起請求,查詢id為1的使用者
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
7.3.5、useCache配置
在statement中設定useCache=false可以禁用當前select語句的二級快取,即每次查詢都會發出sql去查詢,
預設情況是true,即該sql使用二級快取
<select id="findUserById" parameterType="java.lang.Integer" resultType="my.mybatis.po.User" useCache="false">
總結:針對每次查詢都需要最新的資料sql,要設定成useCache=false;
7.3.6、重新整理快取(就是清空快取)
在mapper的同一個namespace中,如果有其他insert、update、delete操作資料後要重新整理快取,
如果不執行重新整理快取會出現髒讀。
設定statement配置中的flushCache="true"屬性,預設情況下為true即重新整理快取,如果改成false則不會重新整理,
使用快取時如果手動修改資料庫表中的查詢資料會出現髒讀。
<select id="findUserById" parameterType="java.lang.Integer" resultType="my.mybatis.po.User" flushCache="true">
總結:一般情況下執行完commit操作需要重新整理快取,flushCache=true表示重新整理快取,這樣可以避免資料庫髒讀
7.3.7、Mybatis Cache引數
flushInterval(重新整理間隔)可以被設定成任意的正整數,而且它們代表一個合理的毫秒形式的時間段,
預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。
size(引用數目)可以被設定為任意正整數,快取的物件數目和執行環境的可用記憶體資源數目。預設值為1024
readOnly(只讀)屬性可以被設定為true或false。只讀的快取會給所有呼叫者返回快取物件的相同例項。
因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取會返回快取物件的拷貝(通過序列化)。
這回慢一些,但是安全,因此預設是false
<cache flushInterval="6000" eviction="FIFO" size="512" readOnly="true"/>
這個更高階的配置建立了一個FIFO快取,並每隔60秒重新整理,存數結果物件或列表的512個引用,而且返回的物件被認為是隻讀的,
因此在不同執行緒中的呼叫者之間修改他們會導致衝突。可用的收回策略 預設的是LRU
1、LRU:最近最少使用的,移除最長時間不被使用的物件
2、FIFO:先進先出:按物件進入快取的順序來移除他們
7.4、mybatis整合ehcache
ehcache是一個分散式快取框架
7.4.1、分佈快取
系統為了提高系統併發,效能,一般對系統進行分散式部署(叢集部署方式)
不使用分佈快取,快取的資料在各各伺服器單獨儲存,不方便系統開發,所以要使用分散式快取對快取資料進行集中管理
mybatis無法實現分散式快取,需要和其他分散式快取框架進行整合
7.4.2、整合方法
mybatis提供了一個cache介面,如果要實現自己的快取邏輯,實現cache介面開發即可
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個cache介面的實現類
cache介面
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
mybatis預設實現cache類:
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
7.4.3、加入ehcache包
7.4.4、整合ehcache
配置mapper中cache中的type為ehcache對cache介面的實現型別
<mapper namespace="my.mybatis.mapper.UserMapper">
<!-- 開啟本mapper的namespace下的二級快取
type:指定cache介面的實現類的型別,mybatis預設使用PerpertualCache
要和ehcache整合,需要配置type為ehcache實現cache介面的型別
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
type的類在mybatis-ehcachexxx.jar中找
7.4.5、加入ehcache的配置檔案
在classpath下配置ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
</ehcache>
在ehcache-core-2.7.0.jar中有個ehcache-failsafe.xml 裡面的預設配置
屬性說明:
diskStore:指定資料在磁碟中的儲存位置
defaultCache:當藉助CacheManager.add("demoCache")建立cache時,Ehcache便會採用<defaultCache/>指定的管理策略
以下屬性是必須的:
maxElementsMemory:在記憶體中快取的element的最大數目
maxElementsOnDisk:在磁碟上快取的element的最大數目,若是0表示無窮大
eternal:設定快取的elements是否永遠不過期,如果為true,則快取的資料始終有效,
如果為false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷。
overflowToDisk:設定當記憶體快取溢位的時候是否將過期的element快取到磁碟上,以下屬性是可選的
timeToIdleSeconds:當快取在EhCache中的資料前後兩次訪問的時間超過TimeToIdleSeconds的屬性取值時,
這些資料便會刪除,預設值是0,也就是可閒置時間無窮大。
timeToLiveSeconds:快取elements的有效生命期,預設是0,也就是element存活時間無窮大,
diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小預設是30Mb,每個cache都應該有自己的一個緩衝區
7.5、二級應用場景
對於訪問多的查詢請求且使用者對查詢結果實時性要求不高,此時可採用mybatis二級快取技術降低資料庫訪問量,
提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等
實現方法如下:通過設定重新整理間隔時間,由mybatis每隔一段時間自動清空快取,根據資料變化頻率設定快取重新整理間隔flushInterval,
比如設定為30分鐘、60分鐘、24小時等,根據需求而定
7.6、二級快取侷限性
mybatis二級快取對細粒度的資料級別的快取實現不好,比如如下需求:對商品資訊進行快取,
由於商品資訊查詢訪問量大,但是要求使用者每次都能查詢最新的商品資訊,此時如果使用mybatis的二級快取
就無法實現當一個商品變化時只重新整理該商品的快取資訊而不重新整理其他商品的資訊。
因為mybatis的二級緩衝區域以mapper為單位劃分,當一個商品資訊變化會將所有商品資訊的快取資料全部清空,
解決此類問題需要在業務層根據需求對資料有針對性快取。
8、spring和mybatis整合
8.1、整合思路
需要spring通過單例方式關聯SqlSessionFactory。
spring和mybatis整合生成對立物件,使用SqlSessionFactory建立SqlSession(spring和mybatis整合自動完成)
持久層的mapper都需要由spring進行管理
8.2、整合環境
建立一個新的java工程(接近實際開發的工程結構)
jar包:
mybatis-3.4.6的jar包
spring-3.2.5的jar包
mybatis和spring的整合包:早期ibatis和spring整合由spring官方提供,mybatis和spring整合由mybatis提供
工程結構
8.3sqlSessionFactory
在applicationContext.xml皮質sqlSessionFactory
sqlSessionFactory在mybatis和spring整合包下
<!-- 載入配置檔案 -->
<context:property-placeholder
location="classpath:db.properties"
/>
<!-- 資料來源,使用dbcp -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxIdle" value="5"></property>
<property name="maxActive" value="10"></property>
</bean>
<!-- sqlSessionFactory在mybatis-spring.jar中找-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 載入mybatis的配置檔案 -->
<property name="configLocation" value="mybatis/SqlMapConfig.xml"></property>
<!-- 資料來源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
8.4、原始dao開發(和spring整合後)
8.4.1、mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<!-- 在對映檔案中配置很多sql語句
需求:通過id查詢使用者表的記錄
通過select執行資料庫查詢
id:標識對映檔案中的sql
將sql語句封裝到mappedStatement物件中,所以將id稱為statement的id
#{}表示一個佔位符號
#{}:其中的id表示接入輸入的引數,引數名稱就是id,如果輸入引數是簡單型別,#{}中的引數名可以任意,可以value或者其他名稱
resultType:指定sql輸出結果所對映的java物件型別,select指定resultType表示將單條記錄對映成的java物件
-->
<select id="findUserById" parameterType="int" resultType="my.mybatis.po.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
在sqlMapConfig.xml中載入User.xml
<!-- 載入對映檔案 -->
<mappers>
<mapper resource="sqlmap/User.xml"/>
8.4.2、dao(實現類繼承SqlSessionDaoSupport)
public interface UserDao {
//根據id查詢使用者資訊
public User findUserById(int id) throws Exception;
}
dao介面實現類需要注入sqlSessionFactory,通過spring進行注入
這裡spring宣告配置方式,配置dao的bean
讓UserDaoImpl實現類繼承SqlSessionDaoSupport
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{
public User findUserById(int id) throws Exception {
SqlSession sqlSession = this.getSqlSession();
User user = sqlSession.selectOne("test.findUserById", id);
//釋放資源
sqlSession.close();
return user;
}
}
8.4.2.1、Dao介面實現類繼承SqlSessionDaoSupport
使用此種方法即原始dao開發方法,需要編寫dao介面,dao介面實現類、對映檔案
1、在sqlMapConfig.xml中配置對映檔案的位置
<mappers>
<mapper resource="mapper.xml檔案的地址"/>
</mappers>
2、定義dao介面
3、定義dao介面
dao介面實現類方法中可以this.getSqlSession()進行資料增刪改查
4、spring配置
<bean id="userDao" class="my.mybatis.dao.UserDaoImpl"></bean>
8.4.3、配置dao
在applicationContext.xml中配置dao
<!-- 原始dao介面 -->
<bean id="userDao" class="my.mybatis.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
8.4.4、測試程式
public class UserDaoImplTest {
private ApplicationContext applicationContext;
//在setUp這個方法得到spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserById() throws Exception {
UserDao userDao= (UserDao) applicationContext.getBean("userDao");
//呼叫userDao的方法
User user= userDao.findUserById(1);
System.out.println(user);
}
}
8.5、mapper代理開發
8.5.1、mapper.xml和mapper.java
8.5.2、通過MapperFactoryBean建立代理物件
<!-- mapper配置
MapperFactoryBean:根據mapper介面生成代理物件
-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- mapperInterface指定mapper介面 -->
<property name="mapperInterface" value="my.ssm.mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
此方法問題:
需要針對每個mapper進行配置,麻煩
8.5.3、通過MapperScannerConfigurer進行mapper掃描(建議使用)
<!-- mapper批量掃描,從mapper包中掃描出mapper介面,自動建立代理物件並且在spring容器註冊
遵循一些規範:需要將mapper介面類名和mapper.xml對映檔名稱名保持一致,且在一個目錄中
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定掃描的包名
如果掃描多個包,每個包中間使用半形逗號分隔
-->
<property name="basePackage" value="my.ssm.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
如果仍使用之前的SqlSessionFactory,properties不會被載入進來,eclipse也會報錯
SqlMapConfig.xml
和spring 整合後,使用mapper掃描器,這裡不需要配置了
<!-- <package name="my.ssm.mapper"/> -->
8.5.4、測試程式碼
public class UserMapperTest {
private ApplicationContext applicationContext;
//在setUp這個方法得到spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserById() throws Exception {
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
User findUserById = userMapper.findUserById(1);
System.out.println(findUserById);
}
}
9、逆向工程
9.1、什麼是逆向工程
mybatis需要程式設計師自己編寫sql語句,mybatis官方提供逆向工程,可以針對單表自動生成mybatis執行所需要的程式碼(mapper.java和mapper、po)
企業開發中,常用的逆向工程方式:
由資料庫的表生成java程式碼
9.2、下載逆向工程
帶有docs其中的index.html可以檢視配置檔案和執行程式
xml檔案編寫
9.3、使用方法
9.3.1、執行逆向工程
還可以通過eclipse的外掛生成程式碼
建議使用java程式方式,不依賴開發工具
9.3.2、生成程式碼配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="./src" />
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否取出自動生成的註釋 true:是 false:否 -->
<property name="suppressAllcomments" value="true" />
</commentGenerator>
<!-- 資料庫連線的資訊:驅動類、連線地址、使用者名稱、密碼 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="123">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:root"
userId="root" password="123"> </jdbcConnection> -->
<!-- 預設false,把JDBC DECIMAL和NUMERIC型別解析為Integer,為true時把 JDBC DECIMAL 和NUMERIC解析為java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO類的位置 -->
<javaModelGenerator targetPackage="my.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的字尾 -->
<property name="enableSubPackages" value="false" />
<!-- 從資料庫返回的值是否被清理前後代的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper對映檔案生成的位置 -->
<sqlMapGenerator targetPackage="my.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的字尾 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetProject:mapper介面生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="my.ssm.mapper" targetProject=".\src">
<!-- enableSubPackages:是否讓schema作為包的字尾 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定資料庫表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
<!-- 有些表的欄位需要指定java型別 -->
<!-- <table schema="" tableName=""> <columnOverride column="" javaType=""></columnOverride>
</table> -->
</context>
</generatorConfiguration>
9.3.3、執行生成程式
public class GeneratorSqlmap {
public static void main(String[] args) {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (XMLParserException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void generator() throws Exception, XMLParserException {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定逆向工程配置檔案
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}
生成後的程式碼
9.3.4、使用生成的程式碼
需要將生成工程中所生成的打碼拷貝到自己的工程中
測試itemsMapper中的方法
public class ItemsMapperTest {
private ApplicationContext applicationContext;
private ItemsMapper itemsMapper;
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
itemsMapper = (ItemsMapper) applicationContext.getBean("itemsMapper");
}
//插入
@Test
public void testInsert() {
//構造items物件
Items items = new Items();
items.setName("手機");
items.setPrice(2133f);
itemsMapper.insert(items);
}
//根據自定義條件查詢
@Test
public void testSelectByExample() {
ItemsExample itemsExample = new ItemsExample();
//通過criteria構造查詢條件
Criteria createCriteria = itemsExample.createCriteria();
createCriteria.andPicLike("%pic%");
//可能返回多條記錄
List<Items> list = itemsMapper.selectByExampleWithBLOBs(itemsExample);
System.out.println(list);
}
//根據主鍵查詢
@Test
public void testSelectByPrimaryKey() {
Items items = itemsMapper.selectByPrimaryKey(1);
System.out.println(items);
}
//更新資料
@Test
public void testUpdateByPrimaryKey() {
//對所有的欄位更新,先查詢出來再更新
Items items = itemsMapper.selectByPrimaryKey(1);
items.setName("杯具");
itemsMapper.updateByPrimaryKey(items);
//如果傳入的欄位不為空才更新,一般在批量更新中使用,不需要先查詢再更新
// itemsMapper.updateByPrimaryKeySelective(record);
}
}