1. 程式人生 > >MyBatis延遲加載和緩存

MyBatis延遲加載和緩存

節點 出現 一次 架構 htm 清空緩存 log4j ins mybatis緩存

一、延遲加載

1.主對象的加載:

根本沒有延遲的概念,都是直接加載。

2.關聯對象的加載時機:

01.直接加載:

訪問主對象,關聯對象也要加載

02.侵入式延遲:

訪問主對象,並不加載關聯對象

訪問主對象屬性的屬性的時候,關聯對象會被加載

03.深度延遲

訪問主對象,並不加載關聯對象

訪問主對象的屬性的時候,關聯對象也不會被加載

訪問關聯對象或關聯對象的屬性的時候,才會加載關聯對象。

3.一對多延遲加載代碼:

01.實體類代碼:

package cn.pb.bean;

import java.util.Set;

/**
* 國家的實體類
*/
public class Country {
private Integer cId;//國家的編號
private String cName;//國家的名稱

//關聯省會的屬性

private Set<Provincial> provincials;

public Integer getcId() {
return cId;
}

public void setcId(Integer cId) {
this.cId = cId;
}

public String getcName() {
return cName;
}

public void setcName(String cName) {
this.cName = cName;
}

public Set<Provincial> getProvincials() {
return provincials;
}

public void setProvincials(Set<Provincial> provincials) {
this.provincials = provincials;
}


}


package cn.pb.bean;

/**
* 省會對應的實體類
*/
public class Provincial {
private Integer pId; //省會的編號
private String pName; //省會名稱

public Integer getpId() {
return pId;
}
public void setpId(Integer pId) {
this.pId = pId;
}
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
}

02.dao層代碼:

public interface CountryDao {
/**
* 根據國家id 查詢國家的信息 以及國家下面的省會
*/
Country selectCountryById(Integer id);
}

03.mapper.xml代碼:

<mapper namespace="cn.pb.dao.CountryDao">
  <!--01.先根據id查詢出國家信息 多條sql的查詢 可以使用延遲加載策略-->
<select id="selectCountryById" resultMap="countryMap">
select cid,cname from country where cid=#{xxx}
</select>

  <!--03.根據國家id 查詢出省份信息 -->
    <select id="selectProvincialByCountryId" resultType="Provincial">
select pid,pname from provincial
where countryid=#{xxx}
<!--#{xxx} 對應的就是resultMap中 collection節點下面的column -->
</select>

  <!--02.國家的映射信息-->
    <resultMap id="countryMap" type="Country">
<id property="cId" column="cid"/>
<result property="cName" column="cname"/>
<!--設置關聯的集合屬性
select:需要關聯的查詢語句
column: select關聯語句中需要的參數 -->
<collection property="provincials" ofType="Provincials"
select="selectProvincialByCountryId"
column="cid"/>
</resultMap>


</mapper>

04.測試代碼:

public class CountryTest {
CountryDao dao=null;
SqlSession session=null;
Logger log= Logger.getLogger(CountryTest.class);

@Before
public void before(){

//獲取session
session= SessionFactoryUtil.getSession();
//獲取執行的類對象
dao=session.getMapper(CountryDao.class);
}

/**
* 在所有的test測試方法執行之後 都要執行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}

  <!--只輸出主加載對象 只會有一條sql語句-->
@Test
public void testSelectCountryById(){
Country country=dao.selectCountryById(1);
log.debug("根據id查詢國家信息"+country);
}

  
  <!--輸出關聯對象的信息 會有兩條sql語句-->
@Test
public void testSelectCountryById(){
Country country=dao.selectCountryById(1);
log.debug("根據id查詢國家信息"+country.getProvincials());
    }

}


4.配置延遲加載:

在mybatis.xml文件中配置

<settings>
<!-- 全局性地啟用或禁用延遲加載。當禁用時,所有關聯的配置都會立即加載。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--當啟用後,一個有延遲加載屬性的對象的任何一個延遲屬性被加載時,該對象
的所有的屬性都會被加載。否則,所有屬性都是按需加載。 侵入式延遲 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

二、MyBatis緩存

1.查詢緩存的作用:

查詢緩存的使用,主要是為了提供查詢訪問速度。將用戶對同一數據的重復查詢過程簡化,不再每次均從數據庫查詢獲取結果數據,從而提高訪問速度。

2.關於緩存的說明:

01.MyBatis查詢緩存機制。根據緩存區的作用域與生命周期,可劃分為兩種:一級緩存和二級緩存。

02.MyBatis查詢緩存的作用域是根據映射文件的namespace劃分的,相同的namespace的mapper查詢數據放在同一個緩存區域。不同namespace下的數據互不幹擾。無論是一級緩存還是二級緩存,都是按照namespace進行分別存放的。

03.但是一級、二級緩存的不同之處在於,SqlSession一旦關閉,則SqlSession中的數據將不存在,即一級緩存就不復存在。而二級緩存的生命周期與整個應用同步,與SqlSession是否關閉無關。換句話說,一級緩存是在同一線程(同一SqlSession)間共享數據,而二級緩存是在不同線程(不同的SqlSession)間共享數據。

04. 一級緩存: 基於PerpetualCache 的 HashMap本地緩存,其生命周期為 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。

05. 二級緩存與一級緩存其機制相同,默認也是采用 PerpetualCache,HashMap存儲,不同在於其存儲作用域為 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。

06. 對於緩存數據更新機制,當某一個作用域(一級緩存Session/二級緩存Namespaces)進行了 C/U/D 操作後,默認該作用域下所有 select 中的緩存將被clear。

3.一級緩存:

01.一級緩存存在的證明代碼:

001.dao層代碼:

/**
* 根據學生的編號查詢對應的信息
* 驗證一級緩存的存在
*/
Student selectStudentById(Integer sId);

002.mapper.xml代碼:

<mapper namespace="cn.pb.dao.StudentDao">
<!-- 查詢指定學生的信息 驗證一級緩存的存在 -->
<select id="selectStudentById" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>
</mapper>

003.測試代碼:

package cn.pb;


import cn.pb.bean.Student;
import cn.pb.dao.StudentDao;
import cn.pb.util.SessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class StudentTest {
StudentDao dao;
SqlSession session;
Logger logger=Logger.getLogger(StudentDao.class);
@Before
public void before(){
session= SessionFactoryUtil.getSession();
dao=session.getMapper(StudentDao.class);
}

/**
* 在所有的test測試方法執行之後 都要執行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}


/**
* 驗證一級緩存的存在
* myBatis的一級緩存是一直開啟的,並且不能關閉!
*/
@Test
public void test1(){
Student student=dao.selectStudentById(1);
logger.debug("第一次查到的id為1的學生:"+student);
//再次查詢相同的id對象
Student student2 = dao.selectStudentById(1);
logger.debug("第2次查到的id為1的學生:"+student2);
}
}


02.一級緩存--從緩存中查找數據的依據:

001.緩存的底層實現是一個Map,Map的value是查詢結果

002.Map的key,即查詢依據,使用的ORM架構不同,查詢依據是不同的

003.MyBatis的查詢依據是:Sql的id+SQL語句

004.Hibernate的查詢依據是:查詢結果對象的id

005.驗證代碼:

a.dao層代碼:

/**
* 驗證mybatis緩存查詢的依據!
*/
Student selectStudentById2(Integer sId);


b.mapper.xml代碼:

<mapper namespace="cn.pb.dao.StudentDao">

<!-- 查詢指定學生的信息 驗證mybatis緩存查詢的依據!
兩個查詢語句的id不一致,但是sql語句一樣-->
<select id="selectStudentById2" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>


</mapper>

c.測試代碼:

/**
* 驗證查詢的依據
* 兩個查詢都是查詢id為1的學生對象,但是查詢語句的id不一致
*/

@Test
public void test2() {
Student student = dao.selectStudentById(1);
logger.debug("第1次查到的id為1的學生:"+student);
//再次查詢相同的id對象
Student student2 = dao.selectStudentById2(1);
logger.debug("第2次查到的id為1的學生:"+student2);
}

得到的結論是:
mybatis的查詢依據是 : mapper文件中sql的id + sql語句!
hibernate底層查詢的依據是: 查詢對象的id!

其實緩存的底層是一個map,
map的key就是查詢依據,value是查詢的結果!



03.增、刪、改對一級緩存的影響:

增刪改會清空一級緩存:註意:必須使用insert標簽,不能使用select,否則實驗做不成功

001.dao層代碼:

/**
* 驗證增刪改查對一級緩存的影響!
*/
void addStudent(Student student);

002.mapper.xml代碼:

<mapper namespace="cn.pb.dao.StudentDao">
<!-- 查詢指定學生的信息 驗證一級緩存的存在 -->
<select id="selectStudentById" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>

<!--新增一個學生-->
<insert id="addStudent">
<!--#{sId},#{sName} 對應的是實體類中的屬性名 -->
insert into stu values(#{sId},#{sName})
</insert>
</mapper>

003.測試代碼:

package cn.pb;


import cn.pb.bean.Student;
import cn.pb.dao.StudentDao;
import cn.pb.util.SessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class StudentTest {
StudentDao dao;
SqlSession session;
Logger logger=Logger.getLogger(StudentDao.class);
@Before
public void before(){
session= SessionFactoryUtil.getSession();
dao=session.getMapper(StudentDao.class);
}

/**
* 在所有的test測試方法執行之後 都要執行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}






/**
* 驗證增刪改對一級緩存的影響
* 之前是只有一條查詢語句!
* 但是加上新增語句之後發現出現了再次查詢!
*
* 因為增刪改查操作都要清空緩存,把數據同步到數據庫,
* 保證後續的查詢得到正確的結果集!
*/
@Test
public void test3() {


Student student = dao.selectStudentById(1);
logger.debug("新增之前查詢到的stuent:"+student);
dao.addStudent(new Student(55, "新增學生"));
session.commit();
//再次查詢相同的id對象
Student student2 = dao.selectStudentById(1);
logger.debug("新增之後查詢到的student:"+student2);
}

}

4.二級緩存:

01.內置二級緩存

001.由於MyBatis從緩存中讀取數據的依據與SQL的id相關,而非查詢出的對象。所以,使用二級緩存的目的,不是在多個查詢間共享查詢結果(所有查詢中只要查詢結果中存在該對象,就直接從緩存中讀取,這是對查詢結果的共享,Hibernate中的緩存就是為了在多個查詢間共享查詢結果,但MyBatis不是),而是為了防止同一查詢(相同的Sql id,相同的sql語句)的反復執行。

002.MyBatis內置的二級緩存為

https://my.oschina.net/KingPan/blog/280167

http://www.mamicode.com/info-detail-890951.html

http://blog.csdn.net/isea533/article/details/44566257

http://blog.csdn.net/u010676959/article/details/43953087

http://blog.csdn.net/xiadi934/article/details/50786293

02.如何開啟二級緩存---三條件

001.你cacheEnabled=true,默認值為true

002.你得在Mapper文件中,<cache/>

003.Entity Implements Serializable

03.增刪改對二級緩存的影響

001.增刪改也會清空二級緩存

002.對於二級緩存的清空實質上是對value清空為null,key依然存在,並非將Entry<k,v>刪除

003.從DB中進行select查詢的條件是:

  0001.緩存中根本不存在這個key

  0002.存在key對應的Entry,但是value為null

04.二級緩存的配置

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

這個更高級的配置創建了一個 FIFO 緩存,並每隔 60 秒刷新,存數結果對象或列表的 512 個引用, 而且返回的對象被認為是只讀的

05.可用的收回策略

001.LRU – 最近最少使用的:移除最長時間不被使用的對象。

002 FIFO – 先進先出:按對象進入緩存的順序來移除它們。

003.SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。

004.WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。

默認的是 LRU

005.flushInterval(刷新間隔)可以被設置為任意的正整數,而且它們代表一個合理的毫秒

006.形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。

007.size(引用數目)可以被設置為任意正整數,要記住你緩存的對象數目和你運行環境的 可用內存資源數目。默認值是 1024。

008.readOnly(只讀)屬性可以被設置為 true 或 false。只讀的緩存會給所有調用者返回 緩 存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優勢。 可讀寫的緩存 會返回緩存對象的拷貝(通過序列化) 。這會慢一些,但是安全,因此默認是 false。

06.二級緩存的關閉:

   001.局部關閉

  在mapper文件中修改

    <select id="selectStudentById"  useCache="false" resultType="Student">
       增加了useCache="false"  相當於 局部關閉 2級緩存  useCache默認值為true===》把查詢放入2級緩存

  002.全局關閉

  在mybatis.xml文件中增加

      <settings>
        <!--全局關閉2級緩存  -->
        <setting name="cacheEnabled" value="false"/>
      </settings>

07.二級緩存的使用原則:

001. 很少被修改的數據 
002. 不是很重要的數據,允許出現偶爾並發的數據 
003. 不會被並發訪問的數據 
004.多個namespace不能操作同一張表
005.不能在關聯關系表上執行增刪改操作

MyBatis延遲加載和緩存