JPA的學習

JPA是Java Persistence API的簡稱,中文名Java持久層API。是Java EE5.0平臺中Sun爲了統一持久層ORM框架而制定的一套標準,注意是一套標準,而不是具體實現,不可能單獨存在,須要有其實現產品。Sun挺擅長制定標準的,例如JDBC就是爲了統一鏈接數據庫而制定的標準,而咱們使用的數據庫驅動就是其實現產品。JPA的實現產品有HIbernate,TopLink,OpenJPA等等。值得說一下的是Hibernate的做者直接參與了JPA的制定,因此JPA中的一些東西能夠與Hibernate相對比。java

JPA特色:mysql

  • JPA可使用xml和註解映射源數據,JPA推薦使用註解來開發。
  • 它有JPQL語言也是面向對象的查詢語言,和hql比較相似。

環境搭建

咱們使用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的主鍵生成策略多,由於其只是作一個抽象):

  • 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放棄維護關係,因此刪除一的一方會出錯(存在外鍵約束),解決辦法我知道的有:

  1. 不要設置mappedBy屬性,可是不注意保存前後順序級就會出現多餘的update
  2. mappedBy和cascade= {CascadeType.REMOVE}屬性配合使用,可是就只能級聯刪除了
  3. 手工將多方的外鍵改成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>
相關文章
相關標籤/搜索