hibernate_get和load方法的區別、持久化類、一級緩存和二級緩存、事務控制

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改爲可以爲空,能成功嗎
還是報錯,
在這裏插入圖片描述
所以我們在開發中,一定要保證表中字段的生成策略和映射中一致

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章