JPA是Java Persistence API的簡稱,中文名Java持久層API。是Java EE5.0平臺中Sun爲了統一持久層ORM框架而制定的一套標準,注意是一套標準,而不是具體實現,不可能單獨存在,須要有其實現產品。Sun挺擅長制定標準的,例如JDBC就是爲了統一鏈接數據庫而制定的標準,而咱們使用的數據庫驅動就是其實現產品。JPA的實現產品有HIbernate,TopLink,OpenJPA等等。值得說一下的是Hibernate的做者直接參與了JPA的制定,因此JPA中的一些東西能夠與Hibernate相對比。java
JPA特色:mysql
咱們使用Hibernate做爲JPA的實現產品,須要導入的jar包有:spring
其實也就是Hibernate的required包下的jar,重點在於有Hibernate-jpa-api這個jar的存在sql
注意:不要忘了咱們的數據庫驅動jar數據庫
Persistence.xml文件的編寫api
JPA規範要求在內路徑下META-INF目錄下放置persistence.xml文件,文件名稱是固定的。這個文件在於spring整合以後就能夠取消了。spring-mvc
一個簡單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> --> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <!-- 添加持久化類 (推薦配置)--> <class>cn.lynu.model.User</class> <properties> <!-- 鏈接數據庫的基本信息 --> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="root"/> <!-- 配置 JPA 實現產品的基本屬性. 配置 hibernate 的基本屬性 --> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence>
name:用於定義持久單元名稱,必須。服務器
transaction-type:指定JPA的事務處理策略,RESOURCE_LOCAL 是默認值,數據庫級別的事務,只支持一種數據庫,不支持分佈式事務。若是要支持分佈式事務,要使用JTA策略:transaction-type:「JTA」session
咱們可使用註解來映射源數據了,而不用再編寫如Hibernate中的*.hbm.xml文件了,Hibernate中其實就可使用JPA的註解來映射數據,學習了JPA以後,經過使用JPA來增長Dao層的通用性。
@Entity
標註用於實體類聲明語句以前,指出該Java 類爲實體類,將映射到指定的數據庫表。如聲明一個實體類 Customer,它將映射到數據庫中的 customer 表上。
@Table
當實體類與其映射的數據庫表名不一樣名時須要使用 @Table 標註說明,該標註與 @Entity 標註並列使用。
經常使用選項是 name,用於指明數據庫的表名。
@Id
標註用於聲明一個實體類的屬性映射爲數據庫的主鍵列,可使用在屬性上,也可使用在getter方法上
@GeneratedValue
用於標註主鍵的生成策略,經過 strategy 屬性指定。默認狀況下,JPA 自動選擇一個最適合底層數據庫的主鍵生成策略:
SqlServer 對應 identity,MySQL 對應 auto increment
在 javax.persistence.GenerationType 中定義瞭如下幾種可供選擇的策略(能夠看到JPA沒有Hibernate的主鍵生成策略多,由於其只是作一個抽象):
在個人Mysql使用@GeneratedValue爲默認值時,會識別爲序列的方式,因此我都是本身指定爲IDENTITY方式。
Sequence策略
在Oracle中沒有主鍵自動增加,因此都是使用序列替代這一功能的,GeneratedValue如何經過序列產生主鍵呢?
首先咱們的Oracle數據庫中須要建立一個序列
將GeneratedValue的strategy設置爲GenerationType.SEQUENCE
還須要使用一個@SequenceGenerator 註解來配置一些序列的信息
@GeneratedValue(generator="seq",strategy=GenerationType.SEQUENCE)
@SequenceGenerator(name="seq",allocationSize=1,sequenceName="master_seq")
@GeneratedValue的generator的值和@SequenceGenerator的name值須要保持一致;allocationSize 代表每次增加1,其實這個屬性在數據庫中建立序列的時候能夠指定步長,因此能夠不寫;sequenceName須要指明在數據庫中咱們建立的序列名。
TABLE策略
將當前主鍵的值單獨保存到一個數據庫的表中(存主鍵的表),主鍵的值每次都是從指定的表中查詢來得到 這種方法生成主鍵的策略能夠適用於任何數據庫,沒必要擔憂不一樣數據庫不兼容形成的問題。
就不是隻使用@GeneratedValue註解了,而是使用@TableGenerator和@GeneratedValue配合:
name :表示該主鍵生成策略的名稱,它被引用在@GeneratedValue中設置的generator 值中
table :表示表生成策略所持久化的表名
pkColumnName :表示在持久化表中,該主鍵生成策略所對應鍵值的名稱(存主鍵的表中表示id的字段名)
pkColumnValue :表示在持久化表中,該生成策略所對應的主鍵(也就是該實體類對應的表的主鍵字段名)
valueColumnName :表示在持久化表中,該主鍵當前所生成的值,它的值將會隨着每次建立累加(存主鍵的表中表示值的字段名)
因此生成的存主鍵的表結構爲:
pk_name就是在pkColumnName 中設置的,對應須要使用table方式生成主鍵的表的id
pk_value就是在valueColumnName 中設置的,其值並不是是在設置主鍵初始值
@Column
當實體的屬性與其映射的數據庫表的列不一樣名時須要使用@Column 標註說明,該屬性一般置於實體的屬性聲明語句以前,還可與 @Id 標註一塊兒使用。
@Column 標註的經常使用屬性是 name,用於設置映射數據庫表的列名。此外,該標註還包含其它多個屬性,如:unique 、nullable、length 等。
@Transient
表示該屬性並不是一個到數據庫表的字段的映射,ORM框架將忽略該屬性. 若是一個屬性並不是數據庫表的字段映射,就務必將其標示爲@Transient,不然,若是咱們使用了自動建表,就會將不須要的屬性映射爲表的字段。
@Temporal
在覈心的 Java API 中並無定義 Date 類型的精度(temporal precision). 而在數據庫中,表示 Date 類型的數據有 DATE, TIME, 和 TIMESTAMP 三種精度(即單純的日期,時間,或者二者兼備). 在進行屬性映射時可以使用@Temporal註解來調整精度。
默認將時間類型映射爲時間戳類型,也就是有日期有時間的,若是咱們只須要日期,如生日字段,就須要使用Date類型,只須要時間,就使用Time類型
JPA也是須要先建立一個EntityManagerFactory(相似於Hibernate中的SessionFactory),經過這個工廠再來生成EntityManager(相似於Hibernate的Session)。
EntityManagerFactory是經過Persistence類的靜態方法 createEntityManagerFactory生成,方法的參數指定JPA的持久單元名稱,也就是咱們在persistence.xml文件中寫的name名。
EntityManager再經過EntityManagerFactory的createEntityManager方法建立。
開啓事務使用的是:entityManager.getTransaction();獲得一個EntityTransaction 對象,再經過這個對象的begin方法就能夠開啓事務了
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(); }
注意:咱們使用的JAP的類和註解都是使用的javax.persistence包下的,不要導錯了
find (Class<T> entityClass,Object primaryKey):返回指定的 OID 對應的實體類對象,若是這個實體存在於緩存中,則返回一個被緩存的對象;不然會建立一個新的 Entity, 並加載數據庫中相關信息;若 OID 不存在於數據庫中,則返回一個 null。第一個參數爲被查詢的實體類類型,第二個參數爲待查找實體的主鍵值。相似於Hibernate中的get方法。
getReference (Class<T> entityClass,Object primaryKey):與find()方法相似,不一樣的是:若是緩存中不存在指定的 Entity, EntityManager 會建立一個 Entity 類的代理,可是不會當即加載數據庫中的信息,只有第一次真正使用此 Entity 的屬性才加載,因此若是此 OID 在數據庫不存在,getReference() 不會返回 null 值, 而是拋出EntityNotFoundException,相似於Hibernate中的load方法。
//相似於hibernate 中 session 的 get方法 //調用find方法時就發sql @Test public void testFind() { User user = entityManager.find(User.class, 1); System.out.println("-------------------------"); System.out.println(user); } //相似於Hibernate 中 session 的 load方法 //使用的時候才發sql @Test public void testGetReference() { User user = entityManager.getReference(User.class, 1); System.out.println("-------------------------"); System.out.println(user); }
persist (Object entity):用於將新建立的 Entity 歸入到 EntityManager 的管理。該方法執行後,傳入 persist() 方法的 Entity 對象轉換成持久化狀態。
若是傳入 persist() 方法的 Entity 對象已經處於持久化狀態,則 persist() 方法什麼都不作。
若是對遊離狀態的實體執行 persist() 操做,可能會在 persist() 方法拋出 EntityExistException(也有多是在flush或事務提交後拋出),這一點也就是說persist方法不能保存遊離態的實體,不一樣於Hibernate的是,HIbernate能夠保存遊離態的對象。
//添加方法相似於hibernate 的 session 中的 save方法 //不一樣點:可是不能添加存在id屬性的實例(遊離態), hibernate能夠 @Test public void testPersist() { User user=new User(); user.setUserName("張三110"); user.setEmail("110@qq.com"); user.setBirth(new Date()); user.setCreateTime(new Date()); entityManager.persist(user); }
remove (Object entity):刪除實例。若是實例是被管理的,即與數據庫實體記錄關聯,則同時會刪除關聯的數據庫記錄。
與HIbernate不一樣點在於JPA是不能刪除在遊離態的對象,HIbernate能夠經過刪除遊離態的對象來影響到數據庫中對應的數據,可是不推薦這樣作。
//相似於Hibernate 中session 的delete方法 //不一樣點:remove不能夠刪除遊離態的對象, //hibernate能夠刪除遊離態的對象,而且會影響到數據庫中的數據 //hibernate能夠操做(插入或刪除)遊離態的對象,而JPA不能夠 @Test public void testRemove() { User user = entityManager.find(User.class, 1); entityManager.remove(user); }
merge (T entity):merge() 用於處理 Entity 的同步。即數據庫的插入和更新操做,相似於HIbernate中的SaveOrUpdate方法。
/** * 總的來講: 相似於 hibernate Session 的 saveOrUpdate 方法. */ //1. 若傳入的是一個臨時對象(沒有id) //會建立一個新的對象, 把臨時對象的屬性複製到新的對象中, 而後對新的對象執行持久化操做(insert). 因此 //新的對象中有 id, 但之前的臨時對象中沒有 id. @Test public void testMerge1(){ User user=new User(); user.setUserName("張三123"); user.setEmail("123@qq.com"); user.setBirth(new Date()); user.setCreateTime(new Date()); User user2 = entityManager.merge(user); // 返回持久化對象的引用 System.out.println(user.getId()); System.out.println(user2.getId()); } //若傳入的是一個遊離對象, 即傳入的對象有 OID. //1. 若在 EntityManager 緩存中沒有該對象 //2. 若在數據庫中也沒有對應的記錄(先進行select查詢) //3. JPA 會建立一個新的對象, 而後把當前遊離對象的屬性複製到新建立的對象中 //4. 對新建立的對象執行 insert 操做. (沒查到對應id的對象) @Test public void testMerge2(){ User user=new User(); user.setUserName("張三222"); user.setEmail("222@qq.com"); user.setBirth(new Date()); user.setCreateTime(new Date()); user.setId(100); User user2 = entityManager.merge(user); System.out.println(user.getId()); System.out.println(user2.getId()); } //若傳入的是一個遊離對象, 即傳入的對象有 OID. //1. 若在 EntityManager 緩存中沒有該對象 //2. 若在數據庫中有對應的記錄(先進行select查詢) //3. JPA 會查詢對應的記錄, 而後返回該記錄對一個的對象, 再而後會把遊離對象的屬性複製到查詢到的對象中. //4. 對查詢到的對象執行 update 操做. (查到對應id的對象) @Test public void testMerge3(){ User user=new User(); user.setUserName("張三333"); user.setEmail("333@qq.com"); user.setBirth(new Date()); user.setCreateTime(new Date()); user.setId(2); User user2 = entityManager.merge(user); System.out.println(user==user2); //false 遊離態對象和返回的持久化對象不一致 } //若傳入的是一個遊離對象, 即傳入的對象有 OID. //1. 若在 EntityManager 緩存中有對應的對象(使用find或者是getReference獲得) //2. JPA 會把遊離對象的屬性複製到查詢到EntityManager 緩存中的對象中. //3. EntityManager 緩存中的對象執行 UPDATE. @Test public void testMerge4(){ User user=new User(); user.setUserName("張三444"); user.setEmail("444@qq.com"); user.setBirth(new Date()); user.setCreateTime(new Date()); user.setId(2); User user2 = entityManager.find(User.class, 2); //緩存對象 User user3 = entityManager.merge(user); //返回的持久化對象 System.out.println(user==user2); //false 遊離態對象和緩存中的對象不一致 System.out.println(user==user3); //false 遊離態對象和持久化對象不一致 System.out.println(user2==user3); //true user2和user3是同一個對象(緩存中已經存在) }
雙向一對多及多對一映射
雙向一對多關係中,必須存在一個關係維護端,在 JPA 規範中,要求 many 的一方做爲關係的維護端(owner side),能夠少發update語句。 one 的一方做爲被維護端(inverse side)。 能夠在 one 方指定 @OneToMany 註釋並設置 mappedBy 屬性(相似於HIbernate中的inverse屬性),以指定它是這一關聯中的被維護端,many 爲維護端。 在 many 方指定 @ManyToOne 註釋,並使用 @JoinColumn 指定外鍵名稱。
Order類(多):
//多的一方 @ManyToOne @JoinColumn(name="user_id") //外鍵名 public User getUser() { return user; }
User類(一):
//一的一方 @OneToMany(mappedBy="user") //使用mappedBy 放棄維護關係(通常都讓一的一方放棄,多的一方維護關係) public Set<Order> getOrders() { return orders; }
保存一對多(沒有設置級聯,建議先保存一再保存多,能夠減小update語句)
//若是保存多對一關係時,若是沒有設置級聯, //建議先保存一的一方,再保存多的一方(與保存的順序有關),就不會出現多餘的update //設置級聯爲CascadeType.PERSIST(級聯保存) 就能夠經過保存Order來級聯保存User @Test public void testPersistOrder() { Order order1=new Order(); order1.setOrderName("FF1"); User user=new User(); user.setUserName("李四"); user.setBirth(new Date()); user.setCreateTime(new Date()); user.setEmail("666@qq.com"); order1.setUser(user); entityManager.persist(user); entityManager.persist(order1); }
咱們也能夠經過設置級聯,來保存一方的時候同時保存另外一方:
@ManyToOne(cascade= {CascadeType.PERSIST}) @JoinColumn(name="user_id") //外鍵名 public User getUser() { return user; }
@Test public void testPersistOrder2() { Order order1=new Order(); order1.setOrderName("GG1"); User user=new User(); user.setUserName("李四光"); user.setBirth(new Date()); user.setCreateTime(new Date()); user.setEmail("666@qq.com"); order1.setUser(user); entityManager.persist(order1); }
刪除(沒有設置級聯刪除)
//刪除時 刪除關係維護端沒有問題 刪除被維護端報錯(存在外鍵約束) //若是想刪除,就不要設置放棄維護關係(這裏是不要設置mappedBy) /** @OneToMany @JoinColumn(name="user_id") */ @Test public void testRemoveOrder() { //Order order = entityManager.find(Order.class, 3); //entityManager.remove(order); User user = entityManager.find(User.class, 16); entityManager.remove(user); }
由於咱們在一的一方設置了mappedBy放棄維護關係,因此刪除一的一方會出錯(存在外鍵約束),解決辦法我知道的有:
還有沒什麼辦法能夠很好的解決這個放棄維護帶來的刪除問題?
其實仔細一想這個異常是存在必定道理的,由於咱們都知道建表或插入數據的時候是先主表,後從表;而刪除的時候想刪除從表,再刪除主表。若是咱們不先處理多的一方,而直接刪除一的一方,數據庫是不容許的,由於外鍵字段的值必須在主表中存在,這個異常也在提示咱們這樣的操做存在問題
一對多雙向關聯查詢
這裏有一個規律:凡是以many結尾的都默認使用懶加載,而以one結尾的默認使用當即加載。如OneToMany 默認使用的就是延遲加載,而ManyToOne,OneToOne默認使用的是當即加載
咱們能夠在ManyToOne(以one結尾,默認使用當即加載)設置fetch屬性:fetch=FetchType.LAZY ,將其設置爲懶加載
//查詢關係維護端(主控方)會發一條左外鏈接(left outer join)的查詢sql //設置了fetch 爲lazy 延遲加載就不會鏈接查詢了 @Test public void testFindOrder() { Order order = entityManager.find(Order.class, 3); System.out.println(order.getUser()); }
雙向多對多關聯關係
在雙向多對多關係中,咱們必須指定一個關係維護端(owner side),能夠經過 @ManyToMany 註釋中指定 mappedBy 屬性來標識其爲關係維護端。
Item類:
@ManyToMany @JoinTable(name="item_category", joinColumns= {@JoinColumn(name="item_id")}, inverseJoinColumns= {@JoinColumn(name="categroy_id")}) public Set<Category> getCategories() { return categories; }
@JoinTable 設置中間表
name="中間表名稱",
joinColumns={@joinColumn(name="本類的外鍵")}
inversejoinColumns={@JoinColumn(name="對方類的外鍵") }
Category類:
@ManyToMany(mappedBy="categories") //根據需求讓一方放棄維護 public Set<Item> getItems() { return items; }
多對多刪除
//多對多的刪除 @Test public void testManyToManyRemove(){ Item item = entityManager.find(Item.class, 1); entityManager.remove(item); }
沒有設置級聯的時候,刪除時先根據刪除方的id去中間表查詢,查詢到以後,先刪除中間表,而後刪除刪除方的記錄
雙向一對一映射
基於外鍵的 1-1 關聯關係:在雙向的一對一關聯中,須要在關係被維護端(inverse side)中的 @OneToOne 註釋中指定 mappedBy(沒有外鍵的一端,相似於主表),以指定是這一關聯中的被維護端。同時須要在關係維護端(owner side 有外鍵存在的一端,相似於從表)創建外鍵列指向關係被維護端的主鍵列。
Dept和Manager是一對一關係,外鍵存在於Dept表中,因此然Manager放棄維護
Manager類:
@OneToOne(mappedBy="mgr") //放棄維護 public Dept getDept() { return dept; }
Dept類:
@OneToOne(fetch=FetchType.LAZY) //一對一(維護端) @JoinColumn(name="mgr_id",unique=true) public Manager getMgr() { return mgr; }
這裏設置了一對一延遲加載,在使用到被維護端的時候,再去查詢,而不是直接發一條left outer join 左外鏈接
//1.默認狀況下, 若獲取維護關聯關係的一方, 則會經過左外鏈接獲取其關聯的對象. //但能夠經過 @OntToOne 的 fetch 屬性(設置延遲加載)來修改加載策略. //必須設置在維護端(主控方)的fetch屬性纔有效,設置在被維護端沒用 @Test public void testOneToOneFind(){ Dept dept = entityManager.find(Dept.class, 1); System.out.println(dept.getDeptName()); System.out.println(dept.getMgr().getClass().getName()); }
在Hibernate中一級緩存是session級別的緩存,是默認帶的且開起的,而二級緩存是sessionFactory級別的緩存,是跨session的。同理,JPA中一級緩存是EntityManager級的緩存,而二級緩存是EntityManagerFactory級別的緩存。
使用二級緩存須要配置其實現產品,這裏使用的是ehcache。
先在persistence.xml文件中配置:
<!-- 配置二級緩存的策略 ALL:全部的實體類都被緩存 NONE:全部的實體類都不被緩存. ENABLE_SELECTIVE:標識 @Cacheable(true) 註解的實體類將被緩存 DISABLE_SELECTIVE:緩存除標識 @Cacheable(false) 之外的全部實體類 UNSPECIFIED:默認值,JPA 產品默認值將被使用 --> <!--ENABLE_SELECTIVE策略 在須要二級緩存的實體類上使用@Cacheable(true) --> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> <!-- 二級緩存相關配置(使用的是hibernate二級緩存,緩存產品爲ehcache) --> <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"/>
這裏有個查詢緩存,等到JPQL以後再說.
<shared-cache-mode> 節點:若 JPA 實現支持二級緩存,該節點能夠配置在當前的持久化單元中是否啓用二級緩存,可配置以下值:
咱們設置的配置是ENABLE_SELECTIVE,因此必定要在須要二級緩存的類上使用 @Cacheable(true) 註解標識
咱們還須要在src下放入一個ehcache的配置文件:ehcache.xml
<ehcache> <!-- 指定一個目錄:當 EHCache 把數據寫到硬盤上時, 將把數據寫到這個目錄下. 如:<diskStore path="d:\\tempDirectory"/> --> <diskStore path="java.io.tmpdir"/> <!--默認的緩存配置--> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> <!-- 設定具體的命名緩存的數據過時策略。每一個命名緩存表明一個緩存區域 緩存區域(region):一個具備名稱的緩存塊,能夠給每個緩存塊設置不一樣的緩存策略。 若是沒有設置任何的緩存區域,則全部被緩存的對象,都將使用默認的緩存策略。即:<defaultCache.../> Hibernate 在不一樣的緩存區域保存不一樣的類/集合。 對於類而言,區域的名稱是類名。如:cn.lynu.entity.Dept 對於集合而言,區域的名稱是類名加屬性名。如cn.lynu.entity.Dept.emps --> <!-- name: 設置緩存的名字,它的取值爲類的全限定名或類的集合的名字 maxElementsInMemory: 設置基於內存的緩存中可存放的對象最大數目 eternal: 設置對象是否爲永久的, true表示永不過時, 此時將忽略timeToIdleSeconds 和 timeToLiveSeconds屬性; 默認值是false timeToIdleSeconds:設置對象空閒最長時間,以秒爲單位, 超過這個時間,對象過時。 當對象過時時,EHCache會把它從緩存中清除。若是此值爲0,表示對象能夠無限期地處於空閒狀態。 timeToLiveSeconds:設置對象生存最長時間,超過這個時間,對象過時。 若是此值爲0,表示對象能夠無限期地存在於緩存中. 該屬性值必須大於或等於 timeToIdleSeconds 屬性值 overflowToDisk:設置基於內存的緩存中的對象數目達到上限後,是否把溢出的對象寫到基於硬盤的緩存中 --> <cache name="sampleCache1" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /> <cache name="sampleCache2" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> </ehcache>
開始測試吧:
//開啓二級緩存 @Test public void testSecondLevelCache(){ User user1 = entityManager.find(User.class, 2); //提交併關閉事務 transaction.commit(); entityManager.close(); //開啓新事務 entityManager=entityManagerFactory.createEntityManager(); transaction=entityManager.getTransaction(); transaction.begin(); User user2 = entityManager.find(User.class, 2); }
這裏查詢以後提交併關閉事務和EntityManager,用一個新的EntityManager和新的事務進行操做,在沒有開二級緩存的時候,會發兩條相同的select語句,成功開啓二級緩存以後,就只有第一次的一條select了。
相似於HQL,JPQL也是面向對象的查詢語句,也能夠完成CRUD操做,可是寫法上有點不一樣,不一樣點在於查詢上,來看一條JPQL語句:
SELECT u from User u
再來看HQL的寫法:
from User
能夠看到JPQL的查詢須要將select關鍵字和須要獲得的字段寫出來(好在須要獲得的字段能夠是一個對象),運行這些語句的都是Query接口,只是要注意是不一樣的包下的。
Query接口的主要方法:
@Test public void testHelloJPQL(){ String jpql="SELECT u from User u"; Query query = entityManager.createQuery(jpql); List list = query.getResultList(); //對應Hibernate中Query接口的list()方法 System.out.println(list.size()); }
我還記得Hibernate的佔位符是從0開始的,而JDBC是從1開始的,老是記這些東西很麻煩,因此在JPA中能夠指定佔位符從幾開始:
@Test public void testHelloJPQL2(){ String jpql="select u from User u where id>?1"; Query query = entityManager.createQuery(jpql); //佔位符的索引是從 0 開始,能夠指定從幾開始,如?1 就是從1開始 query.setParameter(1, 3); List list = query.getResultList(); System.out.println(list.get(3)); }
還可使用名稱佔位:
@Test public void testHelloJPQL3(){ String jpql="select u from User u where id>:id"; Query query = entityManager.createQuery(jpql); //使用名稱佔位 query.setParameter("id", 3); List list = query.getResultList(); System.out.println(list.get(3)); }
Order by子句用於對查詢結果集進行排序。和SQL的用法相似,能夠用 「asc「 和 "desc「 指定升降序。若是不顯式註明,默認爲升序。
select o from Orders o order by o.id
select o from Orders o order by o.address.streetNumber desc
select o from Orders o order by o.customer asc, o.id desc
group by子句與聚合查詢
//分組查詢(查詢 order 數量等於 2 的那些 User) @Test public void testGroupBy(){ String jpql="select o.user from Order o group by o.user having count(o.id)=?1"; List list = entityManager.createQuery(jpql).setParameter(1, 2).getResultList(); System.out.println(list); }
經常使用的聚合函數主要有 AVG、SUM、COUNT、MAX、MIN 等,它們的含義與SQL相同。
Query query = entityManager.createQuery( "select max(o.id) from Orders o"); Object result = query.getSingleResult(); Long max = (Long)result;
JPQL 也支持和 SQL 中相似的關聯語法。如: left out join fetch, right out join fetch 。eft out join,如left out join fetch是以符合條件的表達式的左側爲主。
/** * JPQL 的關聯查詢(left outer join fetch)同 HQL 的關聯查詢. */ @Test public void testLeftOuterJoinFetch(){ String jpql="select u from User u left outer join fetch u.orders where u.id=?1"; List<User> list = entityManager.createQuery(jpql).setParameter(1, 16).getResultList(); System.out.println(list.get(0).getOrders()); }
左外的右邊使用的是User類中的orders屬性表示另外一張表,並且要加上fetch語句,纔是一個真正左外鏈接,要不會報sql異常
JPQL也支持子查詢,在 where 或 having 子句中能夠包含另外一個查詢。當子查詢返回多於 1 個結果集時,它常出如今 any、all、exists表達式中用於集合匹配查詢。它們的用法與SQL語句基本相同。
/** * JPQL子查詢 */ @Test public void testSubQuery(){ //查詢全部 User 的 userName 爲 趙六 的 Order String jpql="select o from Order o where o.user=(select u from User u where u.userName=?1)"; List list = entityManager.createQuery(jpql).setParameter(1, "趙六").getResultList(); System.out.println(list); }
JPQL提供瞭如下一些內建函數,包括字符串處理函數、算術函數和日期函數。如字符串處理函數:
//使用 jpql 內建的函數 @Test public void testJpqlFunction(){ String jpql="select upper(o.orderName) from Order o"; List list = entityManager.createQuery(jpql).getResultList(); System.out.println(list); }
算術函數主要有 abs、mod、sqrt、size 等。Size 用於求集合的元素個數。
日期函數主要爲三個,即 current_date、current_time、current_timestamp,它們不須要參數,返回服務器上的當前日期、時間和時戳。
剛纔說了一個查詢緩存的問題,是由於使用Query接口查詢的結果並不會放入一級緩存中,由於其未與EntityManager關聯,HIbernate中也如此,因此須要配置那個查詢緩存,注意:查詢緩存須要依賴於二級緩存。
須要在須要查詢緩存的時候使用: query.setHint(QueryHints.HINT_CACHEABLE, true);
//使用 hibernate 的查詢緩存.(query接口查詢(hql)的結果不會放到緩存中,須要配置查詢緩存) @Test public void testQueryCache(){ String jpql="SELECT u from User u"; Query query = entityManager.createQuery(jpql); //查詢緩存(org.hibernate.jpa.QueryHints;) query.setHint(QueryHints.HINT_CACHEABLE, true); List list = query.getResultList(); System.out.println(list.size()); //沒有配置的時候屢次相同的sql查詢會發多條相同的sql //配置查詢緩存以後,相同查詢就發一條sql //(配置文件中要配置hibernate.cache.use_query_cache) //query = entityManager.createQuery(jpql); //查詢緩存(只要給query使用jpql就須要setHint) //query.setHint(QueryHints.HINT_CACHEABLE, true); list = query.getResultList(); System.out.println(list.size()); }
Spring整合JPA以後,就不須要persistence.xml文件了,須要在applicationContext.xml文件中配置EntityManagerFactory和JPA的事務:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置 C3P0 數據源 --> <context:property-placeholder location="classpath:jdbc.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 提供商的適配器. 能夠經過內部 bean 的方式來配置 --> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> </property> <!-- 配置實體類所在的包 --> <property name="packagesToScan" value="cn.lynu.model"></property> <!-- 配置 JPA 的基本屬性. 例如 JPA 實現產品的屬性 --> <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 使用的事務管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <!-- 配置支持基於註解是事務配置 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 配置自動掃描的包(組件掃描) --> <context:component-scan base-package="cn.lynu"></context:component-scan> </beans>