作者:鐘昕靈,叩丁狼教育高級(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語句緊密耦合帮寻。
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目錄中
- 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)建
- 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ì)象
- 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é)
persistence.xml文件的配置
配置連接數(shù)據(jù)庫(kù)的基本信息
JPA的基本行為配置實(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)完成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)
@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即可@Table:
指定實(shí)體類映射的表的相關(guān)信息,如:表名,默認(rèn)和類名一致
@Table(name=”t_user”):將映射的表名修改為t_userpersistence.xml文件中的相關(guān)元素的配置說明
<class>:指定需要掃描的實(shí)體類
<exclude-unlisted-classes>:設(shè)置為true的時(shí)候,表示不掃描這里沒有列出來的類
<jar-file>:指定對(duì)項(xiàng)目中引入的jar包中的類進(jìn)行掃描
- 屬性相關(guān):
-
@GeneratedValue,主鍵生成策略
在一張表中,主鍵列的信息通常需要受到程序員的特殊關(guān)照,這里我們需要探討一下主鍵的生成方式(自動(dòng)生成/手動(dòng)設(shè)值)
首先,我們需要在主鍵屬性上使用@GeneratedValue注解中的strategy屬性來設(shè)值主鍵的生成方式strategy=GenerationType.AUTO
把主鍵生成策略交給JPA廠商(Persistence Provider)弥奸,由它根據(jù)具體的數(shù)據(jù)庫(kù)選擇合適的策略赠橙,可以是Table/Sequence/Identity中的一種凤薛。假如數(shù)據(jù)庫(kù)是Oracle缤苫,則選擇Sequence帜矾。
如果不做特別指定,默認(rèn)是使用這種方式生成主鍵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ù)中不支持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)先使用克伊。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ù)
但是一級(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)。
如圖:
有了對(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í)