一、JPA概述
1. JPA是什么
(1) Java Persistence API:用于對(duì)象持久化的 API
(2) Java EE 5.0 平臺(tái)標(biāo)準(zhǔn)的 ORM 規(guī)范,使得應(yīng)用程序以統(tǒng)一的方式訪問持久層
2. JPA和Hibernate的關(guān)系
(1) JPA 是 hibernate 的一個(gè)抽象(就像JDBC和JDBC驅(qū)動(dòng)的關(guān)系):
- JPA 是規(guī)范:JPA 本質(zhì)上就是一種 ORM 規(guī)范,不是ORM 框架 —— 因?yàn)?JPA 并未提供 ORM 實(shí)現(xiàn),它只是制訂了一些規(guī)范,提供了一些編程的 API 接口,但具體實(shí)現(xiàn)則由 ORM 廠商提供實(shí)現(xiàn)
- Hibernate 是實(shí)現(xiàn):Hibernate 除了作為 ORM 框架之外,它也是一種 JPA 實(shí)現(xiàn)
(2) 從功能上來說, JPA 是 Hibernate 功能的一個(gè)子集
3. JPA的供應(yīng)商
(1) JPA 的目標(biāo)之一是制定一個(gè)可以由很多供應(yīng)商實(shí)現(xiàn)的 API,目前Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的實(shí)現(xiàn)
(2) Hibernate
- JPA 的始作俑者就是 Hibernate 的作者
- Hibernate 從 3.2 開始兼容 JPA
(3) OpenJPA
- OpenJPA 是 Apache 組織提供的開源項(xiàng)目
(4) TopLink
- TopLink 以前需要收費(fèi),如今開源了
4. JPA的優(yōu)勢(shì)
(1) 標(biāo)準(zhǔn)化: 提供相同的 API,這保證了基于JPA 開發(fā)的企業(yè)應(yīng)用能夠經(jīng)過少量的修改就能夠在不同的 JPA 框架下運(yùn)行。
(2) 簡(jiǎn)單易用,集成方便: JPA 的主要目標(biāo)之一就是提供更加簡(jiǎn)單的編程模型,在 JPA 框架下創(chuàng)建實(shí)體和創(chuàng)建 Java 類一樣簡(jiǎn)單,只需要使用 javax.persistence.Entity 進(jìn)行注釋;JPA 的框架和接口也都非常簡(jiǎn)單,
(3) 可媲美JDBC的查詢能力: JPA的查詢語言是面向?qū)ο蟮模琂PA定義了獨(dú)特的JPQL,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級(jí)查詢特性,甚至還能夠支持子查詢。
(4) 支持面向?qū)ο蟮母呒?jí)特性: JPA 中能夠支持面向?qū)ο蟮母呒?jí)特性,如類之間的繼承、多態(tài)和類之間的復(fù)雜關(guān)系,最大限度的使用面向?qū)ο蟮哪P?/p>
5. JPA包括3方面的技術(shù)
(1) ORM 映射元數(shù)據(jù):JPA 支持 XML 和 JDK 5.0 注解兩種元數(shù)據(jù)的形式,元數(shù)據(jù)描述對(duì)象和表之間的映射關(guān)系,框架據(jù)此將實(shí)體對(duì)象持久化到數(shù)據(jù)庫表中。
(2) JPA 的 API:用來操作實(shí)體對(duì)象,執(zhí)行CRUD操作,框架在后臺(tái)完成所有的事情,開發(fā)者從繁瑣的 JDBC和 SQL代碼中解脫出來。
(3) 查詢語言(JPQL):這是持久化操作中很重要的一個(gè)方面,通過面向?qū)ο蠖敲嫦驍?shù)據(jù)庫的查詢語言查詢數(shù)據(jù),避免程序和具體的 SQL 緊密耦合。
二、JPA的簡(jiǎn)單使用
1. 創(chuàng)建一個(gè)JPA項(xiàng)目
若eclipse無法創(chuàng)建,請(qǐng)參考https://www.cnblogs.com/crawl/p/7703803.html
2. 修改persistence.xml配置
3. 配置實(shí)體類
4. 執(zhí)行持久化操作
@注:JPA 規(guī)范要求在類路徑的 META-INF 目錄下放置persistence.xml,文件的名稱是固定的
三、JPA基本注解
1. 六個(gè)基本注解
@Entity
標(biāo)注用于實(shí)體類聲明語句之前,指出該Java 類為實(shí)體類,將映射到指定的數(shù)據(jù)庫表。如聲明一個(gè)實(shí)體類 Customer,它將映射到數(shù)據(jù)庫中的 customer 表上。
@Table
- 當(dāng)實(shí)體類與其映射的數(shù)據(jù)庫表名不同名時(shí)需要使用 @Table 標(biāo)注說明,該標(biāo)注與 @Entity 標(biāo)注并列使用,置于實(shí)體類聲明語句之前,可寫于單獨(dú)語句行,也可與聲明語句同行。
- Table 標(biāo)注的常用選項(xiàng)是 name,用于指明數(shù)據(jù)庫的表名
- Table標(biāo)注還有一個(gè)兩個(gè)選項(xiàng) catalog 和 schema 用于設(shè)置表所屬的數(shù)據(jù)庫目錄或模式,通常為數(shù)據(jù)庫名。uniqueConstraints 選項(xiàng)用于設(shè)置約束條件,通常不須設(shè)置。
@Id
- Id 標(biāo)注用于聲明一個(gè)實(shí)體類的屬性映射為數(shù)據(jù)庫的主鍵列。該屬性通常置于屬性聲明語句之前,可與聲明語句同行,也可寫在單獨(dú)行上。
- Id標(biāo)注也可置于屬性的getter方法之前。
@GeneratedValue(不填,默認(rèn)auto)
- GeneratedValue 用于標(biāo)注主鍵的生成策略,通過 strategy 屬性指定。默認(rèn)情況下,JPA 自動(dòng)選擇一個(gè)最適合底層數(shù)據(jù)庫的主鍵生成策略:SqlServer 對(duì)應(yīng) identity,MySQL 對(duì)應(yīng) auto increment。
- 在 javax.persistence.GenerationType 中定義了以下幾種可供選擇的策略:
- IDENTITY:采用數(shù)據(jù)庫 ID自增長(zhǎng)的方式來自增主鍵字段,Oracle 不支持這種方式;
- AUTO: JPA自動(dòng)選擇合適的策略,是默認(rèn)選項(xiàng);
- SEQUENCE:通過序列產(chǎn)生主鍵,通過 @SequenceGenerator 注解指定序列名,MySql 不支持這種方式
- TABLE:通過表產(chǎn)生主鍵,框架借由表模擬序列產(chǎn)生主鍵,使用該策略可以使應(yīng)用更易于數(shù)據(jù)庫移植。
@Column
- 當(dāng)實(shí)體的屬性與其映射的數(shù)據(jù)庫表的列不同名時(shí)需要使用@Column 標(biāo)注說明,該屬性通常置于實(shí)體的屬性聲明語句之前,還可與 @Id 標(biāo)注一起使用。
- Column 標(biāo)注的常用屬性是 name,用于設(shè)置映射數(shù)據(jù)庫表的列名。此外,該標(biāo)注還包含其它多個(gè)屬性,如:unique 、nullable、length 等。
- Column 標(biāo)注的 columnDefinition 屬性: 表示該字段在數(shù)據(jù)庫中的實(shí)際類型.通常 ORM 框架可以根據(jù)屬性類型自動(dòng)判斷數(shù)據(jù)庫中字段的類型,但是對(duì)于Date類型仍無法確定數(shù)據(jù)庫中字段類型究竟是DATE,TIME還是TIMESTAMP.此外,String的默認(rèn)映射類型為VARCHAR, 如果要將 String 類型映射到特定數(shù)據(jù)庫的 BLOB 或TEXT 字段類型.
- Column標(biāo)注也可置于屬性的getter方法之前
@Basic(未加注解的默認(rèn)注解)
- Basic 表示一個(gè)簡(jiǎn)單的屬性到數(shù)據(jù)庫表的字段的映射,對(duì)于沒有任何標(biāo)注的 getXxxx() 方法,默認(rèn)即為@Basic
- fetch: 表示該屬性的讀取策略,有 EAGER 和 LAZY 兩種,分別表示主支抓取和延遲加載,默認(rèn)為 EAGER.
- optional:表示該屬性是否允許為null, 默認(rèn)為true
2. @Transient
- 表示該屬性并非一個(gè)到數(shù)據(jù)庫表的字段的映射,ORM框架將忽略該屬性.
- 如果一個(gè)屬性并非數(shù)據(jù)庫表的字段映射,就務(wù)必將其標(biāo)示為@Transient,否則,ORM框架默認(rèn)其注解為@Basic
3. @Temporal
在核心的 Java API 中并沒有定義 Date 類型的精度(temporal precision). 而在數(shù)據(jù)庫中,表示 Date 類型的數(shù)據(jù)有 DATE, TIME, 和 TIMESTAMP 三種精度(即單純的日期,時(shí)間,或者兩者 兼?zhèn)?. 在進(jìn)行屬性映射時(shí)可使用@Temporal注解來調(diào)整精度.
4. 用 table 來生成主鍵詳解
四、JPA API
1. persistence
(1) Persistence 類是用于獲取 EntityManagerFactory 實(shí)例。該類包含一個(gè)名為 createEntityManagerFactory 的靜態(tài)方法 。
(2) createEntityManagerFactory 方法有如下兩個(gè)重載版本。
- 帶有一個(gè)參數(shù)的方法以 JPA 配置文件 persistence.xml 中的持久化單元名為參數(shù)
- 帶有兩個(gè)參數(shù)的方法:前一個(gè)參數(shù)含義相同,后一個(gè)參數(shù) Map類型,用于設(shè)置 JPA 的相關(guān)屬性,這時(shí)將忽略其它地方設(shè)置的屬性。Map 對(duì)象的屬性名必須是 JPA 實(shí)現(xiàn)庫提供商的名字空間約定的屬性名。
// 創(chuàng)建 EntitymanagerFactory
String persistenceUnitName = "jpa-1";
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName);
Map<String, Object> properites = new HashMap<String, Object>();
properites.put("hibernate.show_sql", true);
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName,properites);
2. EntityManagerFactory
EntityManagerFactory 接口主要用來創(chuàng)建 EntityManager 實(shí)例。該接口約定了如下4個(gè)方法:
createEntityManager():用于創(chuàng)建實(shí)體管理器對(duì)象實(shí)例。
createEntityManager(Map map):用于創(chuàng)建實(shí)體管理器對(duì)象實(shí)例的重載方法,Map 參數(shù)用于提供 EntityManager 的屬性。
isOpen():檢查 EntityManagerFactory 是否處于打開狀態(tài)。實(shí)體管理器工廠創(chuàng)建后一直處于打開狀態(tài),除非調(diào)用close()方法將其關(guān)閉。
close():關(guān)閉 EntityManagerFactory 。 EntityManagerFactory 關(guān)閉后將釋放所有資源,isOpen()方法測(cè)試將返回 false,其它方法將不能調(diào)用,否則將導(dǎo)致IllegalStateException異常。
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManagerFactory.close();
3. EntityManager#find
4. EntityManager#getReference
5. EntityManager#persistence
6. EntityManager#remove
package com.atguigu.jpa.test;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.atguigu.jpa.helloworld.Customer;
public class JPATest {
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
private EntityTransaction transaction;
@Before
public void init() {
entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
}
@After
public void destroy() {
transaction.commit();
entityManager.close();
entityManagerFactory.close();
}
// 類似于 hibernate 中 Session 的 delete 方法. 把對(duì)象對(duì)應(yīng)的記錄從數(shù)據(jù)庫中移除
// 但注意: 該方法只能移除 持久化 對(duì)象. 而 hibernate 的 delete 方法實(shí)際上還可以移除 游離對(duì)象.
@Test
public void testRemove() {
// Customer customer = new Customer();
// customer.setId(2);
Customer customer = entityManager.find(Customer.class, 2);
entityManager.remove(customer);
}
// 類似于 hibernate 的 save 方法。使對(duì)象由臨時(shí)狀態(tài)變?yōu)槌志没癄顟B(tài)
// 和 hibernate 的 save 方法的不同之處:若對(duì)象有id,則不能執(zhí)行 insert 操作,而會(huì)拋出異常
@Test
public void testPersistence() {
Customer customer = new Customer();
customer.setAge(15);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("bb@163.com");
customer.setLastName("BB");
customer.setId(100);
entityManager.persist(customer);
System.out.println(customer.getId());
}
// 類似于 hibernate 中 Session 的 load 方法
@Test
public void testGetReference() {
Customer customer = entityManager.getReference(Customer.class, 1);
// 代理對(duì)象com.atguigu.jpa.helloworld.Customer_$$_javassist_0,可能會(huì)出現(xiàn)懶加載的情況
System.out.println(customer.getClass().getName());
System.out.println("-------------------------------------");
System.out.println(customer.toString());
}
// 類似于 hibernate 中 Session 的 get 方法
@Test
public void testFind() {
Customer customer = entityManager.find(Customer.class, 1);
System.out.println("-----------------------------");
System.out.println(customer.toString());
}
}
實(shí)體的狀態(tài)
- 新建狀態(tài): 新創(chuàng)建的對(duì)象,尚未擁有持久性主鍵。
- 持久化狀態(tài):已經(jīng)擁有持久性主鍵并和持久化建立了上下文環(huán)境
- 游離狀態(tài):擁有持久化主鍵,但是沒有與持久化建立上下文環(huán)境
- 刪除狀態(tài): 擁有持久化主鍵,已經(jīng)和持久化建立上下文環(huán)境,但是從數(shù)據(jù)庫中刪除。
7. EntityManager#merge
/**
* 總的來說: 類似于 hibernate Session 的 saveOrUpdate 方法.
*/
// 1. 若傳入的是一個(gè)臨時(shí)對(duì)象
// 會(huì)創(chuàng)建一個(gè)新的對(duì)象, 把臨時(shí)對(duì)象的屬性復(fù)制到新的對(duì)象中, 然后對(duì)新的對(duì)象執(zhí)行持久化操作. 所以
// 新的對(duì)象中有 id, 但以前的臨時(shí)對(duì)象中沒有 id.
@Test
public void testMerge1() {
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("cc@163.com");
customer.setLastName("CC");
Customer customer2 = entityManager.merge(customer);
System.out.println("customer#id:" + customer.getId());
System.out.println("customer2#id:" + customer2.getId());
}
// 若傳入的是一個(gè)游離對(duì)象, 即傳入的對(duì)象有 OID.
// 1. 若在 EntityManager 緩存中沒有該對(duì)象
// 2. 若在數(shù)據(jù)庫中也沒有對(duì)應(yīng)的記錄
// 3. JPA 會(huì)創(chuàng)建一個(gè)新的對(duì)象, 然后把當(dāng)前游離對(duì)象的屬性復(fù)制到新創(chuàng)建的對(duì)象中
// 4. 對(duì)新創(chuàng)建的對(duì)象執(zhí)行 insert 操作.
@Test
public void testMerge2() {
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("dd@163.com");
customer.setLastName("DD");
customer.setId(100);
Customer customer2 = entityManager.merge(customer);
System.out.println("customer#id:" + customer.getId());
System.out.println("customer2#id:" + customer2.getId());
}
//若傳入的是一個(gè)游離對(duì)象, 即傳入的對(duì)象有 OID.
//1. 若在 EntityManager 緩存中沒有該對(duì)象
//2. 若在數(shù)據(jù)庫中也有對(duì)應(yīng)的記錄
//3. JPA 會(huì)查詢對(duì)應(yīng)的記錄, 然后返回該記錄對(duì)一個(gè)的對(duì)象, 再然后會(huì)把游離對(duì)象的屬性復(fù)制到查詢到的對(duì)象中.
//4. 對(duì)查詢到的對(duì)象執(zhí)行 update 操作.
@Test
public void testMerge3(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("ee@163.com");
customer.setLastName("EE");
customer.setId(4);
Customer customer2 = entityManager.merge(customer);
System.out.println(customer == customer2); //false
}
//若傳入的是一個(gè)游離對(duì)象, 即傳入的對(duì)象有 OID.
//1. 若在 EntityManager 緩存中有對(duì)應(yīng)的對(duì)象
//2. JPA 會(huì)把游離對(duì)象的屬性復(fù)制到查詢到EntityManager 緩存中的對(duì)象中.
//3. EntityManager 緩存中的對(duì)象執(zhí)行 UPDATE.
@Test
public void testMerge4(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("dd@163.com");
customer.setLastName("DD");
customer.setId(4);
Customer customer2 = entityManager.find(Customer.class, 4);
entityManager.merge(customer);
System.out.println(customer == customer2); //false
}
8. EntityManager 其它方法
/**
* 同 hibernate 中 Session 的 refresh 方法.
*/
@Test
public void testRefresh(){
Customer customer = entityManager.find(Customer.class, 1);
customer = entityManager.find(Customer.class, 1);
entityManager.refresh(customer);
}
/**
* 同 hibernate 中 Session 的 flush 方法.
*/
@Test
public void testFlush(){
Customer customer = entityManager.find(Customer.class, 1);
System.out.println(customer);
customer.setLastName("AA");
entityManager.flush();
}
(1) flush ():同步持久上下文環(huán)境,即將持久上下文環(huán)境的所有未保存實(shí)體的狀態(tài)信息保存到數(shù)據(jù)庫中。
(2) setFlushMode (FlushModeType flushMode):設(shè)置持久上下文環(huán)境的Flush模式。參數(shù)可以取2個(gè)枚舉
- FlushModeType.AUTO 為自動(dòng)更新數(shù)據(jù)庫實(shí)體,
- FlushModeType.COMMIT 為直到提交事務(wù)時(shí)才更新數(shù)據(jù)庫記錄。
(3) getFlushMode ():獲取持久上下文環(huán)境的Flush模式。返回FlushModeType類的枚舉值。
(4) refresh (Object entity):用數(shù)據(jù)庫實(shí)體記錄的值更新實(shí)體對(duì)象的狀態(tài),即更新實(shí)例的屬性值。
(5) clear ():清除持久上下文環(huán)境,斷開所有關(guān)聯(lián)的實(shí)體。如果這時(shí)還有未提交的更新則會(huì)被撤消。
(6) contains (Object entity):判斷一個(gè)實(shí)例是否屬于當(dāng)前持久上下文環(huán)境管理的實(shí)體。
(7) isOpen ():判斷當(dāng)前的實(shí)體管理器是否是打開狀態(tài)。
(8) getTransaction ():返回資源層的事務(wù)對(duì)象。EntityTransaction實(shí)例可以用于開始和提交多個(gè)事務(wù)。
(9) close ():關(guān)閉實(shí)體管理器。之后若調(diào)用實(shí)體管理器實(shí)例的方法或其派生的查詢對(duì)象的方法都將拋出 IllegalstateException 異常,除了getTransaction 和 isOpen方法(返回 false)。不過,當(dāng)與實(shí)體管理器關(guān)聯(lián)的事務(wù)處于活動(dòng)狀態(tài)時(shí),調(diào)用 close 方法后持久上下文將仍處于被管理狀態(tài),直到事務(wù)完成。
(10) createQuery (String qlString):創(chuàng)建一個(gè)查詢對(duì)象。
(11) createNamedQuery (String name):根據(jù)命名的查詢語句塊創(chuàng)建查詢對(duì)象。參數(shù)為命名的查詢語句。
(12) createNativeQuery (String sqlString):使用標(biāo)準(zhǔn) SQL語句創(chuàng)建查詢對(duì)象。參數(shù)為標(biāo)準(zhǔn)SQL語句字符串。
(13) createNativeQuery (String sqls, String resultSetMapping):使用標(biāo)準(zhǔn)SQL語句創(chuàng)建查詢對(duì)象,并指定返回結(jié)果集 Map的 名稱。
9. EntityTransaction
EntityTransaction 接口用來管理資源層實(shí)體管理器的事務(wù)操作。通過調(diào)用實(shí)體管理器的getTransaction方法 獲得其實(shí)例。
(1) begin ()
用于啟動(dòng)一個(gè)事務(wù),此后的多個(gè)數(shù)據(jù)庫操作將作為整體被提交或撤消。若這時(shí)事務(wù)已啟動(dòng)則會(huì)拋出 IllegalStateException 異常。
(2) commit ()
用于提交當(dāng)前事務(wù)。即將事務(wù)啟動(dòng)以后的所有數(shù)據(jù)庫更新操作持久化至數(shù)據(jù)庫中。
(3) rollback ()
撤消(回滾)當(dāng)前事務(wù)。即撤消事務(wù)啟動(dòng)后的所有數(shù)據(jù)庫更新操作,從而不對(duì)數(shù)據(jù)庫產(chǎn)生影響。
(4) setRollbackOnly ()
使當(dāng)前事務(wù)只能被撤消。
(5) getRollbackOnly ()
查看當(dāng)前事務(wù)是否設(shè)置了只能撤消標(biāo)志。
(6) isActive ()
查看當(dāng)前事務(wù)是否是活動(dòng)的。如果返回true則不能調(diào)用begin方法,否則將拋出 IllegalStateException 異常;如果返回 false 則不能調(diào)用 commit、rollback、setRollbackOnly 及 getRollbackOnly 方法,否則將拋出 IllegalStateException 異常。
五、映射關(guān)聯(lián)關(guān)系
1. 映射單向多對(duì)一的關(guān)聯(lián)關(guān)系
@Test
public void testManyToOneUpdate(){
Order order = entityManager.find(Order.class, 7);
order.getCustomer().setLastName("FFF");
}
// 不能直接刪除 1 的一端, 因?yàn)橛型怄I約束.
@Test
public void testManyToOneRemove() {
// Order order = entityManager.find(Order.class, 1);
// entityManager.remove(order);
Customer customer = entityManager.find(Customer.class, 10);
entityManager.remove(customer);
}
// 默認(rèn)情況下, 使用左外連接的方式來獲取 n 的一端的對(duì)象和其關(guān)聯(lián)的 1 的一端的對(duì)象.
// 可使用 @ManyToOne 的 fetch 屬性來修改默認(rèn)的關(guān)聯(lián)屬性的加載策略
@Test
public void testManyToOneFind() {
Order order = entityManager.find(Order.class, 7);
System.out.println(order.getOrderName());
System.out.println(order.getCustomer().getLastName());
}
// 保存多對(duì)一時(shí), 建議先保存 1 的一端, 后保存 n 的一端, 這樣不會(huì)多出額外的 UPDATE 語句.
@Test
public void testManyToOnePersist() {
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("gg@163.com");
customer.setLastName("GG");
Order order1 = new Order();
order1.setOrderName("G-GG-1");
Order order2 = new Order();
order2.setOrderName("G-GG-2");
// 設(shè)置關(guān)聯(lián)關(guān)系
order1.setCustomer(customer);
order2.setCustomer(customer);
// 執(zhí)行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}
2. 映射單向一對(duì)多的關(guān)聯(lián)關(guān)系
@Test
public void testUpdate(){
Customer customer = entityManager.find(Customer.class, 3);
customer.getOrders().iterator().next().setOrderName("O-XXX-10");
}
//默認(rèn)情況下, 若刪除 1 的一端, 則會(huì)先把關(guān)聯(lián)的 n 的一端的外鍵置空, 然后進(jìn)行刪除.
//可以通過 @OneToMany 的 cascade 屬性來修改默認(rèn)的刪除策略.
@Test
public void testOneToManyRemove(){
Customer customer = entityManager.find(Customer.class, 2);
entityManager.remove(customer);
}
//默認(rèn)對(duì)關(guān)聯(lián)的多的一方使用懶加載的加載策略.
//可以使用 @OneToMany 的 fetch 屬性來修改默認(rèn)的加載策略
@Test
public void testOneToManyFind(){
Customer customer = entityManager.find(Customer.class, 1);
System.out.println(customer.getLastName());
System.out.println(customer.getOrders().size());
}
//單向 1-n 關(guān)聯(lián)關(guān)系執(zhí)行保存時(shí), 一定會(huì)多出 UPDATE 語句.
//因?yàn)?n 的一端在插入時(shí)不會(huì)同時(shí)插入外鍵列.
@Test
public void testOneToManyPersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("mm@163.com");
customer.setLastName("MM");
Order order1 = new Order();
order1.setOrderName("O-MM-1");
Order order2 = new Order();
order2.setOrderName("O-MM-2");
//建立關(guān)聯(lián)關(guān)系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//執(zhí)行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}
3. 映射雙向多對(duì)一的關(guān)聯(lián)關(guān)系(一般多的一方進(jìn)行維護(hù),會(huì)提升SQL性能)
4. 映射雙向一對(duì)一的關(guān)聯(lián)關(guān)系
5. 映射雙向多對(duì)多的關(guān)聯(lián)關(guān)系(必須有一方放棄外鍵維護(hù))
在雙向多對(duì)多關(guān)系中,我們必須指定一個(gè)關(guān)系維護(hù)端(owner side),可以通過 @ManyToMany 注釋中指定 mappedBy 屬性來標(biāo)識(shí)其為關(guān)系維護(hù)端。
六、二級(jí)緩存
1. 加入 jar 包及配置
2. 添加 ehcache.xml
3. 添加注解:@Cacheable(true)
七、JPQL(不支持使用 INSERT)
1. javax.persistence.Query
Query接口封裝了執(zhí)行數(shù)據(jù)庫查詢的相關(guān)方法。調(diào)用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以獲得查詢對(duì)象,進(jìn)而可調(diào)用 Query 接口的相關(guān)方法來執(zhí)行查詢操作。
(1) createQuery
// 默認(rèn)情況下, 若只查詢部分屬性, 則將返回 Object[] 類型的結(jié)果. 或者 Object[] 類型的 List.
// 也可以在實(shí)體類中創(chuàng)建對(duì)應(yīng)的構(gòu)造器, 然后再 JPQL 語句中利用對(duì)應(yīng)的構(gòu)造器返回實(shí)體類的對(duì)象.
@Test
public void testPartlyProperties() {
// String jpql = "select c.lastName,c.age from Customer c where c.id > ?";
// new 一個(gè)對(duì)象,并提供構(gòu)造器,可返回對(duì)象
String jpql = "select new Customer(c.lastName,c.age) from Customer c where c.id > ?";
List resultList = entityManager.createQuery(jpql).setParameter(1, 0).getResultList();
System.out.println(resultList);
}
(2) createNamedQuery
// createNamedQuery 適用于在實(shí)體類前使用 @NamedQuery 標(biāo)記的查詢語句
@Test
public void testNamedQuery() {
Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 2);
Customer customer = (Customer) query.getSingleResult();
System.out.println(customer);
}
(3) createNativeQuery
// createNativeQuery 適用于本地 SQL
@Test
public void testNativeQuery() {
String sql = "SELECT age FROM jpa_cutomers WHERE id = ?";
Query query = entityManager.createNativeQuery(sql).setParameter(1, 2);
Object result = query.getSingleResult();
System.out.println(result);
}
2. query 的主要方法
int executeUpdate()
用于執(zhí)行update或delete語句。List getResultList()
用于執(zhí)行select語句并返回結(jié)果集實(shí)體列表。Object getSingleResult()
用于執(zhí)行只返回單個(gè)結(jié)果實(shí)體的select語句。Query setFirstResult(int startPosition)
用于設(shè)置從哪個(gè)實(shí)體記錄開始返回查詢結(jié)果。Query setMaxResults(int maxResult)
用于設(shè)置返回結(jié)果實(shí)體的最大數(shù)。與setFirstResult結(jié)合使用可實(shí)現(xiàn)分頁查詢。Query setFlushMode(FlushModeType flushMode)
設(shè)置查詢對(duì)象的Flush模式。參數(shù)可以取2個(gè)枚舉值:FlushModeType.AUTO 為自動(dòng)更新數(shù)據(jù)庫記錄,F(xiàn)lushMode Type.COMMIT 為直到提交事務(wù)時(shí)才更新數(shù)據(jù)庫記錄。setHint(String hintName, Object value)
設(shè)置與查詢對(duì)象相關(guān)的特定供應(yīng)商參數(shù)或提示信息。參數(shù)名及其取值需要參考特定 JPA 實(shí)現(xiàn)庫提供商的文檔。如果第二個(gè)參數(shù)無效將拋出IllegalArgumentException異常。setParameter(int position, Object value)
為查詢語句的指定位置參數(shù)賦值。Position 指定參數(shù)序號(hào),value 為賦給參數(shù)的值。setParameter(int position, Date d, TemporalType type)
為查詢語句的指定位置參數(shù)賦 Date 值。Position 指定參數(shù)序號(hào),value 為賦給參數(shù)的值,temporalType 取 TemporalType 的枚舉常量,包括 DATE、TIME 及 TIMESTAMP 三個(gè),,用于將 Java 的 Date 型值臨時(shí)轉(zhuǎn)換為數(shù)據(jù)庫支持的日期時(shí)間類型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。setParameter(int position, Calendar c, TemporalType type)
為查詢語句的指定位置參數(shù)賦 Calenda r值。position 指定參數(shù)序號(hào),value 為賦給參數(shù)的值,temporalType 的含義及取舍同前。setParameter(String name, Object value)
為查詢語句的指定名稱參數(shù)賦值。setParameter(String name, Date d, TemporalType type)
為查詢語句的指定名稱參數(shù)賦 Date 值。用法同前。setParameter(String name, Calendar c, TemporalType type)
為查詢語句的指定名稱參數(shù)設(shè)置Calendar值。name為參數(shù)名,其它同前。該方法調(diào)用時(shí)如果參數(shù)位置或參數(shù)名不正確,或者所賦的參數(shù)值類型不匹配,將拋出 IllegalArgumentException 異常。
3. where子句
(1) where條件表達(dá)式中可用的運(yùn)算符基本上與SQL一致,包括:
- 算術(shù)運(yùn)算符:+ - * / +(正) -(負(fù))
- 關(guān)系運(yùn)算符:== <> > >= < <= between…and like in is null 等
- 邏輯運(yùn)算符: and or not
(2) 占位符的方式
select o from Orders o where o.id = :myId
注意:參數(shù)名前必須冠以冒號(hào)(:),執(zhí)行查詢前須使用Query.setParameter(name, value)方法給參數(shù)賦值。select o from Order o where o.id = ?1 and o.customer = ?2
其中 ?1 代表第一個(gè)參數(shù),?2 代表第一個(gè)參數(shù)。在執(zhí)行查詢之前需要使用重載方法Query.setParameter(pos, value) 提供參數(shù)值。
4. 查詢緩存
// 使用 hibernate 的查詢緩存.
// 前提配置啟用查詢緩存
@Test
public void testQueryCache() {
String jpql = "from Customer c where c.age > ?";
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
query.setParameter(1, 1);
customers = query.getResultList();
System.out.println(customers.size());
}
5. order by 和 group by
// 查詢 order 數(shù)量大于 2 的那些 Customer
@Test
public void testGroupBy() {
String jpql = "SELECT o.customer FROM Order o " + "GROUP BY o.customer " + "HAVING count(o.id) >= 2";
List<Customer> customers = entityManager.createQuery(jpql).getResultList();
System.out.println(customers);
}
@Test
public void testOrderBy() {
String jpql = "FROM Customer c WHERE c.age > ? ORDER BY c.age DESC";
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
// 占位符的索引是從 1 開始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
}
6. 關(guān)聯(lián)查詢
/**
* JPQL 的關(guān)聯(lián)查詢同 HQL 的關(guān)聯(lián)查詢.
*/
@Test
public void testLeftOuterJoinFetch(){
String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
Customer customer =
(Customer) entityManager.createQuery(jpql).setParameter(1, 1).getSingleResult();
System.out.println(customer.getLastName());
System.out.println(customer.getOrders().size());
// List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, 12).getResultList();
// System.out.println(result);
}
7. 子查詢和內(nèi)建函數(shù)
@Test
public void testSubQuery(){
//查詢所有 Customer 的 lastName 為 YY 的 Order
String jpql = "SELECT o FROM Order o "
+ "WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)";
Query query = entityManager.createQuery(jpql).setParameter(1, "YY");
List<Order> orders = query.getResultList();
System.out.println(orders.size());
}
//使用 jpql 內(nèi)建的函數(shù)
@Test
public void testJpqlFunction(){
String jpql = "SELECT lower(c.email) FROM Customer c";
List<String> emails = entityManager.createQuery(jpql).getResultList();
System.out.println(emails);
}
JPQL提供了以下一些內(nèi)建函數(shù),包括字符串處理函數(shù)、算術(shù)函數(shù)和日期函數(shù)。
字符串處理函數(shù)主要有:
concat(String s1, String s2):字符串合并/連接函數(shù)。
substring(String s, int start, int length):取字串函數(shù)。
trim([leading|trailing|both,] [char c,] String s):從字符串中去掉首/尾指定的字符或空格。
lower(String s):將字符串轉(zhuǎn)換成小寫形式。
upper(String s):將字符串轉(zhuǎn)換成大寫形式。
length(String s):求字符串的長(zhǎng)度。
locate(String s1, String s2[, int start]):從第一個(gè)字符串中查找第二個(gè)字符串(子串)出現(xiàn)的位置。若未找到則返回0。
算術(shù)函數(shù)主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素個(gè)數(shù)。
日期函數(shù)主要為三個(gè),即 current_date、current_time、current_timestamp,它們不需要參數(shù),返回服務(wù)器上的當(dāng)前日期、時(shí)間和時(shí)戳。
8. delete 和 updae
//可以使用 JPQL 完成 UPDATE 和 DELETE 操作.
@Test
public void testExecuteUpdate(){
String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";
Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
query.executeUpdate();
}
八、JPA 整合 Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 配置自動(dòng)掃描的包 -->
<context:component-scan base-package="com.atguigu.jpa"></context:component-scan>
<!-- 配置 C3P0 數(shù)據(jù)源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<!-- 配置其他屬性 -->
</bean>
<!-- 配置 EntityManagerFactory -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的適配器. 可以通過內(nèi)部 bean 的方式來配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置實(shí)體類所在的包 -->
<property name="packagesToScan" value="com.atguigu.jpa.spring.entities"></property>
<!-- 配置 JPA 的基本屬性. 例如 JPA 實(shí)現(xiàn)產(chǎn)品的屬性 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置 JPA 使用的事務(wù)管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 配置支持基于注解是事務(wù)配置 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
注:獲取 EntityManager 的方式
@Repository
public class PersonDao {
//如何獲取到和當(dāng)前事務(wù)關(guān)聯(lián)的 EntityManager 對(duì)象呢 ?
//通過 @PersistenceContext 注解來標(biāo)記成員變量!
@PersistenceContext
private EntityManager entityManager;
public void save(Person person){
entityManager.persist(person);
}
}