深入了解 JPA
轉載自:http://www.cnblogs.com/crawl/p/7703679.html
前言:談起操作數據庫,大致可以分為幾個階段:首先是 JDBC 階段,初學 JDBC 可能會使用原生的 JDBC 的 API,再然后可能會使用數據庫連接池,比如:c3p0、dbcp,還有一些第三方工具,比如 dbutils 等,LZ為 JDBC 是貫穿始終的,即使到了框架部分,也會對 JDBC 進行整合,此階段還是自己手寫 SQL 語句;下一個階段就是 Hibernate,大家體會到了操作數據庫可以不用自己手動編寫 SQL,調用 Hibernate 提供的 API 即可。今天給大家介紹的是操作數據庫的另一個模塊 JPA,即 Java 持久層的 API,JPA 如果與 SpringData 結合起來,會發出不一樣的“化學反應”,大家拭目以待~
一、JPA 概述
1. Java Persistence API(Java 持久層 API):用于對象持久化的 API
2. 作用:使得應用程序以統一的方式訪問持久層
3. 前言中提到了 Hibernate,那么JPA 與 Hibernate究竟是什么關系呢:
1)JPA 是 Hibernate 的一個抽象,就像 JDBC 和 JDBC 驅動的關系
2)JPA 是一種 ORM 規范,是 Hibernate 功能的一個子集 (既然 JPA 是規范,Hibernate 對 JPA 進行了擴展,那么說 JPA 是 Hibernate 的一個子集不為過)
3)Hibernate 是 JPA 的一個實現
4. JPA 包括三個方面的技術:
1)ORM 映射元數據,支持 XML 和 JDK 注解兩種元數據的形式
2)JPA 的 API
3)查詢語言:JPQL
本文也將詳細介紹JPA ORM 映射元數據的注解方式和 JPA 的 API 以及 JPQL 三個方面
二、JPA 的 Helloworld
1.在 Eclipse 中創建 JPA 的工程:New ---> Project ---> JPA Project? 輸入工程名,選擇版本為 2.0,點擊 Next,
若是初次創建 JPA 的工程,可能會出錯,提示必須有一個 user library,導致無法創建工廠,此問題的解決方案,LZ另開一個博客,手把手教你解決無法創建 JPA 工程的問題,大家可前去查看,在此不做贅述。
2.在當前工程下新建一個 lib 目錄,用來存放各種 jar 包,此時工程的目錄結構為:
導入 hibernate 的 jar 包和 jpa 的jar包,注意:需要自己手動的 Build Path:
連接數據庫,不要忘記添加 MySQL 的驅動。
3. 大家會發現當我們創建好 jpa 的工程時在 src 的 META-INF 目錄下自動生成了一個 persistence.xml 文件,我們的配置都編寫在此文件中,接下來就在此文件中進行各種配置
1)打開此文件,選擇左下角的 Connection 選項,修改 Transaction Type 為 Resource Local,填寫下方的 Driver、Url、User、Password 信息保存后,便會在左下角最后一個 Source 選項中自動生成數據庫配置的基本信息 (lZ 創建的數據庫為 jpa2)
2)其他配置(大家注意一點配置 ORM 產品時,要把 <provider> 節點放到 <provider> 節點上面,否則會出小紅×):
1 <?xml version="1.0" encoding="UTF-8"?> 2 <persistence version="2.0" 3 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 5 <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL"> 6 7 <!-- 使用什么 ORM 產品作為 JPA 的實現 --> 8 <provider>org.hibernate.ejb.HibernatePersistence</provider> 9 10 <properties> 11 <!-- 數據庫連接的基本信息 --> 12 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> 13 <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa2" /> 14 <property name="javax.persistence.jdbc.user" value="root" /> 15 <property name="javax.persistence.jdbc.password" value="qiqingqing" /> 16 <!-- 配置 JPA 實現產品的基本屬性,即 Hibernate 的基本屬性 --> 17 <property name="hibernate.show_sql" value="true" /> 18 <property name="hibernate.format_sql" value="true" /> 19 <property name="hibernate.hbm2ddl.auto" value="update" /> 20 </properties> 21 22 </persistence-unit> 23 </persistence>3)創建持久化類 Customer,并為其添加 JPA 的注解,此時會有錯誤,原因是沒有把 Customer 類添加到 persistence.xml 文件中,添加進去便沒有了錯誤:
1 package com.software.jpa.helloworld; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.GeneratedValue; 6 import javax.persistence.GenerationType; 7 import javax.persistence.Id; 8 import javax.persistence.Table; 9 10 @Table(name="JPA_CUSTOMERS") 11 @Entity 12 public class Customer { 13 14 private Integer id; 15 16 private String lastName; 17 18 private String email; 19 20 private Integer age; 21 22 @GeneratedValue(strategy=GenerationType.AUTO) 23 @Id 24 public Integer getId() { 25 return id; 26 } 27 28 public void setId(Integer id) { 29 this.id = id; 30 } 31 32 @Column(name="LAST_NAME") 33 public String getLastName() { 34 return lastName; 35 } 36 37 public void setLastName(String lastName) { 38 this.lastName = lastName; 39 } 40 41 public String getEmail() { 42 return email; 43 } 44 45 public void setEmail(String email) { 46 this.email = email; 47 } 48 49 public Integer getAge() { 50 return age; 51 } 52 53 public void setAge(Integer age) { 54 this.age = age; 55 } 56 57 @Override 58 public String toString() { 59 return "Customer [id=" + id + ", lastName=" + lastName + ", email=" + email + ", age=" + age + "]"; 60 } 61 62 } 1 <?xml version="1.0" encoding="UTF-8"?> 2 <persistence version="2.0" 3 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 5 <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL"> 6 7 <!-- 使用什么 ORM 產品作為 JPA 的實現 --> 8 <provider>org.hibernate.ejb.HibernatePersistence</provider> 9 10 <!-- 添加持久化類 --> 11 <class>com.software.jpa.helloworld.Customer</class> 12 13 <properties> 14 <!-- 數據庫連接的基本信息 --> 15 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> 16 <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa2" /> 17 <property name="javax.persistence.jdbc.user" value="root" /> 18 <property name="javax.persistence.jdbc.password" value="qiqingqing" /> 19 <!-- 配置 JPA 實現產品的基本屬性,即 Hibernate 的基本屬性 --> 20 <property name="hibernate.show_sql" value="true" /> 21 <property name="hibernate.format_sql" value="true" /> 22 <property name="hibernate.hbm2ddl.auto" value="update" /> 23 </properties> 24 25 </persistence-unit> 26 </persistence>4)創建一個測試類進行測試,生成數據表,插入了數據,至此 JPA 的 Helloworld 完成。
1 package com.software.jpa.helloworld; 2 3 import javax.persistence.EntityManager; 4 import javax.persistence.EntityManagerFactory; 5 import javax.persistence.EntityTransaction; 6 import javax.persistence.Persistence; 7 8 public class Main { 9 10 public static void main(String[] args) { 11 12 //1.創建 EntityManagerFactory 13 String persistenceUnitName = "jpa"; 14 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName); 15 16 //2.創建 EntityManager 17 EntityManager entityManager = entityManagerFactory.createEntityManager(); 18 19 //4.開啟事務 20 EntityTransaction transaction = entityManager.getTransaction(); 21 transaction.begin(); 22 23 //5.進行持久化操作 24 Customer customer = new Customer(); 25 customer.setLastName("AA"); 26 customer.setEmail("aa@163.com"); 27 customer.setAge(20); 28 29 entityManager.persist(customer); 30 31 //6.提交事務 32 transaction.commit(); 33 34 //7.關閉 EntityManager 35 entityManager.close(); 36 37 //8.關閉 EntityManagerFactory 38 entityManagerFactory.close(); 39 40 } 41 42 }三、JPA 的基本注解
看了 JPA 的 Helloworld 之后說一下 JPA 的基本注解
1. @Entity :修飾實體類,指明該類將映射到指定的數據表,例如:Customer 類默認的數據表名為 customer
2. @Table :當實體類與映射的數據庫表名不同名時需要使用 @Table 注解,該注解與 @Entity 注解并列使用,使用其 name 屬性指明數據庫的表名
1 @Table(name = "JPA_CUSTOMER")2 @Entity 3 public class Customer {3. @Id :標識該屬性為主鍵,一般標注在該屬性的 getter 方法上
4. @GeneratedValue :標注主鍵的生成策略,通過其 strategy 屬性。通常與 @Id 注解一起使用。默認情況下 JPA 會自動選擇一個最適合底層數據庫的主鍵生成策略,MySQL 默認為 AUTO,常用策略有:
–IDENTITY:采用數據庫 ID自增長的方式來自增主鍵字段,Oracle 不支持這種方式;
–AUTO: JPA自動選擇合適的策略,是默認選項;
–SEQUENCE:通過序列產生主鍵,通過 @SequenceGenerator 注解指定序列名,MySql 不支持這種方式
–TABLE:通過表產生主鍵,框架借由表模擬序列產生主鍵,使用該策略可以使應用更易于數據庫移植
5. @Basic :用于沒有任何標注的 getXxx() 方法,默認即為 @Basic,所以若一個 getter 方法無任何注解,可以使用 @Basic 注解,也可以不使用
6. @Column :當實體的屬性與其映射的數據表的列不同名時使用,一般用于 getter 方法上。其 name 屬性用來指明此屬性在數據表中對應的列名;unique 屬性指明是否為唯一約束;nullable 屬性用來指明是否可以為空,false 為不能為空;length 屬性指明此列的長度。
?
7. @Transient :標注此注解后在創建數據表的時候將會忽略該屬性? Customer 類并沒有 info 這個屬性,所以數據庫中也不應該有 info 這個字段
8. @Temporal :向數據庫映射日期(Date)屬性時用來調整映射的精度。Date 類型的數據有 DATE, TIME, 和 TIMESTAMP 三種精度(即單純的日期,時間,或者兩者兼備).
Birth 屬性應該使用 DATE 類型(生日只具體到日即可,如:2015-10-22),而 CreateTime 應該使用 TIMESTAMP 類型(創建時間應該具體到秒,如:2017-10-11 22:39:13)
補沖:使用 TABLE 生成主鍵詳解
1.創建一個數據表 jpa_id_generators,并添加幾條數據
?
2. 配置使用 TABLE 主鍵生成策略
1 //使用 TABLE 主鍵生成策略 2 @TableGenerator(name="ID_GENERATOR", //該主鍵生成策略的名稱,與 @GeneratedValue 的 generator 屬性值對應 3 table="jpa_id_generators", // 指明根據哪個表生成主鍵 4 pkColumnName="PK_NAME", // 使用 pkColumnName pkColumnValue valueColumnName 三個屬性唯一的定位一個點 5 pkColumnValue="CUSTOMER_ID", 6 valueColumnName="PK_VALUE", 7 allocationSize=100) //指定每次增加的數量 8 @GeneratedValue(strategy=GenerationType.TABLE, generator="ID_GENERATOR") 9 @Id 10 public Integer getId() { 11 return id; 12 }附一張表說明:
?
四、JPA 的 API
1.Persistence :用于獲取 EntiryManagerFactory 的實例
1)常用方法:Persistence.createEntityManagerFactory(persistenceUnitName) 方法
1 String persistenceUnitName = "jpa-1"; 2 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName);2. EntiryManagerFactory :常用方法
1)獲取 EntiryManager
1 //創建 EntityManager,類似于 Hibernate 的 SessionFactory 2 EntityManager entityManager = entityManagerFactory.createEntityManager();2)close() 方法,關閉自身,此方法不再演示
3. EntityManager 的常用 API
1)find() 方法,類似于 Hibernate 中的 Session 的 get() 方法,在執行 find 方法時就發送 SQL 語句
1 //類似于 Hibernate 中 Session 的 get 方法 2 @Test 3 public void testFind() { 4 Customer customer = entityManager.find(Customer.class, 1); 5 6 System.out.println("----------------------------------------"); 7 8 System.out.println(customer); 9 }打印結果為:查看橫線的位置便可證明結論。
1 Hibernate: 2 select 3 customer0_.id as id1_2_0_, 4 customer0_.age as age2_2_0_, 5 customer0_.birth as birth3_2_0_, 6 customer0_.createTime as createTi4_2_0_, 7 customer0_.email as email5_2_0_, 8 customer0_.LAST_NAME as LAST_NAM6_2_0_ 9 from 10 JPA_CUSTOMER customer0_ 11 where 12 customer0_.id=? 13 ---------------------------------------- 14 Customer [id=1, lastName=AA, email=aa@163.com, age=21, birth=2015-10-22, createTime=2017-10-11 22:39:13.0]2)getReference() 方法,類似于 Hibernate 的 Session 的 load() 方法
1 //相當于 Hibernate 中 Session 的 load 方法,若不使用查詢的對象則返回一個代理對象,到真正使用時才發送 SQL 語句查詢 2 //可能會發生懶加載異常 3 @Test 4 public void testGetReference() { 5 Customer customer = entityManager.getReference(Customer.class, 1); 6 System.out.println(customer.getClass().getName()); 7 8 System.out.println("---------------------------------------"); 9 10 // transaction.commit(); 11 // entityManager.close(); 12 13 System.out.println(customer); 14 }打印結果為:打印的是一個代理對象,并且橫線打印在? SQL 前面。
com.software.jpa.helloworld.Customer_$$_javassist_1 --------------------------------------- Hibernate: selectcustomer0_.id as id1_2_0_,customer0_.age as age2_2_0_,customer0_.birth as birth3_2_0_,customer0_.createTime as createTi4_2_0_,customer0_.email as email5_2_0_,customer0_.LAST_NAME as LAST_NAM6_2_0_ fromJPA_CUSTOMER customer0_ wherecustomer0_.id=? Customer [id=1, lastName=AA, email=aa@163.com, age=21, birth=2015-10-22, createTime=2017-10-11 22:39:13.0]3)persistence() 方法,類似于 Hibernate 的 save() 方法,與 Hibernate 的 save() 方法不同的是其不能插入一個有 id 屬性的對象
1 //類似于 Hibernate 的 save 方法,使對象由臨時狀態變為持久化對象 2 //和 Hibernate 的 save 方法的區別為若有 id 屬性,則不會執行插入操作而會拋出異常 3 @Test 4 public void testPersistence() { 5 Customer customer = new Customer(); 6 customer.setLastName("BB"); 7 customer.setEmail("bb@163.com"); 8 customer.setBirth(new Date()); 9 customer.setCreateTime(new Date()); 10 customer.setAge(21); 11 12 // customer.setId(100); 13 14 entityManager.persist(customer); 15 16 System.out.println(customer.getId()); 17 18 }4)remove() 方法,類似于 Hibernate 中 Session 的 delete 方法,但是其不能刪除 游離化對象(僅有 id),執行 5,6行會拋出異常,因為 5 行的 customer 對象為游離化對象
1 //類似于 Hibernate Session 的 delete 方法,把對象對應的記錄從數據庫中刪除 2 //注:該方法只能移出 持久化 對象,而 Hibernate 的 delete 方法可以移除游離對象 3 @Test 4 public void testRemove() { 5 // Customer customer = new Customer(); 6 // customer.setId(2); 7 8 Customer customer = entityManager.find(Customer.class, 2); 9 10 entityManager.remove(customer); 11 12 }5)merge() 方法,類似于 Hibernate 中 Session 的 saveOrUpdate() 方法
① 傳入的是一個臨時對象(沒有 id):會創建一個新的對象,把臨時對象的屬性復制到新的對象中,然后對新的對象執行持久化操作,13行執行了 merge() 方法,傳入了一個臨時對象,返回了一個新的對象,產看 15,16 行的結果可知,新的對象有 id,傳入的對象木有id,說明是將新的對象插入了數據庫
1 //1.若傳入的是一個臨時對象(沒有 Id) 2 //會創建一個新的對象,把臨時對象的屬性復制到新的對象中,然后對新的對象執行持久化操作 3 //所以 新的對象中有 id,而之前的臨時對象中沒有 id 4 @Test 5 public void testMerge1() { 6 Customer customer = new Customer(); 7 customer.setAge(23); 8 customer.setBirth(new Date()); 9 customer.setCreateTime(new Date()); 10 customer.setEmail("cc@126.com"); 11 customer.setLastName("CC"); 12 13 Customer customer2 = entityManager.merge(customer); 14 15 System.out.println("customer's id:" + customer.getId());// null 16 System.out.println("customer's id:" + customer2.getId());// 2 17 }② 傳入的是一個游離對象(有 ID):若在 EntityManager 緩存中沒有該對象,在數據庫中也沒有對應的記錄,JPA 會創建一個新的對象,把當前游離對象的屬性復制到新的對象中,對新創建的對象執行 insert 操作,LZ的數據庫對應的表中并沒有 id 為 100 customer,15 行同樣返回了一個新的對象,根據返回結果可知 ,確實插入的是新的對象
1 //2.若傳入的是一個游離對象,即傳入的對象有 OID 2 //若在 EntityManager 緩存中沒有該對象,在數據庫中也沒有對應的記錄,JPA 會創建一個新的對象, 3 //把當前游離對象的屬性復制到新的對象中,對新創建的對象執行 insert 操作 4 @Test 5 public void testMerge2() { 6 Customer customer = new Customer(); 7 customer.setAge(23); 8 customer.setBirth(new Date()); 9 customer.setCreateTime(new Date()); 10 customer.setEmail("dd@126.com"); 11 customer.setLastName("DD"); 12 13 customer.setId(100); 14 15 Customer customer2 = entityManager.merge(customer); 16 17 System.out.println("customer's id:" + customer.getId());// 100 18 System.out.println("customer's id:" + customer2.getId());// 3 19 }③ 傳入的是游離對象,即傳入的對象有 OID,緩存中沒有,但數據庫中有對應的對象:JPA 會查詢對應的記錄,然后返回該記錄對應的對象把當前游離對象的屬性復制到查詢到的對象中,對查詢到的對象執行 update 操作
1 //3.若傳入的是一個游離對象,即傳入的對象有 OID 2 //若在 EntityManager 緩存中沒有該對象,在數據庫中有對應的記錄,JPA 會查詢對應的記錄,然后返回該記錄對應的對象 3 //把當前游離對象的屬性復制到查詢到的對象中,對查詢到的對象執行 update 操作 4 @Test 5 public void testMerge3() { 6 Customer customer = new Customer(); 7 customer.setAge(23); 8 customer.setBirth(new Date()); 9 customer.setCreateTime(new Date()); 10 customer.setEmail("ff@126.com"); 11 customer.setLastName("FF"); 12 13 customer.setId(3); 14 15 Customer customer2 = entityManager.merge(customer); 16 17 System.out.println(customer == customer2); //false 18 }④ 傳入的是游離對象,即傳入的對象有 OID,EntityManager 緩存中有對應的對象:JPA 會把當前游離對象的屬性復制到查詢到的 EntityManager 緩存中的對象,對 EntityManager 緩存中的對象執行 update 操作
1 //4.若傳入的是一個游離對象,即傳入的對象有 OID 2 //若在 EntityManager 緩存中有對應的對象,JPA 會把當前游離對象的屬性復制到查詢到的 EntityManager 緩存中的對象, 3 //對 EntityManager 緩存中的對象執行 update 操作 4 @Test 5 public void testMerge4() { 6 Customer customer = new Customer(); 7 customer.setAge(23); 8 customer.setBirth(new Date()); 9 customer.setCreateTime(new Date()); 10 customer.setEmail("dd@126.com"); 11 customer.setLastName("DD"); 12 13 customer.setId(3); 14 Customer customer2 = entityManager.find(Customer.class, 3); 15 16 entityManager.merge(customer); 17 18 System.out.println(customer == customer2); //false 19 }4.EntityTransaction:JPA 中的事務操作
常用 API: begin()????? commit()???? rollback()? 代碼不再演示
五、JPA 中映射關聯關系
1. 映射單向多對一的關聯關系:Order : Customer? n:1 ,Order 中有 Customer 屬性,而 Customer 中沒有 Order 屬性(單向多對一區別于單向一對多)
1)創建 Order 實體類,標注注解,生成數據表,使用 @ManyToOne 映射多對一的關聯關系,使用 @JoinColumn 來標注外鍵
1 package com.software.jpa.helloworld; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.FetchType; 6 import javax.persistence.GeneratedValue; 7 import javax.persistence.Id; 8 import javax.persistence.JoinColumn; 9 import javax.persistence.ManyToOne; 10 import javax.persistence.Table; 11 12 @Table(name="JPA_ORDERS") 13 @Entity 14 public class Order { 15 16 private Integer id; 17 18 private String orderName; 19 20 @GeneratedValue 21 @Id 22 public Integer getId() { 23 return id; 24 } 25 26 public void setId(Integer id) { 27 this.id = id; 28 } 29 30 @Column(name="ORDER_NAME") 31 public String getOrderName() { 32 return orderName; 33 } 34 35 public void setOrderName(String orderName) { 36 this.orderName = orderName; 37 } 38 39 private Customer customer; 40 41 /** 42 * 映射單項 n-1 的關聯關系(Customer 和 Order,Order 中有 Customer 屬性,而 Customer 中沒有 Order 屬性) 43 * 使用 @ManyToOne 來映射多對一的關聯關系 44 * 使用 @JoinColumn 來映射外鍵 45 * 可以使用 @ManyToOne 的 fetch 屬性來修改默認的關聯屬性的加載策略 46 */ 47 @JoinColumn(name="CUSTOMER_ID") 48 @ManyToOne(fetch=FetchType.LAZY) 49 public Customer getCustomer() { 50 return customer; 51 } 52 53 public void setCustomer(Customer customer) { 54 this.customer = customer; 55 } 56 57 }2)單向多對一的保存(persist):保存多對一時,建議先保存 1 的一端,后保存 n 的一端,這樣不會多出額外的 UPDATE 語句
3)獲取操作(find):默認情況下使用左外連接的方式來獲取 n 的一端的對象和其關聯的 1 的一端的對象,可以使用 @ManyToOne 的 fetch 屬性來修改默認的關聯屬性的加載策略
4)刪除操作(remove):不能直接刪除 1 的一端,因為有外鍵約束
?
5)修改操作:
2.映射單向 1-n 的關聯關系 Customer :Order? 1 : n,Customer 中有 Order 的 Set 集合屬性,Order 中沒有 Customer的屬性
1)在 Customer 中添加 Order 的 Set 集合屬性,并映射 1-n 關聯關系,重新生成數據表
2)保存操作(persist):總會多出 UPDATE 語句,n 的一端在插入時不會同時插入外鍵列
3)查詢操作(find):默認使用懶加載
?
4)刪除操作(remove):默認情況下,若刪除 1 的一端,會先把關聯的 n 的一端的外鍵置空,然后再進行刪除,可以通過 @OneToMany 的 cascade 屬性修改默認的刪除策略(CascadeType.REMOVE 為級聯刪除)
?
3.映射雙向多對一的關聯關系(注:雙向多對一 同 雙向一對多)
1)實體:Customer 中有 Order 的 Set 集合屬性,Order 中有 Customer 的屬性,注兩個實體映射的外鍵列必須一致,都為 CUSTOMER_ID
?
2)保存操作(persist):
?
?
4.映射雙向一對一的關聯關系
1)實體:Manager 和 Department ,一個部門有一個經理,一個經理管一個部門
2)創建 Manager 類和 Department 類,Manager 類中有 Department 的引用,Department 中有 Manager 的引用,由 Department 來維護關聯關系(實際上雙向 1- 1 雙方均可以維護關聯關系),使用 @OneToOne 來映射 1-1 關聯關系。添加必要注解,生成數據表。
3)保存操作:
4)查詢操作:
5.映射雙向多對多的關聯關系
1)實體:Item 和 Category ,一個類別有多個商品,一個商品對應多個類別。雙方都包含對方的 Set 集合。創建實體類,添加對應的注解,生成數據表。
2)保存操作:
3)查詢操作:
經過了前兩篇的詳細介紹,終于迎來了 JPA 的終結篇,LZ認為如果僅僅了解了 JPA 的話,大家可能感覺與 Hibernate 幾乎差不多,沒有什么亮點,但是等大家了解了 SpringData 后,JPA 與 SpringData 相結合,便會發揮出它巨大的優勢,極大的簡化了我們操作數據庫的步驟,使我們的代碼具有很強的可維護性,LZ隨后的博客也將繼續介紹。
六、JPA 的二級緩存
1. 大家對一級緩存比較熟悉,即若查詢一條同樣的記錄,因為一級緩存的存在只發送一條 SQL 語句。那么 JPA 的二級緩存又體現在哪呢?LZ給大家解釋為:查詢一條同樣的記錄,在第一次查詢后關閉 EntityManager、提交事務后,再重新獲取 EntityManager 并開啟事務再查詢同樣的記錄,因為有二級緩存的存在也會只發送一條記錄。如下:
1 //測試 JPA 的二級緩存 2 @Test 3 public void testSecondLevelCache() { 4 Customer customer1 = entityManager.find(Customer.class, 1); 5 6 transaction.commit(); 7 entityManager.close(); 8 9 entityManager = entityManagerFactory.createEntityManager(); 10 transaction = entityManager.getTransaction(); 11 transaction.begin(); 12 13 Customer customer2 = entityManager.find(Customer.class, 1); 14 }大家可以看到,4 行和 13 行的查詢語句一樣,6 行,7 行 提交了事務關閉了 EntityManager。若不進行二級緩存的配置,這樣的操作會發送兩次一模一樣的 SQL 語句,結果就不貼上了,大家可以試一試。若配置了二級緩存,同樣的操作便只會發送一條 SQL ,這樣可以減小服務器的壓力,減少訪問數據庫的次數。那么如何來配置二級緩存呢?
2. 如何配置二級緩存:
1)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="jpa-1" transaction-type="RESOURCE_LOCAL"><!-- 配置使用什么 ORM 產品來作為 JPA 的實現 1. 實際上配置的是 javax.persistence.spi.PersistenceProvider 接口的實現類 2. 若 JPA 項目中只有一個 JPA 的實現產品,則可以不配置該節點 --><provider>org.hibernate.ejb.HibernatePersistence</provider><!-- 添加持久化類 --><class>com.software.jpa.helloworld.Customer</class><class>com.software.jpa.helloworld.Order</class><class>com.software.jpa.helloworld.Manager</class><class>com.software.jpa.helloworld.Department</class><class>com.software.jpa.helloworld.Category</class><class>com.software.jpa.helloworld.Item</class><!-- 配置二級緩存的策略 ALL:所有的實體類都被緩存NONE:所有的實體類都不被緩存. ENABLE_SELECTIVE:標識 @Cacheable(true) 注解的實體類將被緩存 DISABLE_SELECTIVE:緩存除標識 @Cacheable(false) 以外的所有實體類UNSPECIFIED:默認值,JPA 產品默認值將被使用 --> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode><properties><!-- 連接數據庫的基本信息 --><!-- 在 Connection 選項中配置后會自動生成如下信息 --><property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /><property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" /><property name="javax.persistence.jdbc.user" value="root" /><property name="javax.persistence.jdbc.password" value="qiqingqing" /><!-- 配置 JPA 實現產品的基本屬性,即配置 Hibernate 的基本屬性 --><property name="hibernate.format_sql" value="true" /><property name="hibernate.show_sql" value="true" /><property name="hibernate.hbm2ddl.auto" value="update" /><!-- 二級緩存相關 --><property name="hibernate.cache.use_second_level_cache"value="true" /><property name="hibernate.cache.region.factory_class"value="org.hibernate.cache.ehcache.EhCacheRegionFactory" /><property name="hibernate.cache.use_query_cache" value="true" /></properties></persistence-unit> </persistence>2)導入 ehcache 的 jar 包和配置文件 ehcache.xml?
jar 包:
配置文件:對二級緩存參數的配置
<ehcache><!-- Sets the path to the directory where cache .data files are created.If the path is a Java System Property it is replaced byits value in the running VM.The following properties are translated:user.home - User's home directoryuser.dir - User's current working directoryjava.io.tmpdir - Default temp file path --><diskStore path="java.io.tmpdir"/><!--Default Cache configuration. These will applied to caches programmatically created throughthe CacheManager.The following attributes are required for defaultCache:maxInMemory - Sets the maximum number of objects that will be created in memoryeternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the elementis never expired.timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only usedif the element is not eternal. Idle time is now - last accessed timetimeToLiveSeconds - Sets the time to live for an element before it expires. Is only usedif the element is not eternal. TTL is now - creation timeoverflowToDisk - Sets whether elements can overflow to disk when the in-memory cachehas reached the maxInMemory limit.--><defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true"/><!--Predefined caches. Add your cache configuration settings here.If you do not have a configuration for your cache a WARNING will be issued when theCacheManager startsThe following attributes are required for defaultCache:name - Sets the name of the cache. This is used to identify the cache. It must be unique.maxInMemory - Sets the maximum number of objects that will be created in memoryeternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the elementis never expired.timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only usedif the element is not eternal. Idle time is now - last accessed timetimeToLiveSeconds - Sets the time to live for an element before it expires. Is only usedif the element is not eternal. TTL is now - creation timeoverflowToDisk - Sets whether elements can overflow to disk when the in-memory cachehas reached the maxInMemory limit.--><!-- Sample cache named sampleCache1This cache contains a maximum in memory of 10000 elements, and will expirean element if it is idle for more than 5 minutes and lives for more than10 minutes.If there are more than 10000 elements it will overflow to thedisk cache, which in this configuration will go to wherever java.io.tmp isdefined on your system. On a standard Linux system this will be /tmp"--><cache name="sampleCache1"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true"/><!-- Sample cache named sampleCache2This cache contains 1000 elements. Elements will always be held in memory.They are not expired. --><cache name="sampleCache2"maxElementsInMemory="1000"eternal="true"timeToIdleSeconds="0"timeToLiveSeconds="0"overflowToDisk="false"/> --><!-- Place configuration for your caches following --></ehcache>3)給需要緩存的類添加 @Cacheable(true) 注解,有前面的代碼可知,樓主獲取的是 Customer 對象
二級緩存就給大家介紹到這里。
七、JPQL
1.什么是 JPQL:JPQL語言,即 Java Persistence Query Language 的簡稱。
2.然后來看一個 JPQL 的 Helloworld:
1 //JPQL 的 HelloWorld 2 @Test 3 public void testHelloJPQL() { 4 String jpql = "FROM Customer c WHERE c.age > ?";5 Query query = entityManager.createQuery(jpql); 6 7 //占位符的索引是從 1 開始 8 query.setParameter(1, 21); 9 10 List<Customer> lists = query.getResultList(); 11 System.out.println(lists.size()); 12 13 }乍一看,大家可能感覺 JPQL 像極了 Hibernate 的 HQL 查詢,沒錯,這兩種查詢的相似度極高。需要注意的是,使用 Query 的 setParameter() 的方法填占位符是,索引是從 1
開始的。
3. 查詢部分屬性:
1 @Test 2 public void testPartlyProperties() { 3 String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?"; 4 Query query = entityManager.createQuery(jpql); 5 6 query.setParameter(1, 1); 7 8 List lists = query.getResultList(); 9 System.out.println(lists); 10 }默認情況下若只查詢部分屬性,則將返回 Object[] 類型的結果或 Object[] 類型的 List,可以在實體類中創建對應的構造器,然后在 jpql 中利用對應的構造器返回實體類對應的對象,這樣得到的結果可以很令人滿意,也很方便我們來操作。
?4.命名查詢 NamedQuery:
1)在需要查詢的對象類上添加 @NamedQuery 注解:
2)創建測試方法:
5. 本地 SQL 查詢使用 EntityManager 的 createNativeQuery() 方法:
1 //本地 SQL 查詢 2 @Test 3 public void testNativeQuery() { 4 String sql = "SELECT age FROM jpa_customer WHERE id = ?"; 5 Query query = entityManager.createNativeQuery(sql).setParameter(1, 1); 6 Object result = query.getSingleResult(); 7 System.out.println(result); 8 }6. 可以使用 Order By 字句:
1 // jpql 中的 Order By 子句 2 @Test 3 public void testOrderBy() { 4 String jpql = "FROM Customer c WHERE c.age > ? ORDER BY c.age DESC"; 5 Query query = entityManager.createQuery(jpql); 6 7 //占位符的索引是從 1 開始 8 query.setParameter(1, 21); 9 10 List<Customer> lists = query.getResultList(); 11 System.out.println(lists.size()); 12 }7.還可以使用 Group By 子句:
1 //查詢 order 數量大于 2 的那些 Customer 2 @Test 3 public void testGroupBy() { 4 String jpql = "SELECT o.customer FROM Order o GROUP BY o.customer HAVING count(o.id) >= 2"; 5 List<Customer> lists = entityManager.createQuery(jpql).getResultList(); 6 System.out.println(lists); 7 }8.也可以使用子查詢
1 //子查詢 2 @Test 3 public void testSubQuery() { 4 //查詢所有 Customer 的 lastName 為 YY 的 Order 5 String jpql = "SELECT o FROM Order o" 6 + " WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)"; 7 List<Order> orders = entityManager.createQuery(jpql).setParameter(1, "YY").getResultList(); 8 System.out.println(orders.size()); 9 }八、Spring 整合 JPA
JPA 的一些 API 也可以放到 Spring 的 IOC 容器中,交由 Spring 容器管理,那么如何用 Spring 來整合 JPA 呢?
1.新建 JPA 工程,導入所需的 jar包(Hibernate、JPA、c3p0、Spring、MySQL 驅動)
2.類路徑下創建 db.properties 數據庫配置文件,配置數據庫的鏈接信息(LZ 在這只配置了必須屬性)
1 jdbc.user=root 2 jdbc.password=qiqingqing 3 jdbc.driverClass=com.mysql.jdbc.Driver 4 jdbc.jdbcUrl=jdbc:mysql://localhost:3306/jpa3.類路徑下創建 Spring 的配置文件 applicationContext.xml,配置自動掃描的包,將 db.propertiest 文件導入,并配置 c3p0 數據源
1 <!-- 配置自動掃描的包 --> 2 <context:component-scan base-package="com.software.jpa"></context:component-scan> 3 4 <!-- 配置數據源 --> 5 <context:property-placeholder location="classpath:db.properties"/> 6 7 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 8 <property name="user" value="${jdbc.user}"></property> 9 <property name="password" value="${jdbc.password}"></property> 10 <property name="driverClass" value="${jdbc.driverClass}"></property> 11 <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> 12 </bean>4.在 applicationContext.xml 中配置 JPA 的 EntityManagerFactory
1 <!-- 配置 EntityManagerFactory --> 2 <bean id="entityManagerFactory" 3 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 4 <!-- 配置數據源 --> 5 <property name="dataSource" ref="dataSource"></property> 6 <!-- 配置 JPA 提供商的適配器,可以通過內部 bean 的方式來配置 --> 7 <property name="jpaVendorAdapter"> 8 <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> 9 </property> 10 <!-- 配置實體類所在的包 --> 11 <property name="packagesToScan" value="com.software.jpa.spring.entities"></property> 12 <!-- 配置 JPA 的基本屬性,比如,JPA 實現產品的屬性 --> 13 <property name="jpaProperties"> 14 <props> 15 <prop key="hibernate.show_sql">true</prop> 16 <prop key="hibernate.format_sql">true</prop> 17 <prop key="hibernate.hbm2ddl.auto">update</prop> 18 </props> 19 </property> 20 </bean>5.配置 JPA 使用的事務管理器及配置支持基于注解的事務配置
1 <!-- 配置 JPA 使用的事務管理器 --> 2 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 3 <property name="entityManagerFactory" ref="entityManagerFactory"></property> 4 </bean> 5 6 <!-- 配置支持基于注解的事務配置 --> 7 <tx:annotation-driven transaction-manager="transactionManager"/>6.為了測試創建實體類 Person,添加相應的 JPA 注解,生成對應的數據表
1 package com.software.jpa.spring.entities; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.GeneratedValue; 6 import javax.persistence.Id; 7 import javax.persistence.Table; 8 9 @Table(name="JPA_PERSONS") 10 @Entity 11 public class Person { 12 13 private Integer id; 14 15 private String lastName; 16 17 private String email; 18 19 private Integer age; 20 21 @GeneratedValue 22 @Id 23 public Integer getId() { 24 return id; 25 } 26 27 public void setId(Integer id) { 28 this.id = id; 29 } 30 31 @Column(name="LAST_NAME") 32 public String getLastName() { 33 return lastName; 34 } 35 36 public void setLastName(String lastName) { 37 this.lastName = lastName; 38 } 39 40 public String getEmail() { 41 return email; 42 } 43 44 public void setEmail(String email) { 45 this.email = email; 46 } 47 48 public Integer getAge() { 49 return age; 50 } 51 52 public void setAge(Integer age) { 53 this.age = age; 54 } 55 56 }7.創建 PersonDao 使用 @PersistenceContext 獲取和當前事務關聯的 EntityManager 對象
1 package com.software.jpa.dao; 2 3 import javax.persistence.EntityManager; 4 import javax.persistence.PersistenceContext; 5 6 import org.springframework.stereotype.Repository; 7 8 import com.software.jpa.spring.entities.Person; 9 10 @Repository 11 public class PersonDao { 12 13 //使用 @PersistenceContext 獲取和當前事務關聯的 EntityManager 對象 14 @PersistenceContext 15 private EntityManager entityManager; 16 17 public void save(Person p) { 18 entityManager.persist(p); 19 } 20 21 }8.創建 PersonService ,模擬事務操作,20 行 LZ 設計了一個算數異常,若整合成功,因為添加了事務操作,所以 18 行和 22 行的兩條記錄都沒有插入進數據庫。
1 package com.software.jpa.service; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import org.springframework.transaction.annotation.Transactional; 6 7 import com.software.jpa.dao.PersonDao; 8 import com.software.jpa.spring.entities.Person; 9 10 @Service 11 public class PersonService { 12 13 @Autowired 14 private PersonDao dao; 15 16 @Transactional 17 public void save(Person p1, Person p2) { 18 dao.save(p1); 19 20 int i = 10/0; 21 22 dao.save(p2); 23 } 24 25 }9.創建測試方法,并執行
1 package com.software.jpa.spring; 2 3 import javax.sql.DataSource; 4 5 import org.junit.Test; 6 import org.springframework.context.ApplicationContext; 7 import org.springframework.context.support.ClassPathXmlApplicationContext; 8 9 import com.software.jpa.service.PersonService; 10 import com.software.jpa.spring.entities.Person; 11 12 public class JPATest { 13 14 private ApplicationContext ctx = null; 15 16 private PersonService personService = null; 17 18 { 19 ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 20 21 personService = ctx.getBean(PersonService.class); 22 } 23 24 @Test 25 public void testSave() { 26 Person p1 = new Person(); 27 p1.setAge(11); 28 p1.setEmail("aa@163.com"); 29 p1.setLastName("AA"); 30 31 Person p2 = new Person(); 32 p2.setAge(12); 33 p2.setEmail("bb@163.com"); 34 p2.setLastName("BB"); 35 36 System.out.println(personService.getClass().getName()); 37 personService.save(p1, p2); 38 } 39 40 @Test 41 public void testDataSourct() throws Exception { 42 DataSource dataSource = ctx.getBean(DataSource.class); 43 System.out.println(dataSource.getConnection()); 44 } 45 46 }JPA 的知識介紹到此就完全結束了,LZ 整理了不短的時間,希望可以幫助到需要的朋友。
總結
- 上一篇: 〈原创〉诗如文学之筋骨,琴乃乐中之天籁
- 下一篇: 上手快、门槛低,微信小程序成“就业大户”