1.get和load方法的區別
1.1 獲取Session的兩種方式
Session是Hibernate的核心,我們在創建了SessionFactory對象後,有兩種方式可以獲取Session實例
- Session session = sessionFactory.
openSession();
採用openSession方法獲取Session實例時,SessionFactory直接創建一個新的Session實例,並且在使用完成後需要調用close方法進行手動關閉。 - Session session = sessionFactory.
getCurrentSession();
而getCurrentSession方法創建的Session實例會被綁定到當前線程中,它在提交或回滾操作時會自動關閉。
1.2 Session的事務管理—Transaction對象
我們通過Session開啓事務,獲取Transaction
對象
//開啓事務
Transaction transaction = session.beginTransaction();
// 提交事務
transaction.commit();
// 回滾事務
transaction.rollback();
Session執行完數據庫操作後,要使用Transaction接口的commit()方法進行事務提交,才能真正的將數據操作同步到數據庫中。發生異常時,需要使用rollback()方法進行事務回滾,以避免數據發生錯誤。
由於Session對象屬於線程不安全,當一個線程有多個Session時,無法保證事務。所以一個線程要保障只有一個Session對象。
爲了保證一個線程中只有一個Session,所以在實際開發中,我們將Session綁定到當前線程中
1.3 get方法和load方法
Hibernate中get()和load()的區別
在hibernate.cfg.xml中開啓顯示SQL語句
<property name="hibernate.show_sql">true</property>
(1)實體表和映射文件
Customer.java
public class Customer {
private Long custId;// 客戶編號
private String custName;// 客戶名稱
private String custSource;// 客戶信息來源
private String custIndustry;// 客戶所屬行業
private String custLevel;// 客戶級別
private String custAddress;// 客戶聯繫地址
private String custPhone;// 客戶聯繫方式
//一對多關係映射:一個客戶可以對應多個聯繫人
private Set<LinkMan> linkmans = new HashSet<LinkMan>();
......
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.itcast.domain">
<class name="Customer" table="cst_customer">
<id name="custId" column="cust_id">
<generator class="native"></generator>
</id>
<property name="custName" column="cust_name"></property>
<property name="custSource" column="cust_source"></property>
<property name="custIndustry" column="cust_industry"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
<set name="linkmans" table="cst_linkman" cascade="save-update,delete" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="cn.itcast.domain.LinkMan"></one-to-many>
</set>
</class>
</hibernate-mapping>
LinkMan.java
public class LinkMan implements Serializable {
private Long lkmId;//聯繫人編號(主鍵)
private String lkmName;//聯繫人姓名
private String lkmGender;//聯繫人性別
private String lkmPhone;//聯繫人辦公電話
private String lkmMobile;//聯繫人手機
private String lkmEmail;//聯繫人郵箱
private String lkmPosition;//聯繫人職位
private String lkmMemo;//聯繫人備註
private Customer customer;//對應聯繫人表中的外鍵
......
}
LinkMan.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 導入DTD約束
位置:在Hibernate的核心jar包(hibernate-core-5.0.7.Final.jar)中名稱爲hibernate-mapping-3.0.dtd
明確該文件中的內容:
實體類和表的對應關係
實體類中屬性和表的字段的對應關係
-->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.itcast.domain"><!-- package屬性用於設定包的名稱,接下來該配置文件中凡是用到此包中的對象時都可以省略包名 -->
<!-- 建立實體類和表的對應關係 -->
<class name="LinkMan" table="cst_linkman">
<!-- 映射主鍵 -->
<id name="lkmId" column="lkm_id">
<!-- 配置主鍵的生成策略 -->
<generator class="native"></generator>
</id>
<!-- 映射其他字段 -->
<property name="lkmName" column="lkm_name"></property>
<property name="lkmGender" column="lkm_gender"></property>
<property name="lkmPhone" column="lkm_phone"></property>
<property name="lkmMobile" column="lkm_mobile"></property>
<property name="lkmPosition" column="lkm_position"></property>
<property name="lkmEmail" column="lkm_email" ></property>
<property name="lkmMemo" column="lkm_memo"></property>
<!-- 多對一關係映射
many-to-one標籤:用於建立多對一的關係映射配置
屬性:
name:指定的實體類中屬性的名稱
class:該屬性所對應的實體類名稱。如果在hibernate-mapping上沒有導包,則需要寫全限定類名
column:指定從表中的外鍵字段名稱
-->
<many-to-one name="customer" class="cn.itcast.domain.Customer" column="lkm_cust_id"></many-to-one>
</class>
</hibernate-mapping>
(2)dao類
public class CustomerDao {
/**
* 根據id查詢客戶信息 get立即加載
*/
public static Customer findOneById(long id){
Session session = null;//Session對象
try{
// 查詢不需要開啓事務,只有對數據庫內容進行多次增刪改操作時
session = HibernateUtil.openSession();
Customer newCustomer = session.get(Customer.class, id);
return newCustomer;
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();//釋放資源
}
return null;
}
/**
* 根據id查詢客戶信息 load延遲加載
*/
public static Customer loadOneById(long id){
Session session = null;//Session對象
try{
// 查詢不需要開啓事務,只有對數據庫內容進行多次增刪改操作時
session = HibernateUtil.openSession();
Customer newCustomer = session.load(Customer.class, id);
return newCustomer;
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();//釋放資源
}
return null;
}
}
(3)工具類獲取session
HibernateUtil
public class HibernateUtil {
private static SessionFactory factory;
// 使用靜態代碼塊獲取SessionFactory
static {
//細節:Hibernate把所有可預見的異常,都轉成了運行時異常(工具中不會提示要添加異常塊)
try {
// 加載hibernate.cfg.xml配置文件
Configuration config = new Configuration().configure();
factory = config.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取一個新的Session對象(每次都是一個新對象)
* @return
*/
public static Session openSession(){
return factory.openSession();
}
/**
* 獲取一個新的Session對象(每次都是一個新對象)
* @return
*/
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
}
(4)測試get方法
@Test
public void testGet(){
Customer oneById = CustomerDao.findOneById(1L);
System.out.println("-------------------------");
System.out.println(oneById);
}
測試結果如下:
我們發現確實有執行sql,但最後還是報錯了,報錯的位置在Customer的toString方法
中,在獲取Customer中的linkmans屬性時報錯,這是因爲在執行get方法
時,查詢的sql,只針對於cst_customer表(等於select * from cst_customer where cust_id = ?),所以不會填充linkmans屬性,而在CustomerDao方法的最後,我們手動關閉了session
,所以在test方法中,再想通過session去懶加載就加載不了了
解決方式:
- 1.刪除finally中的session.close();
不可取,使用openSession時,必須要手動關閉session,如果此次不關閉,session就會一直存在,消耗性能 - 2.在session沒關閉時,使用linkmans對象,這樣就會去執行sql,初始化linkmans對象了
public static Customer findOneById(long id){
Session session = null;//Session對象
try{
session = HibernateUtil.openSession();
Customer newCustomer = session.get(Customer.class, id);
System.out.println("---------------------------------");
System.out.println(newCustomer);
return newCustomer;
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();//釋放資源
}
return null;
}
(5)測試load方法
@Test
public void tesLoad(){
Customer customer = CustomerDao.loadOneById(1L);
System.out.println(customer);
}
執行結果:我們發現這次連sql語句都沒有,報錯的情況也是no session,所以load方法獲取的對象,也是在使用時纔去執行sql
當調用load()方法的時候會返回一個目標對象的代理對象,在這個代理對象中只存儲了目標對象的ID值,只有當調用除ID值以外的屬性值的時候纔會發出SQL查詢的。
public static Customer loadOneById(long id){
Session session = null;//Session對象
try{
// 查詢不需要開啓事務,只有對數據庫內容進行多次增刪改操作時
session = HibernateUtil.openSession();
Customer newCustomer = session.load(Customer.class, id);
System.out.println(newCustomer.getCustId());
return newCustomer;
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();//釋放資源
}
return null;
}
這個查詢結果也證明了剛剛說的話
解決方式:和get方法一樣,在session未關閉前,使用他們
可以使用 Hibernate.initialize(xx);方法,不過這個方法需要對類中的屬性對象也要使用
public static Customer loadOneById(long id){
Session session = null;//Session對象
try{
// 查詢不需要開啓事務,只有對數據庫內容進行多次增刪改操作時
session = HibernateUtil.openSession();
Customer newCustomer = session.load(Customer.class, id);
Hibernate.initialize(newCustomer);
Hibernate.initialize(newCustomer.getLinkmans());
return newCustomer;
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();//釋放資源
}
return null;
}
總結:
- 返回值:
get()返回的是查詢出來的實體對象
而load()查詢出來的是一個目標實體的代理對象。 - 查詢時機:
get()在調用的時候就立即發出SQL語句查詢
而load()在訪問非ID屬性的時候纔會發出查詢語句並且將被代理對象target填充上,但是如果這個動作發生在Session被關閉後的話就會拋出LazyInitializationException。 - 查詢結果爲空時:
get()拋出NullPointerException
load()拋出ObjectNotFoundException
2.持久化類和對象標識符
2.1 持久化類的編寫規範
- 持久化類需要提供無參數的構造方法。因爲在Hibernate的底層需要使用反射生成類的實例。
- 持久化類的屬性需要私有,對私有的屬性提供公有的get和set方法。因爲在Hibernate底層會將查詢到的數據進行封裝。
- 持久化類的屬性要儘量使用包裝類的類型。
- 持久化類要有一個唯一標識OID與表的主鍵對應。
- 持久化類儘量不要使用final進行修飾。
3.一級緩存和二級緩存
3.1 一級緩存:session
Hibernate的一級緩存就是指Session緩存
在使用Hibernate查詢對象的時候,首先會使用對象屬性的OID值在Hibernate的一級緩存中進行查找,如果找到匹配OID值的對象,就直接將該對象從一級緩存中取出使用,不會再查詢數據庫;如果沒有找到相同OID值的對象,則會去數據庫中查找相應數據。(session關閉,緩存消失)
Hibernate的一級緩存的作用就是減少對數據庫的訪問次數(降低IO讀寫次數)。
Hibernate的一級緩存有如下特點:
- 一級緩存默認是打開狀態
- 一級緩存的使用範圍是session範圍(從session創建到session關閉)
- 一級緩存中
存儲的數據必須都是持久態數據
@Test
public void testCache(){
//從Hibernate封裝的工具類中獲取Session對象
Session session=HibernateUtil.openSession();
//開啓事務
Transaction tx=session.beginTransaction();
//第一次執行get方法:一級緩存中無數據,會去數據庫中查詢
Customer c1=session.get(Customer.class, 100L);
System.out.println("One : "+c1);
//第二次執行get方法:一級緩存中有數據,直接獲取緩存中的數據
Customer c2=session.get(Customer.class, 100L);
System.out.println("Two : "+c2);
System.out.println(c1==c2);//結果爲true
tx.commit();
session.close();
}
一級緩存執行過程
一級緩存的特性:快照機制
public void testUpdateName(){
//從Hibernate封裝的工具類中獲取Session對象
Session session=HibernateUtil.openSession();
//開啓事務
Transaction tx=session.beginTransaction();
//第一步:先查詢出客戶信息(根據ID查詢)
Customer c=session.get(Customer.class, 95L);
//第二步:對查詢出的客戶實體進行修改(修改名稱)
c.setCustName("傳智.黑馬程序員");
//第三步:調用Hibernate方法實現更新操作
//session.update(c); //常規方式是要調用update方法,但此處省略看看執行結果
tx.commit();
session.close();
}
解釋:以上java程序中沒有直接調用update方法,同樣也對數據修改成功。主要是藉助了Hibernate的快照功能
Hibernate 向一級緩存放入數據時,同時複製一份數據放入到Hibernate快照中,當使用commit()方法提交事務時,同時會清理Session的一級緩存,這時會使用OID判斷一級緩存中的對象和快照中的對象是否一致,如果兩個對象中的屬性發生變化,則執行update語句,將緩存的內容同步到數據庫,並更新快照;如果一致,則不執行update語句。
結論:Hibernate快照的作用就是確保一級緩存中的數據和數據庫中的數據一致。
4.Hibernate的事務控制
爲了實現一個線程中session是同一個,可以使用ThreadLocal將業務層獲取的Session綁定到當前線程中,然後在DAO中獲取Session的時候,都從當前線程中獲取。
在 Hibernate 的配置文件中, hibernate.current_session_context_class 屬性用於指定 Session 管理方式, 可選值包括:
<property name="hibernate.current_session_context_class">thread</property>
獲取當前線程的session
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
5.Session、EntityManager、HibernateTemplate的區別
- Session是Hibernate操作數據庫的對象
- EntityManager是JPA操作數據庫的對象
- HibernateTemplate是Spring整合Hibernate後操作數據庫的對象(底層是session)
6.數據庫中字段類型和映射文件中不匹配時
cst_customer表:表中cust_id並非主鍵,只是不能爲null
Customer.hbm.xml,配置中custId爲主鍵,且生成策略爲native(使用本地數據庫的策略)
<hibernate-mapping package="cn.itcast.domain">
<class name="Customer" table="cst_customer">
<id name="custId" column="cust_id">
<generator class="native"></generator>
</id>
<property name="custName" column="cust_name"></property>
<property name="custSource" column="cust_source"></property>
<property name="custIndustry" column="cust_industry"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
</class>
</hibernate-mapping>
此時當我們在保存對象時:
public static void findOne(Customer c){
Session session = null;//Session對象
Transaction tx = null ;//事務對象
try{
//使用Hibernate工具類獲取Session對象
session = HibernateUtil.openSession();
tx = session.beginTransaction();//開啓事務
//保存客戶
session.save(c);//save方法返回實體保存後的主鍵值
//提交事務
tx.commit();
}catch(Exception e){
tx.rollback();//事務回滾
e.printStackTrace();
}finally{
session.close();//釋放資源
}
}
執行報錯,我們看執行的sql,sql中並沒有cust_id的值,而表中cust_id的值不能爲空,所以報了這個錯
那麼將表中cust_id改爲可以爲空,能成功嗎
還是報錯,
所以我們在開發中,一定要保證表中字段的生成策略和映射中一致