Hibernate(4)——主鍵生成策略、CRUD 基礎API區別的總結 和 註解的使用

俗話說,本身寫的代碼,6個月後也是別人的代碼……複習!複習!複習!涉及的知識點總結以下:java

  • hibernate的主鍵生成策略
  • UUID
  • 配置的補充:hbm2ddl.auto屬性用法
  • 註解仍是配置文件
  • hibernate註解的基本用法
  • 使用Session API CRUD操做對象,以及對象狀態的轉換
  • hibernate緩存的概念
  • get()/load()的區別究竟是什麼,源碼分析
  • 代理模式實現的懶加載
  • saveOrUpdate()/merge()的區別 

  

  Assigned(經常使用,通常狀況使用很方便):mysql

  由程序生成主鍵值,而且在save()以前指定,不然會拋出異常。web

  特色:主鍵的生成值徹底由用戶決定,與底層數據庫無關。用戶須要維護主鍵值,在調用session.save()以前要指定主鍵值。惟一的例外:int auto_increment類型主鍵除外,在程序中不用指定主鍵值,直接看代碼:算法

sid是主鍵,且是int類型,設置爲自動增加。sql

/**
 * StudentDao
 *
 * @author Wang Yishuai.
 * @date 2016/3/11 0011.
 * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
 */
public class StudentDao {
    private static final Logger LOG = LoggerFactory.getLogger(StudentDao.class);

    private Session session;

    @Before
    public void init() {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        this.session = sessionFactory.getCurrentSession();
    }

    @After
    public void clear() {

    }

    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();

        Student student = new Student();

        try {
            //student.setSname("dashuai");
            student.setSname("dadadad");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
}
View Code

配置文件設置:數據庫

<hibernate-mapping>
    <!-- class 裏面的屬性必須和對應的類名,表名保持一致,實現映射-->
    <class name="zhujian.vo.Student" table="students">
        <!-- id表明數據庫表的主鍵, name="userId",對應數據庫表裏的字段column="userId"-->
        <id name="sid" column="sid" type="string">
            <generator class="assigned"/>
        </id>
        <!-- 數據庫裏其餘普通字段和實體屬性的映射,屬性的類型須要小寫-->
        <property name="sname" column="sname" type="string"/>
    </class>
</hibernate-mapping>
View Code

前後保存兩個學生對象,沒有錯誤,以下:設計模式

即便我設置了主鍵生成策略爲assigned,由於學生表的sid是自動增加且爲int類型,因此程序中不用人工指定sid值,這是一個例外狀況。其餘狀況若是設置主鍵爲asigned策略,必須手動設主鍵值,不然會出問題。api

  把sid的自動增加設置取消,再運行以前的代碼,會發生問題:若是不設置sid(主鍵)值,則沒法成功插入數據,且會生成主鍵爲0的數據記錄(if以前沒有),本地測試沒有報錯,可是沒法成功插入。數組

  把主鍵改成String(varchar)類型(對象映射文件和實體類也須要修改),再運行以前的代碼,一樣只能插入sid爲0的記錄(if以前沒有),其餘的沒法繼續插入。若改成手動設置主鍵值,則恢復正常。緩存

 

  Increment(MySQL不適用,集羣不適用,多進程不適用,與底層數據庫有關)

  Increment方式對主鍵值採起自動增加的方式生成新的主鍵值,但要求底層數據庫支持Sequence序列。如Oracle,DB2等。須要在映射文件xxx.hbm.xml中加入Increment標誌符的設置。

  特色:由Hibernate自己維護,適用於全部的數據庫,不適合多進程併發更新數據庫適合單一進程訪問數據庫。不能用於羣集環境

  好比如今有4個數據庫的集羣,共享一張學生表,我從中查找某個編號(主鍵)爲1000的學生,若是使用的hibernate的主鍵生成策略爲increment,則沒法保證編號1000的學生在集羣裏是惟一的。

   Identity (經常使用,與底層數據庫有關,oracle沒法使用)

  Identity根據底層數據庫支持自動增加,無需手動設置主鍵,但不一樣的數據庫用不一樣的主鍵增加方式,移植性很差。

  特色:與底層數據庫有關,適用於MySQL、DB二、MS SQL Server,採用數據庫生成的主鍵,用於爲long、short、int類型生成惟一標識。使用SQL Server 和 MySQL 的自增字段,這個方法不能放到 Oracle 中,Oracle 不支持自增字段,要設定sequence(MySQL 和 SQL Server 中很經常使用),Identity無需Hibernate和用戶的干涉,使用較爲方便,但不便於在不一樣的數據庫之間移植程序。

 

  Sequence (經常使用)

  Sequence須要底層數據庫支持Sequence方式,例如Oracle數據庫等。

  特色:須要底層數據庫的支持序列,支持序列的數據庫有DB二、PostgreSql、Qracle、SAPDb等在不一樣數據庫之間移植程序,特別從支持序列的數據庫移植到不支持序列的數據庫須要修改配置文件。好比:Oracle:create sequence seq_name increment by 1 start with 1; 須要主鍵值時能夠調用seq_name.nextval或者seq_name.curval獲得,數據庫會幫助咱們維護這個sequence序列,保證每次取到的值惟一,如:insert into tbl_name(id, name) values(seq_name.nextval, ‘Jimliu’);

  配置:

<id name="id" column="id" type="long">
<generator class="sequence">
<param name="sequence">seq_name</param>
</generator>
</id> 
View Code

  

  Native(經常使用,推薦使用,移植性好,也適合多數據庫)

  Native主鍵生成方式會根據不一樣的底層數據庫自動選擇Identity、Sequence、Hilo主鍵生成方式。

  特色:根據不一樣的底層數據庫採用不一樣的主鍵生成方式。因爲Hibernate會根據底層數據庫採用不一樣的映射方式,所以便於程序移植,項目中若是用到多個數據庫時,可使用這種方式。

  配置:

<id name="id" column="id">
<generator class="native" /></id>
View Code

  

  UUID(經常使用在網絡環境,可是耗費存儲空間)

  UUID使用128位UUID算法生成主鍵,可以保證網絡環境下的主鍵惟一性,也就可以保證在不一樣數據庫及不一樣服務器下主鍵的惟一性。

  特色;可以保證數據庫中的主鍵惟一性,生成的主鍵佔用比較多的存貯空間。 

  配置:

<id name="id" column="id">
<generator class="uuid.hex" /></id>
View Code

 

  Hilo(不經常使用)

  使用高低位算法生成主鍵,高低位算法使用一個高位值和一個低位值,而後把算法獲得的兩個值拼接起來做爲數據庫中的惟一主鍵。Hilo方式須要額外的數據庫表和字段提供高位值來源。默認請況下使用的表是hibernate_unique_key,默認字段叫做next_hi。next_hi必須有一條記錄不然會出現錯誤。

  特色:和底層數據庫無關,可是須要額外的數據庫表的支持,能保證同一個數據庫中主鍵的惟一性,但不能保證多個數據庫之間主鍵的惟一性。Hilo主鍵生成方式由Hibernate 維護,因此Hilo方式與底層數據庫無關,但不該該手動修改hilo算法使用的表的值,不然會引發主鍵重複的異常。 

 

  配置的補充:hbm2ddl.auto屬性用法

  在項目的配置文件裏設置:<property name="hbm2ddl.auto">update</property>,屬性含義的解釋:

  • create:表示啓動的時候先drop數據庫,再create。
  • create-drop: 也表示建立,只不過再系統關閉前執行一下drop。
  • update: 這個操做啓動的時候會去檢查schema是否一致,若是不一致會作scheme更新。
  • validate: 啓動時驗證現有schema與你配置的hibernate是否一致,若是不一致就拋出異常,並不作更新。

 

  在Hibernate是用映射文件好仍是用註解的方式好?

  兩種方式本質上沒區別。使用元數據能夠在寫實體的同時配好映射,而 XML 則須要來回切換配置和實體,不太方便。從維護上來講,註解更好。由於代碼和「配置」在一塊兒,不容易漏掉信息。從這一點上來講,註解必須是第一選擇。可是也有人說到這樣一個問題,若是使用註解,有很大可能會違反開閉原則,使用配置文件則不會。閒言少敘,看代碼:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory name="MySQL">
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/bbs</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123</property>
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="current_session_context_class">thread</property>

        <mapping class="net.nw.vo.Students"/>
    </session-factory>
</hibernate-configuration>
View Code

寫好配置文件,代碼以下:

package net.nw.vo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

//學生實體類
@Entity
public class Students {
    private int sid;

    private String sname;
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }
}
View Code

測試:

import net.nw.vo.Students;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;


public class HibernateTest {

    public static void main(String[] args) {
        AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration().configure();
        SessionFactory sessionFactory = annotationConfiguration.buildSessionFactory();
        Session session = sessionFactory.getCurrentSession();
        Transaction transaction = session.beginTransaction();

        try {
            Students s = new Students();
            //s.setSid(1);
            s.setSname("xxxx");
            session.save(s);
            transaction.commit();
        } catch (Exception ex) {
            transaction.rollback();
            ex.printStackTrace();
        }
    }
}
View Code

執行成功!本地測試成功插入xxxxx(接以前的bbs數據庫表students):

  小結:

  • 使用AnnotationConfiguration建立config對象代替以前的Configuration方式(hibernate3的方式)。
  • hibernate4中新增了ServiceRegistry接口。4.0之後改成使用ServiceRegistry 註冊:
Configuration cfg = new Configuration().configure(); 
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry(); 
SessionFactory factory = cfg.buildSessionFactory(serviceRegistry); 
Session s = factory.openSession();
View Code
  • @Entity——必選的註解,在class類體上面,註解將一個類聲明爲一個實體bean。屬性 name 可選,對應數據庫中的一個表。若表名與實體類名相同,則能夠省略。
  • @Table(name="",catalog="",schema="") ——該註解可選,一般和@Entity 配合使用,只能標註在實體的 class 定義處,表示實體對應的數據庫表的信息。屬性:

    • name - 可選,表示表的名稱,默認地,表名和實體名稱一致,只有在不一致的狀況下才需要指定表名

    • catalog - 可選,表示Catalog名稱,默認爲 Catalog("").

    • schema - 可選 , 表示 Schema 名稱 , 默認爲Schema("").

  • @Id——必選,通常加在getter方法上面, 定義了映射到數據庫表的主鍵的屬性,一個實體只能有一個屬性被映射爲主鍵,置於get方法前。
  • @GeneratedValue(strategy=GenerationType,generator="") ——可選,用於定義主鍵生成策略。屬性:
    • Strategy - 表示主鍵生成策略,取值有:
      • GenerationType.AUTO - 根據底層數據庫自動選擇(默認),若數據庫支持自動長類型,則爲自動增加。
      • GenerationType.INDENTITY - 根據數據庫的Identity字段生成,支持DB二、MySQL、MS、SQL Server、SyBase與HyperanoicSQL數據庫的Identity類型主鍵。

      • GenerationType.SEQUENCE - 使用Sequence來決定主鍵的取值,適合Oracle、DB2等 ,支持Sequence的數據庫,通常結合@SequenceGenerator使用。(Oracle沒有自動增加類型,只能用Sequence)

      • GenerationType.TABLE - 使用指定表來決定主鍵取值,結合@TableGenerator使用。如:

        @Id
        
        @TableGenerator(name="tab_cat_gen",allocationSize=1)
        
        @GeneratedValue(Strategy=GenerationType.Table)
        View Code
    • Generator - 表示主鍵生成器的名稱,這個屬性一般和ORM框架相關 , 例如:Hibernate 能夠指定 uuid 等主鍵生成方式

  • @Version——能夠在實體bean中使用@Version註解,經過這種方式可添加對樂觀鎖定的支持。

  • @Basic ——用於聲明屬性的存取策略:

    • @Basic(fetch=FetchType.EAGER)   即時獲取(默認的存取策略)

    • @Basic(fetch=FetchType.LAZY)    延遲獲取

  • @Temporal ——用於定義映射到數據庫的時間精度:@Temporal(TemporalType=TIME) 時間、@Temporal(TemporalType=DATE) 日期、@Temporal(TemporalType=TIMESTAMP) 二者兼具

 

  註解太多了,之後一一說明。

  • 使用註解,在Hibernate.cfg.xml中一樣也須要註冊實體類。固然配置文件不用配,只是仍是須要引入實體類的關係,<mapping class="net.nw.vo.Students"/>,配置文件方式的resource=改成了class=。

 

  使用session api實現crud

  結合以前的總結,看看crud時對象狀態的轉換關係。直接看代碼:

  • create,session.save(obj);,注意這裏面的玄機:
    • 若是當前對象是持久態,則執行save方法,會自動觸發Update語句的執行,而不是insert語句。
    • 若是當前對象爲瞬時態,則執行save方法,會馬上執行insert語句,使得對象從瞬時態轉換爲持久態。
    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();
        Student student = new Student();

        try {
            student.setSname("xiaoli");
            // 以前的student是瞬時態的
            session.save(student);
            // 以後變爲持久態
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

debug一下,驗證以前的理論:在sava處斷點,step over以後,當即控制檯打印了insert into students(sname) values(?)。後臺數據庫也插入了xiaoli。

  下面看若是當前對象是持久態的狀況:

    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();
//        Student student = new Student();

        try {
//            student.setSname("xiaoli");

            // 從數據庫讀取xiaoli,該對象是持久態的
            Student student = (Student) session.get(Student.class, 7);
            // 從新賦值,再保存
            student.setSname("小李");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

仍是在save處debug,發現首先發送SQL語句:

    select
        student0_.sid as sid2_0_,
        student0_.sname as sname2_0_ 
    from
        students student0_ 
    where
        student0_.sid=?
View Code

執行save以後,發送的SQL是:

    update
        students 
    set
        sname=? 
    where
        sid=?
View Code

道理很明顯,若是仍是插入語句,則會插入新的記錄,顯然不對了。

  • retrive,hibernate基本的查詢api是get和load(只根據主鍵查詢,不能根據其它字段查詢,若是想根據非主鍵查詢,可使用HQL),以前也看了get的用法了,使用get查詢對象,執行以後,對象變爲持久態的,馬上發送SQL語句select……並且也能夠直接填寫實體類名,get查找返回的是真正的實體對象,若是找不到則返回null,代碼以下:
Student student = (Student) session.get("zhujian.vo.Student", 1);
            LOG.info("sid = {}, sname = {}", student.getSid(), student.getSname());
View Code

debug一下:

執行48句以後:

再看load方法,參數寫法和get同樣的,可是load是一種懶加載的方式,只有使用的時候才生成sql語句:

    @Test
    public void retrive() {
        Transaction transaction = session.beginTransaction();

        try {
            Student student = (Student) session.load(Student.class, 2);
            student.setSname("小李");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

load方法查詢的對象是代理對象,且執行此方法時不會當即發出查詢語句。由於使用load加載對象去查詢某一條數據的時候並不會直接將這條數據以指定對象的形式來返回,而是在真正須要使用該對象裏面的一些屬性的時候纔會去查找對象。他的好處就是能夠減小程序自己由於與數據庫頻繁的交互形成的處理速度緩慢的問題。

  而hibernate的load方法延遲加載實現原理是使用代理(代理設計模式在hibernate的的應用),Hibernate的懶加載,是經過在內存中對類、集合等的加強(即在內存中擴展類的特性[繼承])來實現的,這些類一般稱爲代理類。好比咱們經過session.load(class, id)操做,加載一個對象的時候,hibernate返回的其實是實體類的代理類實例。如圖:

執行65句以後:

  但如經過session.get操做,則返回實際的對象實例(不是代理類實例),對上例而言,get操做返回實體類的實例。採用load()方法加載數據,若是數據庫中沒有相應的記錄,則會拋出異常:org.hibernate.ObjectNotFoundException: No row with the given identifier exists:……因此若是我知道該id在數據庫中必定有對應記錄存在,那麼我就可使用load方法來實現延遲加載。

  

  session緩存(hibernate一級緩存)概念

  在繼續探討這個問題以前,必須先提早總結下緩存的一些東西。

  Hibernate緩存包括兩大類:Hibernate一級緩存和Hibernate二級緩存。

  一級緩存就是Session級別的緩存,一個Session作了一個查詢操做,它會把這個操做的結果放在一級緩存中,若是短期內這個session(必定要同一個session)又作了同一個操做,那麼hibernate直接從一級緩存中拿,而不會再去連數據庫取數據。它是屬於事務範圍的緩存,Session對象的生命週期一般對應一個數據庫事務或者一個應用事務,一級緩存中,持久化類的每一個實例都具備惟一的OID。這一級別的緩存由hibernate管理,無需主動干預便可工做。

  二級緩存就是 SessionFactory 級別的緩存,查詢的時候會把查詢結果緩存到二級緩存中若是同一個sessionFactory建立的某個session執行了相同的操做,hibernate就會從二級緩存中拿結果,而不會再去鏈接數據庫,由於SessionFactory對象的生命週期和應用程序的整個過程對應,所以Hibernate二級緩存是進程範圍或者集羣範圍的緩存,有可能出現併發問題,所以須要採用適當的併發訪問策略,該策略爲被緩存的數據提供了事務隔離級別,須要人工配置和更改,能夠動態加載,卸載(二級緩存是一個可配置的插件,默認下SessionFactory不會啓用這個插件。)。而一級緩存不能夠卸載。

  當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查,查不到,若是配置了二級緩存,那麼從二級緩存中查,若是都查不到,再查詢數據庫,把結果按照ID放入到緩存刪除、更新、增長數據的時候,同時更新緩存。

            Student student = (Student) session.get(Student.class, 6);
            LOG.info("student sid = {}, student sname = {}", student.getSid(), student.getSname());

            Student student1 = (Student) session.get(Student.class, 6);
            LOG.info("student1 sid = {}, student1 sname = {}", student1.getSid(), student1.getSname());
View Code

debug查看效果:執行完第一個get語句,當即發送了SQL語句,執行第二個同樣的get語句時,我發現並沒打印SQL語句,而是直接打印了log,只能說明,它第二次同樣的對象查詢是在緩存中查找到的。

  網上有人說get不從緩存中查找,這是不對的,還有人說get不會去二級緩存中查找,其實也不對。或者嚴格的說必須指定哪一個版本的hibernate再來討論這個問題,下面還會分析源碼來再次佐證(hibernate 3)

 

  再看load的方式:

            Student student = (Student) session.load(Student.class, 6);
            LOG.info("sid = {}, sname = {}", student.getSid(), student.getSname());

            Student student1 = (Student) session.load(Student.class, 6);
            LOG.info("student1 sid = {}, student1 sname = {}", student.getSid(), student.getSname());
View Code

也是第二次查找同一個對象的時候,沒有再生成SQL語句,直接打印的兩個log,只能說明它是在緩存中查詢了。load方法也是走緩存的。那麼具體怎麼走,下面看源碼級別的分析。

 

  load和get的源碼分析:

  先看get和load的api源碼,位於SessionImpl類,它是Session接口的一個實現類:

上面是load方法的,下面是get方法:

兩種方式相似,那麼繼續看重載的load和get方法源碼:

public Object get(String entityName, Serializable id) throws HibernateException {
        LoadEvent event = new LoadEvent(id, entityName, false, this);
        boolean success = false;

        Object var5;
        try {
            this.fireLoad(event, LoadEventListener.GET);
            success = true;
            var5 = event.getResult();
        } finally {
            this.afterOperation(success);
        }

        return var5;
    }


    public Object load(String entityName, Serializable id) throws HibernateException {
        LoadEvent event = new LoadEvent(id, entityName, false, this);
        boolean success = false;

        Object var5;
        try {
            this.fireLoad(event, LoadEventListener.LOAD);
            if(event.getResult() == null) {
                this.getFactory().getEntityNotFoundDelegate().handleEntityNotFound(entityName, id);
            }

            success = true;
            var5 = event.getResult();
        } finally {
            this.afterOperation(success);
        }

        return var5;
    }
View Code

關鍵區別是this.fireLoad(event, LoadEventListener.LOAD);和this.fireLoad(event, LoadEventListener.GET);這兩句代碼,官方文檔說,使用fireLoad方法把加載事件event和事件加載的監聽器(一個接口)的方式組合,也就是這兩個方法觸發事件的方式不同。下面先看看這兩個表明不一樣觸發方式的常量:

    LoadEventListener.LoadType GET = (new LoadEventListener.LoadType("GET")).setAllowNulls(true).setAllowProxyCreation(false).setCheckDeleted(true).setNakedEntityReturned(false);
    LoadEventListener.LoadType LOAD = (new LoadEventListener.LoadType("LOAD")).setAllowNulls(false).setAllowProxyCreation(true).setCheckDeleted(true).setNakedEntityReturned(false);
View Code

區別就是:在allowNulls上get容許空,也就是get找不到就返回null,而load不容許空,因此拋出異常!而在allowProxyCreation上get不容許代理建立,而load容許代理的建立,記住這點。繼續看fireload方法源碼:

    private void fireLoad(LoadEvent event, LoadType loadType) {
        this.errorIfClosed();
        this.checkTransactionSynchStatus();
        LoadEventListener[] loadEventListener = this.listeners.getLoadEventListeners();

        for(int i = 0; i < loadEventListener.length; ++i) {
            loadEventListener[i].onLoad(event, loadType);
        }

    }
View Code

使用的內部的LoadEventListener[] loadEventListener數組去調用loadEventListener[i].onLoad(event, loadType);方法,加載方式和事件做爲參數傳入,下面看onload方法:

    public void onLoad(LoadEvent event, LoadType loadType) throws HibernateException {
        EventSource source = event.getSession();
        EntityPersister persister;
        if(event.getInstanceToLoad() != null) {
            persister = source.getEntityPersister((String)null, event.getInstanceToLoad());
            event.setEntityClassName(event.getInstanceToLoad().getClass().getName());
        } else {
            persister = source.getFactory().getEntityPersister(event.getEntityClassName());
        }

        if(persister == null) {
            throw new HibernateException("Unable to locate persister: " + event.getEntityClassName());
        } else {
            if(!persister.getIdentifierType().isComponentType() || EntityMode.DOM4J != event.getSession().getEntityMode()) {
                Class keyToLoad = persister.getIdentifierType().getReturnedClass();
                if(keyToLoad != null && !keyToLoad.isInstance(event.getEntityId())) {
                    throw new TypeMismatchException("Provided id of the wrong type for class " + persister.getEntityName() + ". Expected: " + keyToLoad + ", got " + event.getEntityId().getClass());
                }
            }

            EntityKey keyToLoad1 = new EntityKey(event.getEntityId(), persister, source.getEntityMode());

            try {
                if(loadType.isNakedEntityReturned()) {
                    event.setResult(this.load(event, persister, keyToLoad1, loadType));
                } else if(event.getLockMode() == LockMode.NONE) {
                    event.setResult(this.proxyOrLoad(event, persister, keyToLoad1, loadType));
                } else {
                    event.setResult(this.lockAndLoad(event, persister, keyToLoad1, loadType, source));
                }

            } catch (HibernateException var7) {
                log.info("Error performing load command", var7);
                throw var7;
            }
        }
    }
View Code

找到關鍵的代碼,由於個人測試例子裏,兩個方法都沒有調用帶 lockMode 的 api,它們默認 null,執行這一句:

get和load查詢方法都走該句,那麼繼續看proxyOrLoad方法:

    protected Object proxyOrLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
        if(log.isTraceEnabled()) {
            log.trace("loading entity: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
        }

        if(!persister.hasProxy()) {
            return this.load(event, persister, keyToLoad, options);
        } else {
            PersistenceContext persistenceContext = event.getSession().getPersistenceContext();
            Object proxy = persistenceContext.getProxy(keyToLoad);
            return proxy != null?this.returnNarrowedProxy(event, persister, keyToLoad, options, persistenceContext, proxy):(options.isAllowProxyCreation()?this.createProxyIfNecessary(event, persister, keyToLoad, options, persistenceContext):this.load(event, persister, keyToLoad, options));
        }
    }
View Code

關鍵語句 if(!persister.hasProxy()),大概機制就是當該方式沒有發現代理存在,也就是get的加載方式,就return this.load(event, persister, keyToLoad, options) 方法的值,那麼看load方法的源碼:

    protected Object load(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
        if(event.getInstanceToLoad() != null) {
            if(event.getSession().getPersistenceContext().getEntry(event.getInstanceToLoad()) != null) {
                throw new PersistentObjectException("attempted to load into an instance that was already associated with the session: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
            }

            persister.setIdentifier(event.getInstanceToLoad(), event.getEntityId(), event.getSession().getEntityMode());
        }

        Object entity = this.doLoad(event, persister, keyToLoad, options);
        boolean isOptionalInstance = event.getInstanceToLoad() != null;
        if((!options.isAllowNulls() || isOptionalInstance) && entity == null) {
            event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(event.getEntityClassName(), event.getEntityId());
        }

        if(isOptionalInstance && entity != event.getInstanceToLoad()) {
            throw new NonUniqueObjectException(event.getEntityId(), event.getEntityClassName());
        } else {
            return entity;
        }
    }
View Code

關鍵一句:Object entity = this.doLoad(event, persister, keyToLoad, options);,到這裏就一目瞭然了:

 1 protected Object doLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
 2         if(log.isTraceEnabled()) {
 3             log.trace("attempting to resolve: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
 4         }
 5 
 6         Object entity = this.loadFromSessionCache(event, keyToLoad, options);// 先從session緩存(一級緩存)中加載對象
 7 
 8         if(entity == REMOVED_ENTITY_MARKER) {
 9             log.debug("load request found matching entity in context, but it is scheduled for removal; returning null");
10             return null;
11         } else if(entity == INCONSISTENT_RTN_CLASS_MARKER) {
12             log.debug("load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null");
13             return null;
14         } else if(entity != null) {// 在一級緩存裏查找到對象就返回該對象
15             if(log.isTraceEnabled()) {
16                 log.trace("resolved object in session cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
17             }
18 
19             return entity;
20         } else { // 一級緩存找不到,get方法去二級緩存找
21             entity = this.loadFromSecondLevelCache(event, persister, options);
22             if(entity != null) {// 若是存在且找到就返回該對象
23                 if(log.isTraceEnabled()) {
24                     log.trace("resolved object in second-level cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
25                 }
26 
27                 return entity;
28             } else { // 最後緩存找不到,纔去數據庫查詢
29                 if(log.isTraceEnabled()) {
30                     log.trace("object not resolved in any cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
31                 }
32 
33                 return this.loadFromDatasource(event, persister, keyToLoad, options);
34             }
35         }
36     }
View Code

  顯然,get的查詢方式,首先查詢session緩存,也就是一級緩存,沒有就去查詢二級緩存,若是沒有二級緩存或者沒找到,最後纔去查數據庫

  下面看看load方法,其實大同小異,和get的源碼差很少,只不過走了其餘一些分支,仍是看方法proxyOrLoad裏,若是找到了代理(也就是使用的load查找方式),進入以下分支:

PersistenceContext persistenceContext = event.getSession().getPersistenceContext();// load方式先從事件的上下文環境中查找(一級緩存session)
// 返回的對象賦值給proxy,名字都那麼明顯了,代理對象
Object proxy = persistenceContext.getProxy(keyToLoad);
// 若是是空的,就建立代理對象,不是空的,先判斷是否容許建立代理(還記得以前的LOAD和GET常量麼,就在這裏起做用),load確定容許,那麼開始建立代理
return proxy != null?this.returnNarrowedProxy(event, persister, keyToLoad, options, persistenceContext, proxy):(options.isAllowProxyCreation()?this.createProxyIfNecessary(event, persister, keyToLoad, options, persistenceContext):this.load(event, persister, keyToLoad, options));
View Code

  到這裏確定明確一點:load查找方式首先會去緩存裏查找,那麼load的代理對象在獲取對象屬性時究竟如何加載數據?

  若是緩存中找不到對象,則進入該方法:

// 該內部的私有方法中第一個log就說了:實體代理類先從session中查找
private Object returnNarrowedProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options, PersistenceContext persistenceContext, Object proxy) {
        log.trace("entity proxy found in session cache");
        // 執行的是懶加載
        LazyInitializer li = ((HibernateProxy)proxy).getHibernateLazyInitializer();
        if(li.isUnwrap()) {// 沒有解包,就直接返回該代理對象
            return li.getImplementation();
        } else {
            Object impl = null;
            // load查找容許建立代理,那麼這個if確定不進入執行
            if(!options.isAllowProxyCreation()) {
                impl = this.load(event, persister, keyToLoad, options);
                // 沒有找到就返回異常信息
                if(impl == null) {
                    event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(persister.getEntityName(), keyToLoad.getIdentifier());
                }
            }
   
            // 重寫使用代理包裝一下再返回
            return persistenceContext.narrowProxy(proxy, persister, keyToLoad, impl);
        }
    }
View Code

也就是說,此時的load方式直接返回相應的對象,不會發送SQL語句查詢數據庫,也無論給定的id是否在數據庫中存在數據對象。也就是此時此刻load不會直接訪問數據庫,只是簡單地返回一個由底層封裝的一個代理對象,永遠不爲null,null會拋出異常。固然了,固然了最後load也會查數據,只不過是在實際使用數據時纔去查詢二級緩存,二級緩存找不到,最後去數據庫查找,不然拋出異常。

  以上源碼,來自hibernate 3。若是其餘版本狀況大同小異,大不了變化了就分析源碼,看官方文檔……不再隨便相信網上的一些不負責任轉載的文章了。

 

  Get()和Load()的區別小結

  1. get()方法默認不支持lazy(延遲加載)功能,而load支持延遲加載get方法,get方法首先查詢session緩存,也就是一級緩存,沒有找到查二級緩存,若是沒二級緩存或者找不到,則查詢數據庫,而load方法建立時首先查詢session緩存,沒有就建立代理對象並返回,實際使用數據時才查詢二級緩存,沒有就查詢數據庫。
  2. 若是找不到符合條件的記錄,get方法返回null,而load方法拋出異常(ObjectNotFoundException),load擁有不爲null。
  3. 使用load方法,通常都假定你要取得對象確定是存在的,才能使用,不然報錯,而get方法則嘗試查找,若是不存在,就返回null。
  4. get()和load()只根據主鍵查詢,不能根據其它字段查詢,若是想根據非主鍵查詢,可使用HQL或者原生SQL。
  5. 使用load()時若是在session關閉以後再查詢此對象,會報異常:could not initialize proxy - no Session。處理辦法:在session關閉以前初始化一下查詢出來的對象:Hibernate.initialize(對象);
  6. 雖然好多書中都這麼說:「get()永遠只返回實體類」,但實際上這是不正確的,get方法若是在session緩存中找到了該id對應的對象,若是恰好該對象前面是被代理過的,如被load方法使用過,或者被其餘關聯對象延遲加載過,那麼返回的仍是原先的代理對象,而不是實體類對象,若是該代理對象尚未加載實體數據(就是id之外的其餘屬性數據),那麼它會查詢二級緩存或者數據庫來加載數據,可是返回的仍是代理對象,只不過已經加載了實體數據。
  7. 前面已經從源碼角度講了,get方法首先查詢session緩存,沒有的話查詢二級緩存,最後查詢數據庫;load方法建立時首先查詢session緩存,沒有就建立代理,實際使用數據時才查詢二級緩存和數據庫。

 

這裏比較細緻了,繼續看刪除和修改:

  • update,總結兩點:
    • 瞬時態不能執行update()。
    • 持久態和遊離態能夠執行update()。
    • 注意:代理對象在session關閉以後,不能執行update()。

第一個瞬時態對象,也就是數據庫沒有該對象保存,那麼確定更新不了啦!很好理解。看代碼:

        try {
            Student student = new Student();
            // 建立瞬時態對象student
            student.setSid(9);
            student.setSname("隔壁老王");
            // 更新瞬時態對象
            session.update(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
View Code

報錯:org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]

第二個持久態確定能執行更新,毋庸置疑,先從數據庫中把對象讀取,變爲持久態,在更新就ok。而遊離態是存在於數據庫,可是脫離session管理的對象,也是能夠更新的。道理都同樣。好比:

    @Test
    public void updateObj() {
        Transaction transaction = session.beginTransaction();

        try {
            Student student = (Student) session.get(Student.class, 1);
            transaction.commit(); // 讀取一個持久對象,什麼都不作,提交事務,session自動關閉(使用的線程封閉的session)
            // 此時student變爲遊離態
            // 從新開啓session和事務
            session = sessionFactory.getCurrentSession();
            Transaction transaction1 = session.beginTransaction();
            session.update(student); // ok
            transaction1.commit();// ok
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

發送了SQL:

update
students
set
sname=?
where
sid=?

Process finished with exit code 0

  • SaveOrUpdate():當對象從瞬時態轉換爲持久態時,若是不肯定主鍵是否衝突,推薦使用SaveOrUpdate()。很簡單,顧名思義,設置的主鍵在數據庫存在,自動執行的是更新方法update,若是主鍵不存在,那麼執行的是save方法保存這個對象,把對象從瞬時態變爲持久態。站在用戶角度,用戶事先不知道該主鍵在數據庫存在否,使用這個保險。固然了,主鍵生成策略不能是native,由於它會自增,根本不會衝突。使用assigned就能夠。
  • delete:持久態或者遊離態對象均可以執行delete()方法。不過瞬時態對象也能夠執行delete(),但Hibernate並不推薦使用delete()刪除瞬時狀態對象,由於這樣作沒有任何意義。 
  • merge方法:若是數據庫中有該記錄,則更新該記錄,若是不存在該記錄,則進行insert。乍一看很像saveOrUpdate方法,可是有區別,由於執行完merge(obj)後,它返回一個持久化對象的引用,而實參obj自己仍是遊離的。

  merge和saveOrUpdate方法區別

  使用saveOrUpdate,若是數據庫中有記錄,會無條件執行update,若是數據庫中無記錄,則執行insert操做。merge方法是把咱們提供的對象變爲遊離狀態的對象,只不過merger從新返回一個新的持久化對象的引用。而saveOrUpdate則把咱們提供的對象轉變爲一個持久化對象。

  也就是說saveOrUpdate後的對象會歸入session的管理,對象的狀態會跟數據庫同步,再次查詢該對象會直接從session緩存中取,merge後的對象不會歸入session的管理,再次查詢該對象仍是會從數據庫中取。

Students s1 = new Students();

s1.setSid(4);// 假設id=4的對象存在數據庫

s1.setSname(「masi」);//s1是託管態的

Students s2 =(Students)session.merge(s1);
View Code

可是merge(s1)以後,返回的s2(引用)這個對象歸入了session管理,和數據庫同步,變爲持久化態,而s1仍是託管態的,沒有session管理它。

相關文章
相關標籤/搜索