JPA快速入門(一)

作者:鐘昕靈,叩丁狼教育高級(jí)講師。原創(chuàng)文章茂卦,轉(zhuǎn)載請(qǐng)注明出處。

JPA簡(jiǎn)介

JPA是Java Persistence API的簡(jiǎn)稱蹄梢,中文名Java持久層API疙筹,是JDK 5.0注解或XML描述對(duì)象-關(guān)系表的映射關(guān)系,并將運(yùn)行期的實(shí)體對(duì)象持久化到數(shù)據(jù)庫(kù)中禁炒。

Sun引入新的JPA ORM規(guī)范出于兩個(gè)原因:
其一,簡(jiǎn)化現(xiàn)有Java EE和Java SE應(yīng)用開發(fā)工作霍比;
其二幕袱,Sun希望整合ORM技術(shù),實(shí)現(xiàn)天下歸一悠瞬。

JPA的宗旨是為POJO提供持久化標(biāo)準(zhǔn)規(guī)范们豌,由此可見,經(jīng)過這幾年的實(shí)踐探索浅妆,能夠脫離容器獨(dú)立運(yùn)行望迎,方便開發(fā)和測(cè)試的理念已經(jīng)深入人心了。Hibernate3.2+凌外、TopLink 10.1.3以及OpenJPA都提供了JPA的實(shí)現(xiàn)辩尊。

JPA的總體思想和現(xiàn)有Hibernate、TopLink康辑、JDO等ORM框架大體一致摄欲〗瘟粒總的來說,JPA包括以下3方面的技術(shù):

ORM映射元數(shù)據(jù)

JPA支持XML和JDK5.0注解兩種元數(shù)據(jù)的形式胸墙,元數(shù)據(jù)描述對(duì)象和表之間的映射關(guān)系我注,框架據(jù)此將實(shí)體對(duì)象持久化到數(shù)據(jù)庫(kù)表中迟隅;

API

用來操作實(shí)體對(duì)象但骨,執(zhí)行CRUD操作智袭,框架在后臺(tái)替代我們完成所有的事情,開發(fā)者從繁瑣的JDBC和SQL代碼中解脫出來。

查詢語言

這是持久化操作中很重要的一個(gè)方面谚攒,通過面向?qū)ο蠖敲嫦驍?shù)據(jù)庫(kù)的查詢語言查詢數(shù)據(jù)括儒,避免程序的SQL語句緊密耦合帮寻。


image.png
JPA開發(fā)環(huán)境搭建
  • jar包的依賴
    如果是maven項(xiàng)目,將下面的配置添加到pom.xml文件中
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
                <target>1.8</target>
                <source>1.8</source>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.5.Final</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.21</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.6</version>
    </dependency>
</dependencies>

如果是普通的java項(xiàng)目,將下面的jar包添加到項(xiàng)目的lib目錄中


image.png
  • persistence.xml文件
    如果是maven項(xiàng)目,在src/main/resources下創(chuàng)建META-INF文件夾,將persistence.xml文件放在該目錄下
    如果是普通的java項(xiàng)目,在src下創(chuàng)建META-INF文件夾,將persistence.xml文件夾放在該目錄下

在persistence.xml文件中做如下配置

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <!--
    JPA根據(jù)下面的配置信息創(chuàng)建EntityManagerFactory,一個(gè)項(xiàng)目中可以配置多個(gè)持久單元
    name:為當(dāng)前持久單元命名,可以通過該名稱指定加載對(duì)應(yīng)的配置信息
-->
    <persistence-unit name="myPersistence">
        <!--指定掃描貼Entity實(shí)體類所在的jar包-->
        <properties>
    <!--數(shù)據(jù)庫(kù)的方言,告訴JPA當(dāng)前應(yīng)用使用的數(shù)據(jù)庫(kù)-->
            <property name="hibernate.dialect"  value="org.hibernate.dialect.MySQL5Dialect"/>
            <!--jpa的相關(guān)的配置信息-->
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
            <property name="javax.persistence.jdbc.driver"  value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="admin"/>
    <!--是否在控制臺(tái)打印執(zhí)行的sql語句-->
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

到此,開發(fā)JPA應(yīng)用的環(huán)境就搭建完成,接下來,在此基礎(chǔ)上來完成基本的CRUD操作吧

基于JPA的CRUD
  • 實(shí)體類及映射配置
//getter/setter和toString方法
@Getter@Setter@ToString

//JPA會(huì)掃描到貼了Entity注解的類,將其作為需要持久化的類
@Entity
//根據(jù)需求,對(duì)類和表做相關(guān)映射(如:表名)
@Table(name="user")
public class User {
    //標(biāo)識(shí)該字段為主鍵列對(duì)應(yīng)的字段
    @Id
    //指定主鍵的生成策略
    @GeneratedValue(strategy = GenerationType.AUTO)
    //為當(dāng)前字段和對(duì)應(yīng)的列做映射(如:列名,列的長(zhǎng)度等)
    @Column(name = "id")
    private Long id;
    @Column(name = "name",length = 20)
    private String name;
    @Column(name = "sn",nullable = false)
    private String sn;
    //對(duì)日期類型做映射
    @Temporal(TemporalType.DATE)
    private Date hiredate;
}
  • EntityManagerFactory和EntityManager對(duì)象的創(chuàng)建
  1. EntityManagerFactory:JPA通過加載META-INF/persistence.xml文件中配置的persistence-unit創(chuàng)建EntityManagerFactory對(duì)象,該對(duì)象相當(dāng)于一個(gè)連接池對(duì)象,用來創(chuàng)建EntityManager,是線程安全的,多線程可以共用同一個(gè)EntityManagerFactory,創(chuàng)建該對(duì)象需要消耗較多的資源,所以通常一個(gè)項(xiàng)目只需要?jiǎng)?chuàng)建一個(gè)EntityManagerFactory對(duì)象
  2. EntityManager:相當(dāng)于一個(gè)連接對(duì)象,該對(duì)象線程不安全,所以,每次對(duì)象數(shù)據(jù)庫(kù)的訪問應(yīng)該創(chuàng)建一個(gè)新的EntityManager對(duì)象
public class JPAUtil {

    private static EntityManagerFactory emf;

    private JPAUtil() {}

    static {
        //加載persistence.xml文件中的persistence-util中的配置信息創(chuàng)建EntityManagerFactory對(duì)象
        emf = Persistence.createEntityManagerFactory("myPersistence");
    }

    //使用EntityManager創(chuàng)建EntityManager對(duì)象
    public static EntityManager getEntityManager() {
        return emf.createEntityManager();
    }
}
  • 保存操作
@Test
public void testSave() throws Exception {
    //封裝需要持久化的數(shù)據(jù)
    User u = new User();
    u.setName("Neld");
    u.setSn("sn");
    u.setHiredate(new Date());

    EntityManager em = JPAUtil.getEntityManager();
    //開啟事務(wù)
    em.getTransaction().begin();
    //執(zhí)行保存
    em.persist(u);
    //提交事務(wù)
    em.getTransaction().commit();
    //釋放資源
    em.close();
}
  • 刪除操作
@Test
public void testDelete() throws Exception {
    EntityManager em = JPAUtil.getEntityManager();
    em.getTransaction().begin();
    User u = em.getReference(User.class, 1L);
//執(zhí)行刪除,將持久化狀態(tài)的對(duì)象從數(shù)據(jù)庫(kù)中刪除
    em.remove(u);
    em.getTransaction().commit();
    em.close();
}
  • 修改操作
@Test
public void testUpdate() throws Exception {
    EntityManager em = JPAUtil.getEntityManager();
    em.getTransaction().begin();
    User u = em.find(User.class, 1L);
    u.setName("xxxx");
    em.merge(u);
    em.getTransaction().commit();
    em.close();
}
  • 查詢操作
@Test
public void testGet() throws Exception {
    EntityManager em = JPAUtil.getEntityManager();
    //查詢指定類型和OID的用戶信息
    User u = em.find(User.class, 1L);
    em.close();
    System.out.println(u);
}
  • CRUD小結(jié)
  1. persistence.xml文件的配置
    配置連接數(shù)據(jù)庫(kù)的基本信息
    JPA的基本行為配置

  2. 實(shí)體類的基本映射
    @Entity:標(biāo)注該類為持久化類
    JPA掃描到類上的注解,會(huì)將當(dāng)前類作為持久化類
    @Table:配置當(dāng)前類和表的相關(guān)映射
    下面的注解可以貼在字段或者是get方法上,
    如果選定了一個(gè)位置,那么所有的屬性相關(guān)的注解都應(yīng)該貼在這個(gè)位置,意思是說,不能一部分在字段上,一部分在get方法上
    @Id:主鍵屬性的映射---和表中的主鍵映射
    @GeneratedValue:主鍵生成策略(指定生成主鍵的方式:自增長(zhǎng)/手動(dòng)設(shè)置)
    @Column:配置當(dāng)前屬性和列的映射
    @Temporal:對(duì)日期類型的屬性映射(Date/DateTime/TimeStemp)

  3. 完成CRUD的步驟
    加載persistence.xml文件,使用指定的<persistence-unit>配置創(chuàng)建EntityManagerFactory對(duì)象,相當(dāng)于根據(jù)配置信息創(chuàng)建一個(gè)連接池對(duì)象
    創(chuàng)建EntityManager對(duì)象,相當(dāng)于獲取到一個(gè)連接對(duì)象
    開啟事務(wù)
    執(zhí)行crud相關(guān)的方法(persist/merge/remove/find),查詢所有調(diào)用Query中的getResultList方法
    Persist:保存數(shù)據(jù)
    Merge:保存或者更新,當(dāng)對(duì)象有OID的時(shí)候,更新,反之,保存
    Remove:刪除數(shù)據(jù)
    Find:根據(jù)主鍵查詢數(shù)據(jù)
    Query:其他的查詢需要使用該對(duì)象,傳入對(duì)應(yīng)的JPQL(相當(dāng)于SQL),調(diào)用getResultList方法執(zhí)行查詢,返回對(duì)應(yīng)的List集合
    提交事務(wù)
    釋放資源

hbm2ddl工具的使用

在持久層應(yīng)用的開發(fā)過程中,我們發(fā)現(xiàn),實(shí)體類和表結(jié)構(gòu)是一一對(duì)應(yīng)的,所以,我們會(huì)想,是否可以讓JPA根據(jù)實(shí)體類和對(duì)應(yīng)的映射信息的配置,為我們自動(dòng)的生成對(duì)應(yīng)的表結(jié)構(gòu)呢?

答案是肯定的,又因?yàn)槲覀儸F(xiàn)在講的是hibernate對(duì)JPA的實(shí)現(xiàn),所以我們應(yīng)用hibernate中提供的hbm2ddl工具來實(shí)現(xiàn),配置很簡(jiǎn)單,在persistence.xml文件中作如下配置即可

<property name="hibernate.hbm2ddl.auto" value="create"/>

接下來,我們來解釋一下每種策略的含義及使用場(chǎng)景

  • hibernate.hbm2ddl.auto=create
    在啟動(dòng)的時(shí)候先刪除被管理的實(shí)體對(duì)應(yīng)的表,然后再創(chuàng)建jpa管理的實(shí)體類對(duì)應(yīng)的表
  • hibernate.hbm2ddl.auto=create-drop
    和create一致,只是在關(guān)閉系統(tǒng)之前會(huì)刪除jpa管理的所有的表
  • hibernate.hbm2ddl.auto=update
    在啟動(dòng)的時(shí)候,檢查實(shí)體類和表結(jié)構(gòu)是否有變化,如果有,執(zhí)行更新表結(jié)構(gòu)相關(guān)的sql
    如果添加一個(gè)屬性,JPA可以幫我們?cè)诒碇刑砑訉?duì)應(yīng)的列
    如果刪除一個(gè)屬性,JPA不會(huì)幫我們?nèi)ケ碇袆h除對(duì)應(yīng)的列
    如果修改一個(gè)屬性(類型),JPA不會(huì)幫我們?nèi)ケ碇袆h除對(duì)應(yīng)的列
  • hibernate.hbm2ddl.auto=validate
    在啟動(dòng)的時(shí)候,檢查實(shí)體類和表結(jié)構(gòu)是否有變化,如果有,啟動(dòng)失敗,拋出異常
    Caused by: org.hibernate.HibernateException: Missing column: sn in jpa.user
選擇:
  • 在開發(fā)階段,我們通常使用create或者create-drop,可以快速的創(chuàng)建對(duì)應(yīng)的表結(jié)構(gòu)
  • 在測(cè)試階段,不要使用create或者create-drop,因?yàn)檫@樣會(huì)將我們辛苦錄入的測(cè)試數(shù)據(jù)刪除,所以,我們使用update,在實(shí)體類修改的時(shí)候,更新表結(jié)構(gòu)即可
  • 在生產(chǎn)環(huán)境中,我們通常使用validate,這樣可以在啟動(dòng)階段發(fā)現(xiàn)表結(jié)構(gòu)相關(guān)的問題,至于表結(jié)構(gòu)的修改,交給我們的DBA去完成吧.

單對(duì)象映射中常用的注解

  • 對(duì)象映射相關(guān)
  1. @Entity:
    對(duì)實(shí)體類的映射,默認(rèn)使用當(dāng)前類的簡(jiǎn)單名稱作為類名,如在使用JPQL做查詢的時(shí)候,使用該名字實(shí)現(xiàn)數(shù)據(jù)的查詢
    JPQL語句:SELECT u FROM User u;
    User:為默認(rèn)使用的類名,可以通過Entity中的name屬性修改
    @Entity(name=”UserInfo”):將類的名稱修改為UserInfo,那么上面的JPQL中的User修改為UserInfo即可

  2. @Table:
    指定實(shí)體類映射的表的相關(guān)信息,如:表名,默認(rèn)和類名一致
    @Table(name=”t_user”):將映射的表名修改為t_user

  3. persistence.xml文件中的相關(guān)元素的配置說明
    <class>:指定需要掃描的實(shí)體類
    <exclude-unlisted-classes>:設(shè)置為true的時(shí)候,表示不掃描這里沒有列出來的類
    <jar-file>:指定對(duì)項(xiàng)目中引入的jar包中的類進(jìn)行掃描

  • 屬性相關(guān):
  1. @GeneratedValue,主鍵生成策略
    在一張表中,主鍵列的信息通常需要受到程序員的特殊關(guān)照,這里我們需要探討一下主鍵的生成方式(自動(dòng)生成/手動(dòng)設(shè)值)
    首先,我們需要在主鍵屬性上使用@GeneratedValue注解中的strategy屬性來設(shè)值主鍵的生成方式

    1. strategy=GenerationType.AUTO
      把主鍵生成策略交給JPA廠商(Persistence Provider)弥奸,由它根據(jù)具體的數(shù)據(jù)庫(kù)選擇合適的策略赠橙,可以是Table/Sequence/Identity中的一種凤薛。假如數(shù)據(jù)庫(kù)是Oracle缤苫,則選擇Sequence帜矾。
      如果不做特別指定,默認(rèn)是使用這種方式生成主鍵

    2. strategy=GenerationType.IDENTITY
      多數(shù)數(shù)據(jù)庫(kù)支持IDENTITY死陆,數(shù)據(jù)庫(kù)會(huì)在新行插入時(shí)自動(dòng)給ID賦值规哪,這也叫做ID自增長(zhǎng)列,比如MySQL中可以在創(chuàng)建表時(shí)聲明“AUTO_INCREMENT”,該策略在Oracle數(shù)據(jù)庫(kù)中不支持

    3. strategy=GenerationType.TABLE
      有時(shí)候?yàn)榱瞬灰蕾囉跀?shù)據(jù)庫(kù)的具體實(shí)現(xiàn)努酸,在不同數(shù)據(jù)庫(kù)之間更好的移植烙荷,可以在數(shù)據(jù)庫(kù)中新建序列表來生成主鍵昼伴,序列表一般包含兩個(gè)字段:第一個(gè)字段引用不 同的關(guān)系表持舆,第二個(gè)字段是該關(guān)系表的最大序號(hào)。這樣吧享,只需要一張序列就可以用于多張表的主鍵生成。
      如果不指定表生成器耻卡,JPA廠商會(huì)使用默認(rèn)的表,比如Hibernate在Oracle數(shù)據(jù)庫(kù)上會(huì)默認(rèn)使用表hibernate_sequence瘸羡。
      這種方式雖然通用性最好卷仑,所有的關(guān)系型數(shù)據(jù)庫(kù)都支持锚扎,但是由于不能充分利用具體數(shù)據(jù)庫(kù)的特性浪听,建議不要優(yōu)先使用克伊。

    4. strategy=GenerationType.SEQUENCE
      Oracle不支持ID自增長(zhǎng)列而是使用序列的機(jī)制生成主鍵ID条舔,對(duì)此,可以選用序列作為主鍵生成策略:
      如果不指定序列生成器的名稱,則使用廠商提供的默認(rèn)序列生成器吟温,比如Hibernate默認(rèn)提供的序列名稱為hibernate_sequence糙申。
      支持的數(shù)據(jù)庫(kù): Oracle衔统、PostgreSQL舱殿、DB2
      屬性映射
      @Column:
      使用該注解可以對(duì)屬性和列進(jìn)行相關(guān)映射

該注解可以貼在字段上,也可貼在getter方法上,但是必須是統(tǒng)一的,不能一部分在字段上,一部分在getter方法上

  • @Access
    在實(shí)際開發(fā)中,也可以告訴JPA只去掃描哪個(gè)位置上的@Column注解,如果沒有就不在去其他地方掃描
    @Access(AccessType.PROPERTY):屬性,對(duì)應(yīng)著get方法
    @Access(AccessType.FIELD):字段:對(duì)應(yīng)字段

  • @Column
    name:列名,通常,屬性名和列名一直的時(shí)候,不需要指定,默認(rèn)使用屬性名作為列名
    unique:唯一性約束
    nullable:非空約束
    insertable:false,表示在生成insert語句的時(shí)候不插入這一列的值
    updatable:false,表示在生成update語句的時(shí)候不更新這一列的值
    length:指定該列的長(zhǎng)度
    columnDefination:自定義列的類型,默認(rèn)是JPA根據(jù)屬性的類型自動(dòng)生成
    precision:在使用decimal類型的時(shí)候指定總長(zhǎng)度
    scale:在使用decimal類型的時(shí)候指定小數(shù)位數(shù)

  • @Temporal:
    日期類型的映射
    指定日期類型的屬性對(duì)應(yīng)的列的類型(date/datatime/timestamp)

  • @Transient:
    非持久化類型的映射
    JPA在做對(duì)象關(guān)系映射的時(shí)候,默認(rèn)是對(duì)實(shí)體類中的所有屬性進(jìn)行映射的,如果有不需要映射的屬性,可以使用該注解完成

  • @Lob:
    大數(shù)據(jù)類型的映射
    對(duì)象如果是String類型的,默認(rèn)情況下載表中映射的是VARCHAR類型
    該注解可以對(duì)應(yīng)text/blob/clob類型進(jìn)行映射,如:

@Lob
private String content;

一級(jí)緩存

在EntityManager中存在一個(gè)緩存區(qū)域,稱之為一級(jí)緩存

在該緩存區(qū)中,會(huì)將查詢到的對(duì)象緩存到該區(qū)域中

如果在同一個(gè)EntityManager中,查詢相同OID的數(shù)據(jù),那么只需要發(fā)送一條sql

在事務(wù)提交/關(guān)閉EntityManager之后,一級(jí)緩存會(huì)清空,所以在不同的EntityManager中使用不
同的一級(jí)緩存

一級(jí)緩存也可以使用下面的方法手動(dòng)清除緩存數(shù)據(jù)
detach:清除一級(jí)緩存中指定的對(duì)象
clear:清除一級(jí)緩存中的所有的緩存數(shù)據(jù)

image.png

但是一級(jí)緩存的緩存能力是非常有限的,因?yàn)槲覀儾粫?huì)經(jīng)常在一個(gè)EntityManager中查詢相同的數(shù)據(jù)
延遲加載

JPA中,根據(jù)主鍵查詢數(shù)據(jù)可以使用下面兩個(gè)方法完成:
<T> T find(Class<T> type, Object oid);
<T> T getReference(Class<T> type, Object oid);
相同點(diǎn):都是根據(jù)主鍵查詢指定類型的數(shù)據(jù)

不同點(diǎn): getReference方法是在真實(shí)使用該對(duì)象的時(shí)候才會(huì)發(fā)送查詢的sql語句,如

public void testGetReference() throws Exception {
    EntityManager em = JPAUtil.getEntityManager();
    //這里不會(huì)立即發(fā)送sql查詢
    User u = em.getReference(User.class, 1L);
    System.out.println("-------------");
    //在訪問User對(duì)象中的屬性值的時(shí)候表示真正使用該對(duì)象
    System.out.println(u.getName());
    em.close();
}

執(zhí)行結(jié)果:

-------------

Hibernate: select user0_.id as id1_0_0_, user0_.hiredate as hiredate2_0_0_, user0_.name             as  name3_0_0_, user0_.sn as sn4_0_0_ from User user0_ where user0_.id=?

Neld

根據(jù)執(zhí)行的打印結(jié)果可以看到,是我們?cè)谡嬲褂迷搶?duì)象的時(shí)候才會(huì)執(zhí)行查詢的sql,而在這之前是不會(huì)發(fā)送SQL執(zhí)行數(shù)據(jù)的查詢

延遲加載

getReference方法查詢數(shù)據(jù)的方式我們稱之為延遲加載

什么是延遲加載? 就是不會(huì)立即執(zhí)行查詢的sql,而是延遲到真正使用的時(shí)候再執(zhí)行,上面的例子已經(jīng)證明了這一點(diǎn)

再觀察:
find方法查詢到的結(jié)果,如果查詢到了對(duì)應(yīng)的數(shù)據(jù),返回查詢到的結(jié)果即可,反之,返回null,所以可以使用ifnull判斷是否有數(shù)據(jù)
getReference方法查詢到的結(jié)果,無論是否查詢到了數(shù)據(jù),結(jié)果都不會(huì)是null,所以不能使用ifnull判斷是否有對(duì)應(yīng)的數(shù)據(jù)
如果在表中沒有對(duì)應(yīng)的數(shù)據(jù),拋出異常
javax.persistence.EntityNotFoundException: Unable to find cn.wolfcode._01_hello.User with id 2

原理:
JPA使用動(dòng)態(tài)代理機(jī)制實(shí)現(xiàn)延遲加載,覆寫該對(duì)象中的所有的getter方法,在getter方法中執(zhí)行查詢當(dāng)前對(duì)象的sql

延遲加載需要搞懂的問題:
1.延遲加載什么時(shí)候發(fā)送SQL執(zhí)行數(shù)據(jù)?
2.為什么需要在關(guān)閉EntityManager對(duì)象之前初始化延遲加載對(duì)象?
3.為什么在訪問對(duì)象的get方法的時(shí)候,會(huì)去初始化當(dāng)前對(duì)象(發(fā)送SQL執(zhí)行查詢)呢?
4.使用find方法沒有查詢到數(shù)據(jù)的時(shí)候,返回值是什么?使用getReference方法沒有查詢到數(shù)據(jù)的時(shí)候,返回值是什么?

對(duì)象狀態(tài)

對(duì)象的狀態(tài)是JPA中非常重要的概念,描述了實(shí)體對(duì)象從瞬時(shí)到持久险掀、從刪除到游離的狀態(tài)變換沪袭。對(duì)實(shí)體的操作其實(shí)就是對(duì)象實(shí)體狀態(tài)的改變, 這對(duì)于我們分析SQL的執(zhí)行情況有很大的幫助。

  • 瞬時(shí)狀態(tài)(Transient)
    使用new關(guān)鍵字創(chuàng)建出來的新對(duì)象,沒有OID,不在一級(jí)緩存中
  • 持久狀態(tài)(Persistent)
    調(diào)用持久化方法之后,將對(duì)象保存到數(shù)據(jù)庫(kù)中,對(duì)象狀態(tài)轉(zhuǎn)化成持久狀態(tài)
  • 游離狀態(tài)(Detached)
    對(duì)象存在于數(shù)據(jù)庫(kù)中,但是不在一級(jí)緩存中
  • 刪除狀態(tài)(Removed)
    事務(wù)一旦提交,對(duì)象就會(huì)被從數(shù)據(jù)庫(kù)中刪除,是介于持久狀態(tài)和被刪除之間的一個(gè)臨界狀態(tài)

我們可以通過下面的表格了解到各個(gè)狀態(tài)的特點(diǎn):

狀態(tài) 是否在一級(jí)緩存 是否有OID
瞬時(shí)狀態(tài)(Transient)
持久狀態(tài)(Persistent)
游離狀態(tài)(Detached)
刪除狀態(tài)(Removed)

EntityManager提供一系列的方法管理實(shí)體對(duì)象的狀態(tài)樟氢,包括:

  • persist, 將新創(chuàng)建的或已刪除的實(shí)體轉(zhuǎn)變?yōu)镻ersistent狀態(tài)冈绊,數(shù)據(jù)存入數(shù)據(jù)庫(kù)。
  • remove埠啃,刪除持久狀態(tài)的實(shí)體
  • merge死宣,將游離實(shí)體轉(zhuǎn)變?yōu)镻ersistent狀態(tài),數(shù)據(jù)存入數(shù)據(jù)庫(kù)碴开。

如果使用了事務(wù)管理毅该,則事務(wù)的commit/rollback也會(huì)改變實(shí)體的狀態(tài)。
如圖:


image.png

有了對(duì)對(duì)象狀態(tài)的了解之后,我們來分析面的案例中sql的發(fā)送

@Test
public void test() throws  Exception{
    EntityManager em = JPAUtil.getEntityManager();
    em.getTransaction().begin();
    //通過find方法查詢到處于持久狀態(tài)的User對(duì)象
    User u = em.find(User.class, 1L);
    u.setName("Lucy");//①
    em.getTransaction().commit();
    em.close();
}

執(zhí)行結(jié)果:
Hibernate: select user0_.id as id1_0_0_, user0_.hiredate as hiredate2_0_0_, user0_.name as name3_0_0_, user0_.sn as sn4_0_0_ from User user0_ where user0_.id=?

Hibernate: update User set hiredate=?, name=?, sn=? where id=?

  • 分析:
    ①:在這里,我們修改了查詢出來處于持久狀態(tài)的User對(duì)象的name屬性的值

我們并沒有調(diào)用merge方法去更新User對(duì)象,為什么會(huì)發(fā)送update語句呢?

  • 原因:
    首先,將數(shù)據(jù)從數(shù)據(jù)庫(kù)中查詢出來后,在內(nèi)存中會(huì)有兩份數(shù)據(jù),一份在EntityManager一級(jí)緩存區(qū)域,一份在EntityManager的快照區(qū),兩份數(shù)據(jù)完全一樣

然后,修改User的name屬性時(shí),其實(shí)是修改的緩存區(qū)的數(shù)據(jù)

最后,在提交事務(wù)的時(shí)候,會(huì)清理一級(jí)緩存,此時(shí)會(huì)對(duì)比兩份數(shù)據(jù)是否一致,如果不一致,發(fā)送對(duì)應(yīng)的update語句將緩存中的臟數(shù)據(jù)(和數(shù)據(jù)庫(kù)中的數(shù)據(jù)不一致)同步到數(shù)據(jù)庫(kù)中

所以,在上面的例子中,我們看到執(zhí)行了一條更新語句,這樣相信大家就能夠理解了,這也是在我們了解了對(duì)象的狀態(tài)之后對(duì)SQL的發(fā)送有了更深入的認(rèn)識(shí)

WechatIMG9.jpeg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末潦牛,一起剝皮案震驚了整個(gè)濱河市眶掌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巴碗,老刑警劉巖朴爬,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異橡淆,居然都是意外死亡召噩,警方通過查閱死者的電腦和手機(jī)母赵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚣常,“玉大人市咽,你說我怎么就攤上這事〉治茫” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵溯革,是天一觀的道長(zhǎng)贞绳。 經(jīng)常有香客問我,道長(zhǎng)致稀,這世上最難降的妖魔是什么冈闭? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮抖单,結(jié)果婚禮上萎攒,老公的妹妹穿的比我還像新娘。我一直安慰自己矛绘,他們只是感情好耍休,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著货矮,像睡著了一般羊精。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上囚玫,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天喧锦,我揣著相機(jī)與錄音,去河邊找鬼抓督。 笑死燃少,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铃在。 我是一名探鬼主播阵具,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼涌穆!你這毒婦竟也來了怔昨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤宿稀,失蹤者是張志新(化名)和其女友劉穎趁舀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祝沸,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矮烹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年越庇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奉狈。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卤唉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仁期,到底是詐尸還是另有隱情桑驱,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布跛蛋,位于F島的核電站熬的,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赊级。R本人自食惡果不足惜押框,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望理逊。 院中可真熱鬧橡伞,春花似錦、人聲如沸晋被。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墨微。三九已至道媚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翘县,已是汗流浹背最域。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锈麸,地道東北人镀脂。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像忘伞,于是被迫代替她去往敵國(guó)和親薄翅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344