JPA是Java Persistence API的簡稱,中文名Java持久層API。是Java EE5.0平台中Sun為了統一持久層ORM框架而制定的一套標准,注意是一套標准,而不是具體實現,不可能單獨存在,需要有其實現產品。Sun挺擅長制定標准的,例如JDBC就是為了統一連接數據庫而制定的標准,而我們使用的數據庫驅動就是其實現產品。JPA的實現產品有HIbernate,TopLink,OpenJPA等等。值得說一下的是Hibernate的作者直接參與了JPA的制定,所以JPA中的一些東西可以與Hibernate相對比。
JPA特點:
- JPA可以使用xml和注解映射源數據,JPA推薦使用注解來開發。
- 它有JPQL語言也是面向對象的查詢語言,和hql比較類似。
環境搭建
我們使用Hibernate作為JPA的實現產品,需要導入的jar包有:
其實也就是Hibernate的required包下的jar,重點在於有Hibernate-jpa-api這個jar的存在
注意:不要忘了我們的數據庫驅動jar
Persistence.xml文件的編寫
JPA規范要求在內路徑下META-INF目錄下放置persistence.xml文件,文件名稱是固定的。這個文件在於spring整合之后就可以取消了。
一個簡單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”
開始操作實體類
我們可以使用注解來映射源數據了,而不用再編寫如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的主鍵生成策略多,因為其只是做一個抽象):
- IDENTITY:采用數據庫 ID自增長的方式來自增主鍵字段,Oracle 不支持這種方式;
- AUTO: JPA自動選擇合適的策略,是默認選項;
- SEQUENCE:通過序列產生主鍵,通過 @SequenceGenerator 注解指定序列名,MySql 不支持這種方式;
- TABLE:通過表產生主鍵,框架借由表模擬序列產生主鍵,使用該策略可以使應用更易於數據庫移植。
在我的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 API
創建EntityManagerFactory和EntityManager
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包下的,不要導錯了
EntityManager下的方法
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放棄維護關系,所以刪除一的一方會出錯(存在外鍵約束),解決辦法我知道的有:
- 不要設置mappedBy屬性,但是不注意保存先后順序級就會出現多余的update
- mappedBy和cascade= {CascadeType.REMOVE}屬性配合使用,但是就只能級聯刪除了
- 手工將多方的外鍵改為null
還有沒什么辦法可以很好的解決這個放棄維護帶來的刪除問題?
其實仔細一想這個異常是存在一定道理的,因為我們都知道建表或插入數據的時候是先主表,后從表;而刪除的時候想刪除從表,再刪除主表。如果我們不先處理多的一方,而直接刪除一的一方,數據庫是不允許的,因為外鍵字段的值必須在主表中存在,這個異常也在提示我們這樣的操作存在問題
一對多雙向關聯查詢
這里有一個規律:凡是以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 實現支持二級緩存,該節點可以配置在當前的持久化單元中是否啟用二級緩存,可配置如下值:
- ALL:所有的實體類都被緩存
- NONE:所有的實體類都不被緩存.
- ENABLE_SELECTIVE:標識 @Cacheable(true) 注解的實體類將被緩存
- DISABLE_SELECTIVE:緩存除標識 @Cacheable(false) 以外的所有實體類
- UNSPECIFIED:默認值,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了。
JPQL
類似於HQL,JPQL也是面向對象的查詢語句,也可以完成CRUD操作,但是寫法上有點不同,不同點在於查詢上,來看一條JPQL語句:
SELECT u from User u
再來看HQL的寫法:
from User
可以看到JPQL的查詢需要將select關鍵字和需要得到的字段寫出來(好在需要得到的字段可以是一個對象),運行這些語句的都是Query接口,只是要注意是不同的包下的。
Query接口的主要方法:
- int executeUpdate() 用於執行update或delete語句。
- List getResultList() 用於執行select語句並返回結果集實體列表。
- Object getSingleResult() 用於執行只返回單個結果實體的select語句。
- Query setFirstResult(int startPosition) 用於設置從哪個實體記錄開始返回查詢結果。
- Query setMaxResults(int maxResult) 用於設置返回結果實體的最大數。與setFirstResult結合使用可實現分頁查詢。
- setHint(String hintName, Object value) 設置與查詢對象相關的特定供應商參數或提示信息。參數名及其取值需要參考特定 JPA 實現庫提供商的文檔。如果第二個參數無效將拋出IllegalArgumentException異常。
- setParameter(int position, Object value) 為查詢語句的指定位置參數賦值。Position 指定參數序號,value 為賦給參數的值。
- setParameter(String name, Object value) 為查詢語句的指定名稱參數賦值。
@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)); }
在JPQL中還可以使用Order by
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提供了以下一些內建函數,包括字符串處理函數、算術函數和日期函數。如字符串處理函數:
- concat(String s1, String s2):字符串合並/連接函數。
- substring(String s, int start, int length):取字串函數。
- trim([leading|trailing|both,] [char c,] String s):從字符串中去掉首/尾指定的字符或空格。
- lower(String s):將字符串轉換成小寫形式。
- upper(String s):將字符串轉換成大寫形式。
- length(String s):求字符串的長度。
- locate(String s1, String s2[, int start]):從第一個字符串中查找第二個字符串(子串)出現的位置。若未找到則返回0。
//使用 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
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>