Hibernate學習4 二級快取強化和事務管理 註解方式實現
如何證明二級快取和一級快取的物件是不一樣的?
//第一次查詢
Country country = session.get(Country.class, 2);
System.out.println("country = " + country);
//已經把session清空了,然後不通過select還能查詢導資料說明二級快取確實存在
session.clear ();
//如何證明二級快取和一級快取物件不一樣,去除其中一個tostring看原始地址是否一致即可
//二級快取快取得是物件的詳情內容,不是快取的引用 二級換粗你的類快取 那麼集合快取快取的是什麼呢?
//第2次查詢
Country country1 = session.get(Country.class, 2 );
System.out.println("country1 = " + country1);
session.clear();
//第3次查詢
Country country2 = session.get(Country.class, 2);
System.out .println("country2 = " + country2);
如何證明二級快取的集合裡面儲存的是什麼?
//第一次查詢
Country country = session.get(Country.class, 1);
Set<Minister> ministers = country.getMinisters();
System.out.println("ministers.size = " +ministers.size());
//第2次查詢
Country country2 = session.get(Country.class, 1);
Set<Minister> ministers2= country2.getMinisters();
System.out.println("ministers.size = " +ministers2.size());
session.clear();
/*
* 類快取物件存放在專門的一個稱為實體區域的快取中,快取的內容為物件的詳情,
* 集合快取物件存放在專門的一個稱為集合區域的快取中,快取的內容為集合中所包含的物件的id
*/
//第3次查詢
Country country3 = session.get(Country.class, 1);
Set<Minister> ministers3 = country3.getMinisters();
System.out.println("ministers.size = " +ministers3.size());
Query快取
首先需要開啟Query查詢的總開關,在總配置檔案中進行設定
<!-- 開啟Query查詢快取總開關-->
<property name="hibernate.cache.use_query_cache">true</property>
證明Query查詢 同時也需要開啟Query查詢子開關
//第一次查詢
String hql = "from Country where cid=2";
//開啟query查詢快取總開關
Country country = (Country) session.createQuery(hql).setCacheable(true).uniqueResult();
System.out.println("country = " + country);
//第2次查詢
Country country2 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult();
System.out.println("country2 = " + country2);
/* session.clear();*/
//第3次查詢
Country country3 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult();
System.out.println("country3 = " + country3);
證明Query查詢內容
//證明 Query快取的內容 :其從Query快取中查詢的依據不再是查詢結果物件的id,而是Query查詢語句Query查詢結果存到Query快取時
//其key為Query查詢語句,value未查詢結果。每次不同sql語句需要多次對country進行查詢
//第一次查詢
String hql = "from Country where cid=2";
//開啟query查詢快取總開關
Country country = (Country) session.createQuery(hql).setCacheable(true).uniqueResult();
System.out.println("country = " + country);
//第2次查詢 cid = 2
String hql2 = "from Country where cid in (2)";
Country country2 = (Country) session.createQuery(hql2).setCacheable(true).uniqueResult();
System.out.println("country2 = " + country2);
/* session.clear();*/
//第3次查詢
String hql3 = "from Country where cid like 2";
Country country3 = (Country) session.createQuery(hql3).setCacheable(true).uniqueResult();
System.out.println("country3 = " + country3);
//執行更新
Country country = session.get(Country.class, 2);
System.out.println("country = " + country);
executeUpdate的方法
//此跟新繞過了一級快取,沒有進行實際的跟新 不建議這樣寫,會出現快取資料和DB資料不一致的情況
//總會執行,次修改與快照沒有任何關係,不和快照進行對比
String hql = "update Country set cname='吉隆坡' where cid=2";
session.createQuery(hql).executeUpdate();
//查詢結果肯定會放入到二級快取和一級快取 ,先查詢一級快取在查詢二級快取,但為啥呢麼沒有直接從二級快取總直接讀取改資料,而是從DB中直接查詢資料
Country country2 = session.get(Country.class, 2);
System.out.println("country2.Name = " +country2.getCname());
session.clear();
//此查詢沒有繞過 二級快取 但為啥呢麼沒有直接從二級快取總直接讀取改資料,而是從DB中直接查詢資料
//因為Query的executeUpdate()方法會修改二級快取物件中的一個屬性,UpdateTimestamp,修改時間戳
//什麼意思呢?實際上二級快取物件中快取的內容要比一級緩內容多一個屬性,修改時間戳,
//一旦這個屬性被修改,那麼,查詢會不從二級快取中讀取資料,而是直接從DB中讀取資料
在沒有提交之前就能查詢到修改後的資料,說明session存在快取,這就是為什麼會發生髒讀的原因
Country country3 = session.get(Country.class, 2);
System.out.println("country2.Name = " +country3.getCname());
與二級快取相關的方法 是session的方法 來建立
Session session = sessionFactory.getCache()
事務處理: 事務四大性(SCID)
原子性:事務中的全部操作在資料庫中不可分割的,要麼全部完成,要麼均不執行
一致性:幾個併發執行的事務,執行結果必須與按某一順序序列執行的結果相一致
隔離性:事務的執行不受其他事務的干擾,事務執行的中間結果對其他事務必須是透明的(互不干擾,各行其道)
永續性:對於任意已提交事務,系統必須保證該事務對資料庫的改變不被丟失,即使資料庫出現障礙
事務的併發問題:
多個事務對資料庫的併發操作,可能會破壞事務的隔離性和一致性
髒讀 (Dirty read) 事務A讀取了事務B未提交的資料 A事務修改了資料歲未提交,但是B讀取了該資料,然後A發生了回滾,此時B讀取了不存在的資料
不可重複讀
事務A讀取了某個資料,事務B對該資料進行修改、刪除、增加後,當事務A再次讀,發現不一致稱為不可重複讀
丟失修改 兩個事務A和B,讀入同一資料並修改,B提交的結果破壞了A提交的結果,導致A的修改丟失
幻讀 也稱為虛讀,現實存在的主要是併發訪問所引發的問題 在同一事務中,雖然多次執行 相同的查詢,查詢結果是一樣的,但是後面讀取的資料已經不是資料庫的真正資料,是虛的資料,就是DB中的幻讀
事務的隔離級別:
四個隔離級別:讀取未提交 讀取已提交 可重複讀 序列化 JDK中的connection介面已經定義了事務級別
封鎖機制:事務的隔離級別,是DBMS隱式的為資料添加了鎖
鎖:可以分為
樂觀鎖、:每次訪問資料時,都會樂觀的認為其他事務此時肯定不會同時修改該資料,會在程式碼中通過鎖的狀態來判斷資料是或否被其他事務修改過是在程式碼中完成的,所以樂觀鎖是加在程式碼中的 實現原理:版本號 + 時間戳 (每次提交之前先看一下版本號是否一致)
悲觀鎖、:其在訪問資料庫時,在資料庫中就會先給資料加鎖,以防止其他事務同時修改該資料,所以鎖是載入在資料庫中的
分為兩種,排它鎖(X鎖 寫鎖)和共享鎖( S鎖 讀鎖)
select * from student where id in(1,2,3) lock in share mode ,加讀鎖的目的:防止其他事務對該資料加其他鎖 (寫鎖)只能在讀取資料的時候只能加一把讀鎖
如何解決併發問題:
設定Hibernate隔離級別
Hibernate的註解:替換對映檔案
一對多雙向關聯:
Hibernate的重點:
單表查詢會寫
關聯關係對映
快取
快照 ehcache
事務
資料庫的基本理論(四大 特性 )
下面介紹一個學習的demo
jar包下載連結:
首先是結構圖
實體類:Country.java
package com.vrv.yinkailong.bean;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
//自定義一個類 國家可以看到部長
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class Country {
@Id
@GeneratedValue(generator="xxx")
@GenericGenerator(name="xxx",strategy="native")
private Integer cid;
private String cname;
//關聯屬性
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="countryId")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
private Set<Minister> ministers;
public Country() {
ministers = new HashSet<Minister>();
}
public Country(String cname) {
this(); //直接去執行前面的 ministers = new HashSet<Minister>();
this.cname = cname;
}
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<Minister> getMinisters() {
return ministers;
}
public void setMinisters(Set<Minister> ministers) {
this.ministers = ministers;
}
@Override
public String toString() {
return "Country [cid=" + cid + ", cname=" + cname + ", ministers=" + ministers + "]";
}
}
另一個實體類:Minister.java
package com.vrv.yinkailong.bean;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
//自定義一個屬性 部長 看不到國家
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class Minister {
@Id
@GeneratedValue(generator="xxx")
@GenericGenerator(name="xxx",strategy="native")
private Integer mid;
private String mname;
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="countryId")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
private Country country;
public Minister() {
super();
}
public Minister(String mname) {
super();
this.mname = mname;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public Integer getMid() {
return mid;
}
public void setMid(Integer mid) {
this.mid = mid;
}
public String getMname() {
return mname;
}
public void setMname(String mname) {
this.mname = mname;
}
@Override
public String toString() {
return "Minister [mid=" + mid + ", mname=" + mname + "]";
}
}
提取工具類:HiberUtil.java
package com.vrv.yinkailong.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
//自定義一個工具類
public class HiberUtil {
private static SessionFactory sessionFactory = null;;
//必須保證sessionfactory是單例的,佔資源
public static Session getSession()
{
/*Configuration configure = new Configuration().configure();
//2:建立session工廠物件
SessionFactory sessionFactory = configure.buildSessionFactory();
//3:獲取session物件
Session session = sessionFactory.getCurrentSession(); */
SessionFactory sessionFactory = getSessionFactory();
return sessionFactory.getCurrentSession();
}
//抽取區域性方法 alt + shift+ m
public static SessionFactory getSessionFactory() {
//此處做到單例,先判斷在返回,能夠保證做到單例
if (sessionFactory == null || sessionFactory.isClosed()) {
sessionFactory = new Configuration().configure().buildSessionFactory();
}
return sessionFactory;
}
}
引入的ehcache.xml檔案,具體操作在Hibernate 3中有
<!--
Mandatory Default Cache configuration. These settings will be applied to caches
created programmtically using CacheManager.add(String cacheName)
最多可以放多少個二級物件 是否永久 最長空閒時間s 最長生存時間s 溢位之後存到硬碟 應用技術 硬碟是否存放二級資料 硬碟到期執行緒時間s
記憶體儲存驅除策略 LRU 最近最少使用演算法 FIFO 先進先出 LFU 使用頻率最小 LRU 未被使用時間最少 overflowToDisk="true" diskPersistent="false"-->
<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>
主配置檔案 hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- session的不同含義 http:session (view層) hibernate :session(DAO層) MyBatis: session(SQL session) -->
<!-- 三大資料庫 Mysql DB2 Oracal -->
<hibernate-configuration>
<session-factory>
<!-- DB連結四要素 -->
<property name="hibernate.connection.driver_class" >com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/test</property><!-- /// 相當於127.0.0.1:3306 -->
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<!-- 需要指定方言,才能生成相應的sql 語句 去hibernate core 裡面找相應的版本 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 資料來源 資料庫連線池 連結資料庫的那個容器我們成為資料庫連線池 決定啥時候建立連結 :一般是先建立好,然後使用者需要使用的時候直接從資料庫連線池獲取-->
<!--佔用資源 :釋放連線 連線個數 當達到下限需要再建立 然後釋放之後需要設定最多放置多少條(上限) 空閒時間 :空閒多久會自動進行銷燬 使用第三方機構的連線池-->
<!-- 連結提供者 -->
<property name="hibernate.co nnection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<!-- 保證在當前執行緒上下文中獲取到的session是同一個session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 配置自動建表 DDL資料庫定義語言 DML資料庫操作語言 DQL 資料來源查詢語言 create 每執行一次都會重新見一個表 update只會做更新 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 除錯常用 :顯示SQL -->
<property name="hibernate.show_sql">true</property>
<!-- 生成sql語言進行格式化 -->
<property name="hibernate.format_sql">true</property>
<!-- 開啟二級快取 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 專門給二級快取開闢了一個空間 註冊二級快取區域工廠 hibernate.cache.region.factory_class 在二級快取的時候應該去除 .class 否則 會報錯-->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
<!-- 實體類對映 相比以往的resource 的路徑,這裡是實體類對映-->
<mapping class="com.vrv.yinkailong.bean.Country"/>
<mapping class="com.vrv.yinkailong.bean.Minister"/>
</session-factory>
</hibernate-configuration>
最後是測試類:MyTest.java
package com.vrv.yinkailong.test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.hql.internal.ast.HqlASTFactory;
import org.junit.Test;
import com.vrv.yinkailong.bean.Country;
import com.vrv.yinkailong.bean.Minister;
import com.vrv.yinkailong.util.HiberUtil;
public class MyTest {
@Test
public void test00()
{
Session session = HiberUtil.getSession();
try {
session.getTransaction().begin();
//session.beginTransaction();
Minister minister1 = new Minister("洛杉磯");
Minister minister2 = new Minister("自由女神");
Minister minister3 = new Minister("紐約");
Minister minister4 = new Minister("北京");
Minister minister5 = new Minister("新疆");
Minister minister6 = new Minister("武漢");
Country country = new Country("USA");
country.getMinisters().add(minister1);
country.getMinisters().add(minister2);
country.getMinisters().add(minister3);
session.save(country);
Country country2 = new Country("中國");
country2.getMinisters().add(minister4);
country2.getMinisters().add(minister5);
country2.getMinisters().add(minister6);
session.save(country2);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
}
}
//證明後面查詢資料都是從二級查詢中獲取的,沒有經過在此二次查詢
@Test
public void test01()
{
Session session = HiberUtil.getSession();
try {
session.getTransaction().begin();
//第一次查詢
Country country = session.get(Country.class, 2);
System.out.println("country = " + country);
//已經把session清空了,然後不通過select還能查詢導資料說明二級快取確實存在
session.clear();
//第2次查詢
Country country1 = session.get(Country.class, 2);
System.out.println("country1 = " + country1);
session.clear();
//第3次查詢
Country country2 = session.get(Country.class, 2);
System.out.println("country2 = " + country2);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
}
}
///演示集合快取中所快取的內容到底是什麼? 是集合物件的所有id 沒有詳情 所以他需要再次根據id去查詢
@Test
public void test01Collection()
{
Session session = HiberUtil.getSession();
try {
session.getTransaction().begin();
//第一次查詢
Country country = session.get(Country.class, 1);
Set<Minister> ministers = country.getMinisters();
System.out.println("ministers.size = " +ministers.size());
//第2次查詢
Country country2 = session.get(Country.class, 1);
Set<Minister> ministers2= country2.getMinisters();
System.out.println("ministers.size = " +ministers2.size());
session.clear();
/*
* 類快取物件存放在專門的一個稱為實體區域的快取中,快取的內容為物件的詳情,
* 集合快取物件存放在專門的一個稱為集合區域的快取中,快取的內容為集合中所包含的物件的id
*/
//第3次查詢
Country country3 = session.get(Country.class, 1);
Set<Minister> ministers3 = country3.getMinisters();
System.out.println("ministers.size = " +ministers3.size());
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
}
}
//檢視檔案的臨時路徑並輸出
@Test
public void test()
{
String PATH = System.getProperty("java.io.tmpdir");
System.out.println(PATH);
}
}
執行結果截圖:
其中一個操作截圖:
這些程式都是本人親手寫的,如果有需要互相學習和demo的可以直接留言,本人每天都會堅持寫一篇部落格,希望遇見真正熱愛技術的你!