JPA-ORM規(guī)范

一、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 包及配置
image.png
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);
    }
    
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容