Pro JPA2 第二章(入門)
JPA的主要目標之一是簡單易用和易於理解.雖然它的問題域不容忽視或者淡化,但是解決問題的技術非常簡單和直觀.
本章首先將描述實體(entity)的基本特徵.定義什麼是實體,以及如何創建,讀取,更新和刪除實體.還將介紹實體管理器(EntityManager)以及如何獲取和使用它們.接着,將快速瞭解查詢(Query)以及如何使用EntityManager和Query對象指定和執行查詢.然後最後會有一個總結性的小例子.
2.1 實體概述
實體的描述爲:有特性和關係的事物,期望把它的特性和關係保存在關係數據中.實體基本上是一個名詞,或者是一組狀態關聯在一起而形成的單個單元.在面向對象的範例中,將行爲添加到實體中,並稱之爲對象.在JPA中,任何應用程序定義的對象都可以是一個實體.因此,一個重要問題也許是:是的對象變成實體的特徵是什麼?- 2.1.1 持久性
實體的第一個和最基本的特徵是它們是可持久化的.這意味着可以在一個數據存儲中表示它們的裝啊提,並且將來可以對他們進行訪問.
這樣的實體我們稱之爲持久化對象(persistence object).但從技術角度來說這是不正確的.嚴格來講,一個持久化對象只有在內存中實例化的時刻才變得持久.如果存在一個持久化對象,那麼根據定義它已經是持久的.
爲了使實體具有持久性,應用程序必須主動地調用API方法來啓動該過程.這是一個重要的區別,因爲它把持久化的控制權完全留給了應用程序.
要點是實體可能不一定必須被持久化,他們是否應該被持久化將由應用程序來決定. - 2.1.2 標識
類似任何其他的Java對象,實體具有一個對象標識(identity),但是當它存在於數據庫中時,還用友一個持久化標識.持久化標識,或一個標識符(identifier),是唯一標識一個實體實例並區別與其他所有相同的實體類型實例的關鍵.
也就是說,在數據庫表中存在一行,那麼它會具有一個持久化標識,如果不在數據庫中,那麼,即使在內存中的實體可以在一個字段中設置標識,它也不具有持久化標識. - 2.1.3 事務性
我們可以稱實體爲半事務性的(quasi-transactional).
在內存中,其跟數據庫的事務稍微有點不同.因爲實體可能會發生改變,但這些改變並沒有被持久化. - 2.1.4 粒度
實體不是基本類型(primitive),基本類型封裝器(primitive wrapper) 或者具有單維狀態(single-dimensional state)的內置對象.這些類型都僅僅是標量(scalar),對應用程序並沒有任何固有的語義含義.
實體意味着是細粒度的對象,它包含一個聚合狀態集合,他們通常存儲在單個位置.最通常的意義下,它們是業務域對象,對於訪問它們的應用程序具有特定含義. - 2.2 實體元數據
除了其持久化的狀態,每個實體都包含一些相關的元數據來描述它.這些元數據可能作爲保存的類文件的一部分存在,或者可能在類的外部存儲,但不是保存在數據庫中.元數據是的持久化層從加載到在運行時調用實體,均能夠識別,結束以及正確地管理它們.
每個實體實際所需的元數據都是最小量的,以確保實體已於定義和使用.
兩種方式指定實體的元數據:註解(Annotation)或者XML.
- 2.2.1 註解
元數據註解允許把結構化和類型化的元數據附加到源代碼上.因爲註解使得元數據與進程同時存在,所以不必轉義到額外的文件和特別的語言(XML)中. - 2.2.2 XML
依然可以使用XML來使用元數據 - 2.2.3 異常配置
異常配置(configuration by exception)的概念意味着持久化引擎定義了適用於大多數應用程序的默認值,用於只有在希望覆蓋默認值時,才需要提供值.換句話說,提供配置值是規則的異常情況,而不是必需的.
- 2.2.1 註解
- 2.1.1 持久性
2.3 創建實體
@Entity public class Employee { @Id private int id; private String name; private long salary; public Employee() {} public Employee(int id) { this.id = id; } }
說明:
- @Entity註解 這僅僅是一個標記註解,用於通知持久化引擎 該類是一個實體.
- @Id註解 它註解了特定字段或屬性用於保存實體的持久化標識(主鍵).它是必需的.
Id註解可以寫在字段或者屬性上.
當其寫在字段上時,叫字段訪問
當其寫在getter方法上時,叫屬性訪問.此時,將忽略實體字段,僅使用getter和setter方法進行命名,也就是說,此時,數據庫的字段名稱是根據getter方法來產生的.跟字段沒有直接關係.
2.4 實體管理器
前面已經指出,實體在數據庫中真正獲得持久化之前,需要調用一個特定的API.這個API就由實體管理器實現,它幾乎完全封裝在成爲EntityManager的單個接口中.持久化的真正工作需要委託給實體管理器來完成.(注意理解前面持久化的概念,想想爲什麼要把實體持久化的操作交給單獨的API,也就是實體管理器.)
通過顯式地把實體作爲參數傳遞到一個方法調用,或者是從數據庫直接讀取它,實體管理器能夠獲得一個實體的引用.此時,將該對象稱之爲由實體管理器管理.實體管理器在任何給定的時間內所管理的實體實例的結合,稱爲持久化上下文(persistence context).
在任何時候,具有相同持久化標識的Java實例,在一個持久化上下文中只能存在一個.2.4.1 獲取實體管理器
實體管理器總是從一個EntityManagerFactory獲取.獲取實體管理器的這個工廠將確定控制其操作的配置參數.在Java SE環境中,有一個Psersistence的簡單引導類.
獲得EntityManagerFactory:EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
通過EntityManagerFactory創建EntityManager:
EntityManager em = emf.createEntityManager();
2.4.2 持久化實體
持久化一個實體值得是:獲得一個臨時實體,或者在數據庫中尚未有任何持久化表示的實體,然後存儲該實體的狀態是的能夠在後續對其進行檢索.Employee emp = new Employee(158); em.persist(emp);
當persist()調用返回時,emp將成爲在實體管理器的持久化上下文中的一個託管實體.
2.4.3 尋找實體
Employee emp = em.find(Employee.class,158);
返回的Employee實體將是一個託管實體,它將存在於當前實體管理器所關聯的持久化上下文中.
如果對象已被刪除或者如果我們提供了錯誤的id,則程序將會返回null.這個地方我們需要做null檢查.2.4.4 刪除實體
Employee emp = em.find(Employee.class,158); em.remove(emp);
如果在調用remove()之前,沒有進行null檢查,則有可能會拋出java.lang.IllegalArgumentException.
要被刪除的實體,必須是一個託管實體.2.4.5 更新實體
Employee emp = em.find(Employee.class,158); emp.setSalary(emp.getSalary()+100; em.merge(emp);
要更新的實體,必須是一個託管實體.
- 2.4.6 事務
詳細請參考第六章. - 2.4.7 查詢
JPA的查詢類似於數據庫查詢,但它不適用結構化查詢語言(SQL)來指定查詢條件,而是對實體進行查詢,並且其使用Java持久化查詢語言(Java Persistence Query Language,JPQL)的查詢語言.
查詢在代碼中實現爲一個Query活TypedQuery對象.它們使用EntityManager作爲工廠來構建.
可以動態或靜態地定義查詢.靜態查詢可以在註解或者XML元數據中定義,它必須包括查詢條件以及用戶指定的名稱.這類查詢也稱爲命名查詢(named query),在執行時,通過名稱來查找它.
動態查詢可以在運行時通過提供JPQL查詢條件或一個條件對象來生成.當然這會引入一些額外的執行開銷.
2.5 彙總
操作Employee實體的服務類:package pro.jpa.service; import pro.jpa.entity.Employee; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.*; import java.util.ArrayList; import java.util.List; public class EmployeeService { protected EntityManager em; public EmployeeService(EntityManager em) { this.em = em; } public Employee create(int id, String name, long salary) { Employee emp = new Employee(id); emp.setName(name); emp.setSalary(salary); em.persist(emp); return emp; } public void remove(int id) { Employee emp = this.find(id); if (null != emp) { em.remove(emp); } } public Employee find(int id) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Employee.class); Root<Employee> root = cq.from(Employee.class); List<Selection> selections = new ArrayList<>(); selections.add(root.get("id")); cq.multiselect(selections); //cq.select(root); TypedQuery<Employee> query = em.createQuery(cq); List<Predicate> criteria = new ArrayList<>(); ParameterExpression<Integer> p = cb.parameter(Integer.class, "id"); criteria.add(cb.equal(root.get("id"), p)); return query.getSingleResult(); } public Employee updateSalary(int id, long raise) { Employee emp = em.find(Employee.class, id); if (null != emp) { emp.setSalary(emp.getSalary() + raise); } return emp; } public List<Employee> findAll() { Query query = em.createNativeQuery("SELECT e.* FROM Employee e"); return query.getResultList(); } }
測試EmployeeService:
package pro.jpa.service; import org.junit.Before; import org.junit.Test; import pro.jpa.entity.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import java.util.List; public class EmployeeTest { EntityManagerFactory emf; EntityManager em; EmployeeService es; @Before public void before() { emf = Persistence.createEntityManagerFactory("EmployeeService"); em = emf.createEntityManager(); es = new EmployeeService(em); } public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService"); EntityManager em = emf.createEntityManager(); EmployeeService es = new EmployeeService(em); // create and persist an employee em.getTransaction().begin(); Employee emp = es.create(1166, "aaa", 40000000); em.getTransaction().commit(); System.out.println("Persisted " + emp); // find a specific employee emp = es.find(158); System.out.println("Found " + emp); // find all employees List<Employee> emps = es.findAll(); emps.forEach(e -> { System.out.println("Found employee " + e); }); // update the employee em.getTransaction().begin(); emp = es.updateSalary(158, 2000); em.getTransaction().commit(); System.out.println("Updated " + emp); // remove an employee em.getTransaction().begin(); es.remove(158); em.getTransaction().commit(); System.out.println("Removed Employee 158"); em.close(); emf.close(); } @Test public void testFind() { Employee emp = es.find(158); System.out.println(emp.getId()); System.out.println(emp.getName()); System.out.println(emp.getSalary()); } @Test public void testSave() { es.create(999, "aaa", 40l); } }
2.6 組裝
JPA的基本構建模塊已經瞭解.現在我們要把它們組合在一起運行在Java SE的環境上.2.6.1 持久化單元
描述持久化單元(persistence unit)的配置定義在一個成爲persistence.xml的XML文件中.
單個persistenc.xml文件可以包含一個或多個已命名的持久化單元配置,但是每個持久化單元是獨立於和有別於其他持久性單元的,它們在邏輯上可以認爲是存在於單獨的persistence.xml文件中.<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="EmployeeService" transaction-type="RESOURCE_LOCAL"> <class>pro.jpa.entity.Employee</class> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/projpa"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="root"/> </properties> </persistence-unit> </persistence>
- 2.6.2 持久化存檔文件
把持久性的項目組裝在一起,我們寬鬆地稱之爲持久化存檔文件(Persistence Archive).它實際上就是JAR格式的文件,在META-INF目錄下包含persistence.xml文件,並且一般包含實體類文件.