JPA的學習


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

  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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM