JPA詳解

1.1 01、全面闡釋和精彩總結JPA

1.2 02、JPA開發環境和思想介紹

1.3 03、搭建JPA開發環境和全局事務介紹

1.4 04、第一個JPA實例與JPA主鍵生成策略

1.5 05、日期_枚舉等字段類型的JPA映射

1.6 06、大數據字段映射與字段延遲加載

1.7 07、使用JPA加載_更新_刪除對象

1.8 08、分析JPA與持久化實現產品對接的源代碼

1.9 09、使用JPQL語句進行查詢

1.10 10、JPA中的一對多雙向關聯與級聯操作(一對多關係:一)

1.11 11、JPA中的一對多延遲加載與關係維護(一對多關係:二)

1.12 12、JPA中的一對一雙向關聯

1.13 13、JPA中的多對多雙向關聯實體定義與註解設置

1.14 14、JPA中的多對多雙向關聯的各項關係操作

1.15 15、JPA中的聯合主鍵

1.1 01、全面闡釋和精彩總結JPA


什麼是JPA

JPA(Java Persistence API)是Sun官方提出的Java持久化規範。它爲Java開發人員提供了一種對象/關聯 映射工具來管理Java應用中的關係數據。他的出現主要是爲了簡化現有的持久化開發工作和整合ORM技術,結 束現在Hibernate,TopLink,JDO等ORM框架各自爲營的局面。值得注意的是,JPA是在充分吸收了現有 Hibernate,TopLink,JDO等ORM框架的基礎上發展而來的,具有易於使用,伸縮性強等優點。從目前的開 發社區的反應上看,JPA受到了極大的支持和讚揚,其中就包括了Spring與EJB3.0的開發團隊。着眼未來幾年的 技術走向,JPA作爲ORM領域標準化整合者的目標應該不難實現。


JPA的總體思想和現有Hibernate,TopLink,JDO等ORM框架大體一致。總的來說,JPA包括以下3方面的技 術:


ORM映射元數據

JPA支持XML和JDK5.0註釋(也可譯作註解)兩種元數據的形式,元數據描述對象和表之間的映射關 系,框架據此將實體對象持久化到數據庫表中。

  • Java持久化API

    用來操作實體對象,執行CRUD操作,框架在後臺替我們完成所有的事情,開發者可以從繁瑣的 JDBC和SQL代碼中解脫出來。

    查詢語言(JPQL)

    這是持久化操作中很重要的一個方面,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程 序的SQL語句緊密耦合。


    -----------------------------------------------------------------------------------------------------------------

    ----------------------------------------------------------------


    提示:JPA不是一種新的ORM框架,他的出現只是用於規範現有的ORM技術,他不能取代現有的 Hibernate,TopLink等ORM框架。相反,在採用JPA開發時,我們仍將使用到這些ORM框架,只是此時開發 出來的應用不再依賴於某個持久化提供商。應用可以在不修改代碼的情況下在任何JPA環境下運行,真正做到低 耦合,可擴展的程序設計。


    簡單說,JPA乾的東西就是Hibernate乾的東西,他們的作用是一樣的。但要注意的是:JPA只是一套規

    範,不是一套產品,Hibernate已經是一套產品了。

    他的出現主要是爲了簡化現有的持久化開發工作和整合ORM技術,結束現在Hibernate,TopLink,JDO等 ORM框架各自爲營的局面。 之前學的Hibernate,實際上我們面對的是Hibernate的API進行開發,那麼面對 Hibernate的API開發有哪些不好的地方呢?不好的地方是我們跟Hibernate這個產品就會緊密的耦合在一塊, 如果離開了Hibernate我們是無法在別的ORM框架中使用我們的應用的。那麼JPA的出現就是爲了結束現在 Hibernate,TopLink,JDO等ORM框架各自爲營的局面。簡單的說就是:你採用JPA開發應用,那麼你的應用 可以運用在實現了JPA規範的持久化產品中(好比說Hibernate,TopLink,JDO)


    JPA這門技術是未來發展的必然趨勢,以後我們要採用ORM技術呢,我們不會在面對Hibernate編程,不會 在面對TopLink編程,而是面對JPA規範編程。 就是說,過了幾年之後,你們的應用就會很少面對Hibernate API進行編程,這是爲什麼呢? 這就好比以前我們訪問數據庫一樣,假設以前我們沒有JDBC這門技術的話,我 們跟各個數據庫鏈接只能使用各個數據庫廠商給我們提供的API,去訪問他們的數據庫,那麼自從有了JDBC之 後,我們就不再需要面對數據庫廠商給我們提供的API進行跟數據庫鏈接了,而是直接使用JDBC這套規範,我 們就可以跟各個數據庫進行對接。目前,JPA跟Hibernate,TopLink的關係也是一樣的,JPA就和JDBC一樣,提 供一種通用的,訪問各個ORM實現產品的橋樑工具。通過JPA技術,我們只需要面對它的規範編程,編出來的 應用就可以應用在各個持久化產品中(包括Hibernate,TopLink),就是說你底層用的產品對我來說,已經不再 重要了。


    總結一下:JPA是一套規範,不是一套產品,那麼像Hibernate,TopLink,JDO他們是一套產品,如果說這些產 品實現了這個JPA規範,那麼我們就可以叫他們爲JPA的實現產品。

    JPA的主要設計者是Hibernate的設計者。JPA是一種規範不是產品,而Hibernate是一種ORM技術的產品。 JPA有點像JDBC,爲各種不同的ORM技術提供一個統一的接口,方便把應用移植到不同的ORM技術上。


    低耦合一直是我們在軟件設計上追求的目標,使用JPA,就可以把我們的應用完全從Hibernate中解脫出來 1.2 02、JPA開發環境和思想介紹



    開發JPA依賴的jar文件

    注意jar文件不能放在含有中文或是含有空格的路徑下,否則可能會出現找不到類或是編譯失敗的錯誤。

    ---------------------------------------------------------------------------------------------------------------

    Hibernate核心包(8個文件):hibernate-distribution-3.3.1.GA.ZIP

    ---------------------------------------------------------------------------------------

    hibernate3.jar

    lib\bytecode\cglib\hibernate-cglib-repack-2.1_3.jar (CGLIB庫,Hibernate用它來實現PO字節碼的動態 生成,非常核心的庫,必須使用的jar包)

    lib\required\*.jar


    Hibernate註解包(3個文件):hibernate-annotations-3.4.0.GA.ZIP

    -----------------------------------------------------------------------------------------

    hibernate-annotations.jar

    lib\ejb3-persistence.jar, hibernate-commons-annotations.jar


    Hibernate針對JPA的實現包(3個文件):hibernate-entitymanager-3.4.0.GA.ZIP

    --------------------------------------------------------------------------------------------------------------

    hibernate-entitymanager.jar lib\test\log4j.jar, slf4j-log4j12.jar


    Hiberante封裝了JDBC,連接具體的數據庫還需要具體數據庫的JDBC驅動包,這裏使用的是MySQL,需 要MySQL數據庫驅動包:

    mysql-connector-java-3.1.10-bin.jar


    JPA的配置文件

    --------------------------------------------------------------------------------------------------------------

    JPA規範要求在類路徑(Eclipse工程的src目錄)的META-INF目錄下放置persistence.xml, 文件的名稱是固 定的,配置模板(此處是針對Hibernate)如下:

    <?xml version="1.0"?>


    <persistencexmlns="http://java.sun.com/xml/ns/persistence"xmlns:xsi="http://www.w3.org/2001/Xxsi:schemaLocation="http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">


    <persistence-unit name="itcast" transaction-type="RESOURCE_LOCAL">


    <properties>

    <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />

    <property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver" />

    <property name="hibernate.connection.username" value="root" />

    <property name="hibernate.connection.password" value="123456" />

    <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/itcast?useUnic

    <property name="hibernate.max_fetch_depth" value="3" />

    <property name="hibernate.show_sql" value="true" />

    <property name="hibernate.hbm2ddl.auto" value="update"/>

    </properties>


    </persistence-unit>


    </persistence>


    hibernate.hbm2ddl.auto

    --------------------------------------------------------------------------------------------------------------


    <PROPERTIES>


    <property name="hibernate.show_sql" value="true"></property>

    <property name="hibernate.hbm2ddl.auto" value="create"></property>


    </PROPERTIES>


    其實這個hibernate.hbm2ddl.auto參數的作用主要用於:自動創建|更新|驗證數據庫表結構。如果不是此方 面的需求建議set value="none"。裏面可以設置的幾個參數:


    validate每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新 表,但是會插入新值。

    create每次加載hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新

    表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因。

  • create-drop每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。

    update最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立 好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然 存在不會刪除以前的行。要注意的是當部署到服務器後,表結構是不會被馬上建立起來的,是要等應用 第一次運行起來後纔會。


    總結:


    • 請慎重使用此參數,沒必要就不要隨便用。

    • 如果發現數據庫表丟失,請檢查hibernate.hbm2ddl.auto的配置


    再說點“廢話”:

    當我們把hibernate.hbm2ddl.auto=create時hibernate先用hbm2ddl來生成數據庫schema。當我們把 hibernate.cfg.xml文件中hbm2ddl屬性註釋掉,這樣我們就取消了在啓動時用hbm2ddl來生成數據庫 schema。通常只有在不斷重複進行單元測試的時候才需要打開它,但再次運行hbm2ddl會把你保存的一切都 刪除掉(drop)---- create配置的含義是:“在創建SessionFactory的時候,從scema中drop掉所有的表,再 重新創建它們”。注意,很多Hibernate新手在這一步會失敗,我們不時看到關於Table not found錯誤信息的 提問。但是,只要你根據上面描述的步驟來執行,就不會有這個問題,因爲hbm2ddl會在第一次運行的時候創 建數據庫schema,後續的應用程序重啓後還能繼續使用這個schema。假若你修改了映射,或者修改了數據庫 schema,你必須把hbm2ddl重新打開一次。


    示例代碼:


    <?xml version="1.0" encoding="UTF-8"?>


    <persistence version="1.0"xmlns:persistence="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 persistence_1_0.xsd ">


    <!--


    Name屬性用於定義持久化單元的名字 (name必選,空值也合法); transaction-type 指定事務類型(可選)

    -->

    <persistence-unit name="unitName" transaction-type="JTA">


    <!-- 描述信息.(可選) -->

    <description> </description>


    <!-- javax.persistence.PersistenceProvider接口的一個實現類(可選) -->

    <provider> </provider>


    <!-- Jta-data-source和 non-jta-data-source用於分別指定持久化提供商使用的JTA和/或non-JTA數據源的

    <jta-data-source>java:/MySqlDS</jta-data-source>

    <non-jta-data-source> </non-jta-data-source>


    <!-- 聲明orm.xml所在位置.(可選) -->

    <mapping-file>product.xml</mapping-file>


    <!-- 以包含persistence.xml的jar文件爲基準的相對路徑,添加額外的jar文件.(可選) -->

    <jar-file>../lib/model.jar</jar-file>


    <!-- 顯式列出實體類,在Java SE 環境中應該顯式列出.(可選) -->

    <class>com.domain.User</class>

    <class>com.domain.Product</class>


    <!-- 聲明是否掃描jar文件中標註了@Enity類加入到上下文.若不掃描,則如下:(可選) -->

    <exclude-unlisted-classes/>


    <!-- 廠商專有屬性(可選) -->

    <properties>

    <!-- hibernate.hbm2ddl.auto= create-drop / create / update -->

    <property name="hibernate.hbm2ddl.auto" value="update" />

    <property name="hibernate.show_sql" value="true" />

    </properties>


    </persistence-unit>


    </persistence>

    通常在企業開發中,有兩種做法:


    • 1.先建表,後再根據表來編寫配置文件和實體bean。使用這種方案的開發人員受到了傳統數據庫建模的 影響。

    • 2.先編寫配置文件和實體bean,然後再生成表,使用這種方案的開發人員採用的是領域建模思想,這種 思想相對前一種思想更加OOP。


    建議使用第二種(領域建模思想),從軟件開發來想,這種思想比第一種思想更加面向對象。 領域建模思想 也是目前比較新的一門建模思想,第一種是傳統的建模思想,已經有10來年的發展歷程了,而領域建模思想是 近幾年才興起的,這種思想更加的面向對象 。


    附件下載:

  • mysql-connector-java-3.1.10-bin.jar (408.9 KB)

  • dl.javaeye.com/topics/download/ece22346-3240-3847-93fe-ac518d43cc35

    • JPA開發所用到的所有jar包.rar (5.4 MB)

  • dl.javaeye.com/topics/download/906a1117-6a65-32f9-af38-afd1556e874c

1.3 03、搭建JPA開發環境和全局事務介紹


persistence.xml(JPA規範要求在類路徑的META-INF目錄下) ,如下圖:



<persistencexmlns="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/persistence_1_0.xsd" version="1.0">


<persistence-unit name="itcast" transaction-type="RESOURCE_LOCAL">

<properties>

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />

<property name="hibernate.hbm2ddl.auto" value="update"/>

<property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver"

<property name="hibernate.connection.username" value="root" />

<property name="hibernate.connection.password" value="123456" />

<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/itcast

</properties>

</persistence-unit>


</persistence>


persistence.xml這個配置文件的模板可以從哪裏找到呢? 因爲JPA是一規範,所以你即可以從JPA的規範文檔裏找到,也可以從任何一個實現了JPA規範的實現產品中

找到。好比Hibernate,可以從hibernate-

entitymanager-3.4.0.GA\doc\reference\en\html_single\index.html中找到。


<persistence-unit><persistence-unit/>持久化單元,簡單說,就是代表一堆實體bean的集合,那麼這堆 實體bean,我們叫他們做實體bean單元。我們在學Hibernate就已知道,他們就是專門用於跟數據庫映射的普 通的Java對象,在我們JPA裏面,這些對象叫做實體bean。持久化單元就是一堆實體bean的集合,我們爲這堆 集合取個名稱,<persistence-unit name="..."><persistence-unit/>


全局事務 本地事務

-----------------------------------------------------------------------------------------------------------------

全局事務:資源管理器管理和協調的事務,可以跨越多個數據庫和進程。資源管理器一般使用 XA 二階段提 交協議與“企業信息系統”(EIS) 或數據庫進行交互。

本地事務:在單個 EIS 或數據庫的本地並且限制在單個進程內的事務。本地事務不涉及多個數據來源。


<persistence-unit><persistence-unit/>標籤還有個屬性,是transaction-type(事務的類型),這屬性有 兩個值,分別是JTA(全局事務)和RESOURCE_LOCAL(本地事務)。

這裏我們配置爲transaction-type="RESOURCE_LOCAL",因爲我們只針對一個數據庫進行操作,也說只針 對一個事務性資源進行操作。


以前我們學習的事務類型都屬於本地事務。 JTA(全局事務)和RESOURCE_LOCAL(本地事務)有什麼區別呢? 在某些應用場合,只能使用全局事務,比如:

有兩個數據庫:

  1. mysql 2.oracle 現在有個業務需求--轉賬

    step 1> update mysql_table set amount=amount-xx where id=aaa 發生扣錢,假設是在mysql數據庫扣錢 的。

    step 2> update oracle_table set amount=amount+xx where id=bbb 加錢,假設是在oracle數據庫扣錢的。 現在怎麼確保兩個語句在同一個事務裏執行呢?


    以前在JDBC裏是這樣做 connection = mysql 連接mysql

    connection.setAutoCommit(false); 不自動提交

    1> update mysql_table set amount=amount-xx where id=aaa 發生扣錢,假設是在mysql數據庫扣錢的。 2> update oracle_table set amount=amount+xx where id=bbb 發生在oracle數據庫 connection.commit(); 執行這兩條語句,然後通過connection對象提交事務.我們這樣子做只能確保這兩個語句在同一個數據庫mysql 裏面實現在同一個事務裏執行。 但是問題是我們現在是要連接到oracle數據庫,是不是需要connection2啊?

    connection = mysql 連接mysql

    connection2 = oracle 連接oracle connection.setAutoCommit(false); 不自動提交

    1> update mysql_table set amount=amount-xx where id=aaa 發生扣錢,假設是在mysql數據庫扣錢的。 2> update oracle_table set amount=amount+xx where id=bbb 發生在oracle數據庫 connection.commit();

    connection2.setAutoCommit(false); connection2.commit();

    事務只能在一個connection裏打開,並且確保兩條語句都在該connection裏執行,這樣才能讓兩條語句在同一 事務裏執行,現在問題就在於connection2是連接到oracle數據庫的,那麼connection2再開事務有意義嗎?它 能確保嗎?不能,所以在這種情況下就只能使用全局事務了。 這種情況下用普通JDBC操作是滿足不了這個業務需求的,這種業務需求只能使用全局事務,本地事務是無法支 持我們的操作的,因爲這時候,事務的生命週期不應該侷限於connection對象的生命週期範圍


    全局事務怎麼做呢?

    JPA.getUserTransaction().begin(); 首先要全局事務的API,不需要我們編寫,通常容器已經提供給我們了, 我們只需要begin一下

    connection = mysql 連接mysql connection2 = oracle 連接oracle

    connection--> update mysql_table set amount=amount-xx where id=aaa 發生扣錢,假設是在mysql數據 庫扣錢的。

    connection2--> update oracle_table set amount=amount+xx where id=bbb 發生在oracle數據庫 JPA.getUserTransaction().commit();


    那麼它是怎麼知道事務該提交還是回滾呢? 這時候它使用了二次提交協議。二次提交協議簡單說就這樣:如果你先執行第一條語句,執行的結果先預提交 到數據庫,預提交到數據庫了,數據庫會執行這條語句,然後返回一個執行的結果,這個結果假如我們用布爾 值表示的話,成功就是true,失敗就是false.然後把執行的結果放入一個(假設是List)對象裏面去,接下來再 執行第二條語句,執行完第二條語句之後(也是預處理,數據庫不會真正實現數據的提交,只是說這條語句送 到數據庫裏面,它模擬下執行,給你返回個執行的結果),假如這兩條語句的執行結果在List裏面都是true的 話,那麼這個事務就認爲語句是成功的,這時候全局事務就會提交。 二次提交協議,數據庫在第一次提交這個 語句時,只會做預處理,不會發生真正的數據改變,當我們在全局事務提交的時候,這時候發生了第二次提 交,那麼第二次提交的時候纔會真正的發生數據的改動。

    如果說在執行這兩條語句中,有一個出錯了,那麼List集合裏就有個元素爲false,那麼全局事務就認爲你這 個事務是失敗的,它就會進行回滾,回滾的時候,哪怕你的第二條語句在第一次提交的時候是成功的,它在第 二次提交的時候也會回滾,那麼第一次的更改也會恢復到之前的狀態,這就是二次提交協議。(可以查看一下 數據庫方面的文檔來了解二次提交協議)

    回到persistence.xml的配置裏面去,事務類型有兩種,什麼時候該用全局事務(JTA)?什麼時候改用本地事務

    (RESOURCE_LOCAL)?應有你的業務應用需求來定,我們的大部分應用只是需要本地事務。全局事務通常是在 應用服務器裏使用,比如weblogic,JBoss,學習EJB3後給大家介紹。 企業面試時被問到就要注意了(事務類型有 哪幾種?分別用在什麼場景下?)

    1.4 04、第一個JPA實例與JPA主鍵生成策略


    寫實體bean,映射的數據可以採用XML配置方式,也可以採用註解方式,在JPA中推薦大家用註解的方式, 因爲註解的方式開發應用效率是挺高的。


    每個實體bean都要有個實體標識屬性,這個實體標識屬性主要用於在內存裏面判斷對象。通過@Id就可以 定義實體標識。可以標識在屬性的get方法前面,也可以標識在字段上面,通常我們更傾向於標識在屬性的get 方面上面。

    如果我們希望採用數據庫的id自增長的方式來生成主鍵值的話,這時候我們要用到一個註解

    @GeneratedValue,這注解裏面有一些屬性,其中一個是策略strategy,生成主鍵值的方案,JPA裏沒有 Hibernate提供的那麼多方案,它提供的方案有如下圖:



    1.

    ◦ AUTO: JPA自動選擇合適的策略,是默認選項;

    ◦ IDENTITY: 採用數據庫ID自增長的方式來生成主鍵值,Oracle不支持這種方式;

    • SEQUENCE: 通過序列產生主鍵,通過@SequenceGenerator註解指定序列名,MySql不支 持這種方式;

◦ TABLE: 採用表生成方式來生成主鍵值,那怎麼樣生成呢?很簡單,表裏面通常有兩個字段, 第一個字段是給它一個名稱(就是個列名而已),第二個字段專門用來累加用的,就是說每訪 問一次這個表,第二個字段就會累加1,不斷累加。就是說你們要得到這個主鍵值的話,訪問這 個表,然後update這個表的這個字段,把它累加1之後,然後再把這個值取出來作爲主鍵,再 給他賦進去,表生成就是這樣。

◦ Oracle數據庫默認情況下,不能支持用id自增長方式來生成主鍵值;

◦ mysql在默認情況下不能支持SEQUENCE序列的方式來生成主鍵值,所以我們一定要注意我們

使用的數據庫。

◦ TABLE表生成方式纔是通用的,但是這種方式效率並不高。

◦ 如果我們開發的應用,我們不可以預測用戶到底使用哪種數據庫,那麼這個時候應該設爲哪個 值呢?答案是AUTO,就是說由持久化實現產品,來根據你使用的方言來決定它採用的主鍵值 的生成方式,到底是IDENTITY?還是SEQUENCE?還是TABLE? 如果用的是Hibernate,那麼 它會用IDENTITY這種生成方式來生成主鍵值。


IDENTITY和SEQUENCE這兩種生成方案通不通用啊?對所有數據庫: 注意:如果我們把策略strategy設置成@GeneratedValu(strategy=GenerationType.AUTO)的話,AUTO本身就是策略的默認值,我們可以省略掉,就是說簡單寫成這樣@GeneratedValue


摘自CSDN:

@GeneratedValue:主鍵的產生策略,通過strategy屬性指定。默認情況下,JPA自動選擇一個最 適合底層數據庫的主鍵生成策略,如SqlServer對應identity,MySql對應auto increment。在 javax.persistence.GenerationType中定義了以下幾種可供選擇的策略:


1) IDENTITY:採用數據庫ID自增長的方式來自增主鍵字段,Oracle不支持這種方式;

2) AUTO: JPA自動選擇合適的策略,是默認選項;

3) SEQUENCE:通過序列產生主鍵,通過@SequenceGenerator註解指定序列名,MySql不支持這種

方式; 4) TABLE:通過表產生主鍵,框架藉由表模擬序列產生主鍵,使用該策略可以使應用更易於數 據庫移植。不同的JPA實現商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表 Hibernate生成一個hibernate_sequences表,而 TopLink則生成sequence表。這些表都具有一個序 列名和對應值兩個字段,如SEQ_NAME和SEQ_COUNT。


可參考http://blog.csdn.net/lzxvip/archive/2009/06/19/4282484.aspx


實體bean開發完後,就要用持久化API對實體bean進行添刪改查的操作,我們學習持久化API的時 候,可以對照Hibernate來學習,接下來建立個單元測試,在開發的過程中,建議大家一定要用單元測 試(junit可以用來進行單元測試)。


第一步寫:persistence.xml(要求放在類路徑的META-INF目錄下)


<persistencexmlns="http://java.sun.com/xml/ns/persistence"xmlns:xsi="http://www.w3.org/20xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.

<persistence-unit name="itcast" transaction-type="RESOURCE_LOCAL">


<properties>

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />

<property name="hibernate.hbm2ddl.auto" value="update"/>

<property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver" />

<property name="hibernate.connection.username" value="root" />

<property name="hibernate.connection.password" value="456" />

<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/itcast?useUnic

</properties>


</persistence-unit>

</persistence>


第二步寫:Person.java (實體bean)


package cn.itcast.bean;


import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;


@Entity //以javax開頭的包,都是Sun公司制定的一些規範 public class Person {

private Integer id; private String name;


public Person() {

/*對象是由Hibernate爲我們創建的,當我們通過ID來獲取某個實體的時候,這個實體給我們返回了這個對

}

public Person(String name) {

this.name = name;


}


@Id

@GeneratedValue(strategy=GenerationType.AUTO) //auto默認,可不寫,直接寫@GeneratedValue public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}

}


第三步:PersonTest.java (junit單元測試)


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass;

import org.junit.Test;


import cn.itcast.bean.Person; public class PersonTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test public void save(){

//對實體bean進行操作,第一步應該獲取什麼對象啊? SessionFactory對象

//這裏用獲取的EntityManagerFactory對象,這可以把它看成跟Hibernate的SessionFactory EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast")

//參數"itcast"是persistence.xml文件中<persistence-unit&nbsp;name="itcast">name的 EntityManager em = factory.createEntityManager();

em.getTransaction().begin(); //開啓事務 em.persist(new Person("傳智播客")); //持久化對象 em.getTransaction().commit(); //提交事務 em.close();

factory.close();


//SessionFactory --> Session --> begin事務

}

}


/* session.save(obj);

persist這方法在Hibernate裏也存在,Hibernate的作者已經不太推薦大家用save方法,而是推薦大家用 persist方法 。

why? 首先並不是代碼上的問題,主要是這個名字上的問題,因爲我們把這個ORM技術叫做持久化產品,那 麼我們對某個對象持久化,應該叫持久化,而不應該叫保存,所以後來Hibernate的作者推薦用persist方法,這 並不是功能的問題, 主要是取名的問題,所以用persist方法也可以 。

*/


目前數據庫表是不存在的,我們採取實體(領域)建模的思想,讓它根據實體bean來生成數據庫表,在 persistence.xml裏,<property name="hibernate.hbm2ddl.auto" value="update"/>,生成策略是 update,就是說表不存在的時候,它會創建數據庫表。

問題,它什麼時候創建表啊?創建表的時機是在什麼時候創建的啊?答案是得到SessionFactory的時候,在 JPA裏也一樣,是我們得到EntityManagerFactory的時候創建表,也就是說我們只要執行下面的那段代碼就生 成表了。


public class PersonTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test public void save(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") factory.close();

}


}


通過這個特性,可以在開發的時候,用來驗證我們編寫的實體映射元數據是否是正確的,通過這個就可以判 斷。如果生成不了表,就說明是編寫的實體映射出問題了(比如實體bean),以後要學會怎樣排錯。

1.5 05、日期_枚舉等字段類型的JPA映射


映射元數據是什麼樣的?不設置默認的情況下: 實體類Person生成表是Person表;字段id,name,採用 bean中getXXX、setXXX的XXX名稱作爲字段的名稱,而不是採用屬性的名稱作爲字段名稱


Person.java


package cn.itcast.bean;


import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated;

import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;

import javax.persistence.Table; import javax.persistence.Temporal;

import javax.persistence.TemporalType; import java.util.Date;

@Entity //以javax開發的包,都是Sun公司制定的一些規範

@Table(name = "PersonTable") //改變數據庫中映射表名 public class Person {

private Integer id; private String name;

private Date birthday; //1987-12-10

private Gender gender = Gender.MAN; //這裏可以設置默認值, Gender是一個枚舉類型。


@Enumerated(EnumType.STRING) //說明這個屬性是個枚舉類型,括號內的表示存入數據庫的枚舉字符串

@Column(length = 5, nullable = false) //Eclipse代碼助手快捷鍵爲ALT+/ public Gender getGender() {

return gender;

}


public void setGender(Gender gender) { this.gender = gender;

}


@Temporal(TemporalType.DATE) //說明這個屬性映射到數據庫中是一個日期類型,括號中的是日期格式 public Date getBirthday() {

return birthday;


}


public void setBirthday(Date birthday) { this.birthday = birthday;

}


public Person() {

/* 對象是由Hibernate爲我們創建的,當我們通過ID來獲取某個實體的時候,這個實體給我們返


}


public Person(String name) { this.name = name;

}


@Id

@GeneratedValue(strategy = GenerationType.AUTO)

// auto是默認值,可不寫,直接寫@GeneratedValue public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false, name = "personName") public String getName() {

return name;

}


public void setName(String name) { this.name = name;

}

}


@Column 的選項。 看圖:


@Temporal 的選項。 看圖:


@Temporal(TemporalType.)的選項。 看圖:

@Enumerated(EnumType.) 的選項。 看圖:

1.6 06、大數據字段映射與字段延遲加載


Person.java


package cn.itcast.bean;


import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType;

import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;

import javax.persistence.Lob; import javax.persistence.Table; import javax.persistence.Temporal;

import javax.persistence.TemporalType; import javax.persistence.Transient;


import java.util.Date;


@Entity

//以javax開發的包,都是Sun公司制定的一些規範

@Table(name = "PersonTable") public class Person {

private Integer id; private String name; private Date birthday;

private Gender gender = Gender.MAN; // 這裏可以設置默認值 private String info;

private Byte[] file;

private String imagePath; //該屬性不希望成爲可持久化字段

@Transient //這個註解用來標註imagePath這個屬性不作爲可持久化字段,就是說不跟數據庫的字段 public String getImagePath() {

return imagePath;


}


public void setImagePath(String imagePath) { this.imagePath = imagePath;

}


@Lob //申明屬性對應的數據庫字段爲一個大文本類,文件屬性也是用這個聲明映射。 public String getInfo() {

return info;


}


public void setInfo(String info) { this.info = info;

}


@Lob //聲明屬性對應的是一個大文件數據字段。

@Basic(fetch = FetchType.LAZY) //設置爲延遲加載,當我們在數據庫中取這條記錄的時候,不會去取 public Byte[] getFile() {

return file;


}


public void setFile(Byte[] file) { this.file = file;

}


@Enumerated(EnumType.STRING)

@Column(length = 5, nullable = false) public Gender getGender() {

return gender;


}


public void setGender(Gender gender) { this.gender = gender;

}


@Temporal(TemporalType.DATE) public Date getBirthday() {

return birthday;


}


public void setBirthday(Date birthday) { this.birthday = birthday;

}


public Person() {

/* 對象是由Hibernate爲我們創建的,當我們通過ID來獲取某個實體的時候,這個實體給我們返


}


public Person(String name) { this.name = name;

}


@Id

@GeneratedValue(strategy = GenerationType.AUTO)

// auto是默認值,可不寫 public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false, name = "personName") public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}

}


生成的數據庫字段類型如下:



假如 private Byte[] file;保存的是一個文件,如果我們要獲取一個Person對象的話,會把file這個字段保存的 內容找回來,並且放在內存裏面。(如果我們保存的文件有50M,那每次獲取Person bean的時候,都會獲取 file這個文件,在內存中可能是50.1M,這樣太佔資源了,怎麼辦?) 可以給file加@Basic(fetch = FetchType.LAZY)這個註解,如果我們設置了延遲加載,那麼當我們調用Hibernate的get方法得到Person這個 記錄的時候,如果沒有訪問file這個屬性的get方法的話,那麼它就不會從數據庫裏幫我們把這個file得到;如果 說你要訪問這個file屬性,那麼它纔會從數據庫裏面把這個file數據裝載上來。 也就是說,只要我們不訪問它, 那麼它就不會從數據庫裏面把數據裝載進內存裏面。 如果不裝載文件的話,那麼得到的Person記錄可能就是 0.1M左右,當然,如果你訪問了file這個屬性的話,那麼它會從數據庫裏面把數據再裝載一次上來,在內存裏可 能就有50.1M了。所以,@Basic這個標籤一般用在大數據,也就是說你存放的數據大小比較大的話,大概數據 如果超過1M的話,就應該使用@Basci標籤,把屬性做延遲初始化,那麼當初次得到Person對象的時候,就不 會立刻去裝載數據,而是在第一次訪問的時候纔去裝載file數據。當然在第一次訪問file的時候,必須要確保 EntityManager這個對象要處於打開狀態(就好比session對象要處於打開狀態一樣),假如EntityManager對 象被close了的話,我們再訪問它的延遲屬性會出現延遲加載例外,這個在Hibernate的教程裏也經常遇到這問 題。

1.7 07、使用JPA加載_更新_刪除對象


PersonTest.java


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


import cn.itcast.bean.Person; public class PersonTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test public void save(){

//對實體bean進行操作,第一步應該獲取什麼對象啊? SessionFactory對象。

//這裏用獲取的EntityManagerFactory對象,這可以把它看成跟Hibernate的SessionFactory EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager(); em.getTransaction().begin();//開啓事務

em.persist(new Person("傳智播客")); em.getTransaction().commit(); em.close();

factory.close();


//SessionFactory --> Session --> begin事務

}

/*

session.save(obj);

&nbsp;&nbsp;&nbsp;&nbsp;persist這方法在Hibernate裏也存在,Hibernate的作者已經不太推薦大家用save方法

&nbsp;&nbsp;&nbsp;&nbsp;why? 首先並不是代碼上的問題,主要是這個名字上的問題,因爲我們把這個ORM技術叫

*/


@Test public void getPerson(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

Person person = em.find(Person.class,1); //相當於Hibernate的get方法,與lo System.out.println(person.getName());

em.close(); factory.close();

}


@Test public void getPerson2(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

Person person = em.getReference(Person.class,1);

//相當於Hibernate的load方法,延遲加載。

//並不會立刻從數據庫裏面得到這條記錄,只是返回了一個代理對象。 System.out.println(person.getName());

//這時候纔會去數據庫裏得數據,發生加載行爲。

//這時候要確保實體管理器處於打開狀態。 em.close();

//System.out.println(person.getName()); 如果屏蔽上上句,在這裏才訪問屬性,那就會出 factory.close();

}


//jpa實體的四種狀態:

/*(1)new 新建狀態。剛new出來的實體bean沒有與任何EntityManager相關聯就是新建狀態。

(2)managed 託管狀態(與EntityManager相關聯,被EntityManager託管),用EntityManager的fi

(3)遊離狀態。EntityManager對象調用clear方法,就把這個對象所託管的所有對象都變成遊離狀態的

(4)刪除狀態。*/


@Test public void updatePerson(){ //更改數據庫裏標誌爲1的那條記錄的名字爲老張,更改成功的 EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast")

EntityManager em = factory.createEntityManager();

em.getTransaction().begin(); //開啓事務,只讀取數據不需要開事務,更改數據的時候需要 Person person=em.find(Person.class,1); //相當於Hibernate的get方法,第一個參數是實 person.setName("老張"); //這個person是處於一個託管狀態的bean對象,用set方法改變的 em.getTransaction().commit();

em.close(); factory.close();

}


@Test public void updatePerson2(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

em.getTransaction().begin(); //開啓事務。 Person person=em.find(Person.class,1);

em.clear(); //把實體管理器中的所有實體變成遊離狀態。 person.setName("老黎");

em.merge(person); //更新處於遊離狀態的bean對象。 em.getTransaction().commit();

em.close(); factory.close();

}


@Test public void delete(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

em.getTransaction().begin(); //開啓事務。 Person person=em.find(Person.class,1);

em.remove(person); //刪除的bean對象也必須是處於託管狀態的對象才能被刪除成功。否則 em.getTransaction().commit();

em.close(); factory.close();

}


}


我們目前使用的是Hibernate,實際上我們操縱EntityManager對象時,它內部是操縱了Hibernate裏面的 sesson對象。它內部只是對session對象做了個封裝而已。


@Test public void getPerson(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

Person person = em.find(Person.class,2); //相當於Hibernate的get方法。 System.out.println(person);

em.close(); factory.close();

}


如果不存在id爲2的person的話,那麼返回的是null值 。


@Test public void getPerson2(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

Person person = em.getReference(Person.class,2); //1 System.out.println(person); //2 em.close();

factory.close();


}


如果不存在id爲2的person的話,那麼返回的是異常(javax.persistence.EntityNotFoundException)。 異常是在什麼時候觸發的呢? 是在1?還是2呢? 答案是2。這說明並不是em.getReference()這個方法執行時就發生異常,而是在你訪問這個對象或是它屬性

的時候纔出現異常。

數據庫裏沒有相應的記錄,EntityManagerd對象的get方法獲取不到記錄會返回null,而EntityManagerd對

象的getReference方法獲取不到記錄會在下一次訪問這個返回值的時候拋出異常。


@Test public void updatePerson2(){

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager(); em.getTransaction().begin();//開啓事務。

Person person=em.find(Person.class,1);

em.clear(); //把實體管理器中的所有實體變成遊離狀態。 person.setName("老黎"); em.getTransaction().commit();

em.close(); factory.close();

}


在clear之後,person變成了遊離狀態,這時候對遊離狀態的實體進行更新的話(person.setName("老 黎");),更新的數據是不能同步到數據庫的。可以採用方法em.merge(person);這方法是用於把在遊離狀態時候 的更新同步到數據庫。

1.8 08、分析JPA與持久化實現產品對接的源代碼


EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast"); 講解下這個方法內部的一些原理(瞭解下就OK)


打開源代碼Persistence.java (用DJ Java Decompiler 3.7反編譯的代碼)


// Decompiled by DJ v3.7.7.81 Copyright 2004 Atanas Neshkov Date: 2010-7-12 20:30:06

// Home Page :http://members.fortunecity.com/neshkov/dj.html - Check often for new version!

// Decompiler options: packimports(3)

// Source File Name: Persistence.java package javax.persistence;

import java.io.*; import java.net.URL; import java.util.*;

import java.util.regex.Matcher; import java.util.regex.Pattern;

import javax.persistence.spi.PersistenceProvider;


// Referenced classes of package javax.persistence:

// PersistenceException, EntityManagerFactory


public class Persistence

{


public Persistence()

{

}


public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName)

{


return createEntityManagerFactory(persistenceUnitName, null);

}


public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, M

{


EntityManagerFactory emf = null; if(providers.size() == 0)

findAllProviders();

Iterator i$ = providers.iterator(); do

{


if(!i$.hasNext()) break;

PersistenceProvider provider = (PersistenceProvider)i$.next();

emf = provider.createEntityManagerFactory(persistenceUnitName, properties);

} while(emf == null); if(emf == null)

throw new PersistenceException((new StringBuilder()).append("No Persistence provide


else


}


return emf;


private static void findAllProviders()

{



_L2:

ClassLoader loader; Enumeration resources; Set names;

loader = Thread.currentThread().getContextClassLoader();

resources = loader.getResources((new StringBuilder()).append("META-INF/services/").appe names = new HashSet();


InputStream is; if(!resources.hasMoreElements())

break; /* Loop/switch isn't completed */ URL url = (URL)resources.nextElement();

is = url.openStream();

names.addAll(providerNamesFromReader(new BufferedReader(new InputStreamReader(is)))); is.close();


_L1:

if(true) goto _L2; else goto _L1

Exception exception; exception; is.close();

throw exception;


Class providerClass;

for(Iterator i$ = names.iterator(); i$.hasNext(); providers.add((PersistenceProvider)pr

{


String s = (String)i$.next(); providerClass = loader.loadClass(s);

}


break MISSING_BLOCK_LABEL_214;

IOException e; e;

throw new PersistenceException(e); e;

throw new PersistenceException(e); e;

throw new PersistenceException(e); e;

throw new PersistenceException(e);

}


private static Set providerNamesFromReader(BufferedReader reader) throws IOException

{

Set names = new HashSet(); do

{


String line;

if((line = reader.readLine()) == null) break;

line = line.trim();

Matcher m = nonCommentPattern.matcher(line); if(m.find())

names.add(m.group().trim());

} while(true); return names;

}


public static final String PERSISTENCE_PROVIDER = "javax.persistence.spi.PeristenceProvider protected static final Set providers = new HashSet();

private static final Pattern nonCommentPattern = Pattern.compile("^([^#]+)");


}


這個資源在哪裏呢? 看圖:


打開,內容爲 org.hibernate.ejb.HibernatePersistence


程序會在類路徑地下尋找到這個文件,並讀取這個配置文件裏面指定的可持久化驅動。 Hibernate提供的可持久化驅動就是org.hibernate.ejb.HibernatePersistence這個類,這個類是Hibernate的 入口類,類似JDBC裏面的驅動類。

當然,不同的可持久化產品的入口類是不同的,


調用JPA應用,它能使用Hibernate,是因爲有這樣一個驅動類,它起到了一個橋樑的作用,過渡到Hibernate 的產品上,這就是調用EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast"); 創建實體管理器方法的一些執行細節


factory 是由Hibernate的可持久化驅動類創建出來的,如果觀察Hibernate的實現類的話,會發現實際上 EntityManagerFactory 是對SessionFactory這個類進行了一層封裝。 包括EntityManager類也是對Session對象進行了一層封裝而已。 只要研究下Hibernate的JPA實現代碼就可以觀察出來

1.9 09、使用JPQL語句進行查詢


查詢語言(JPQL)

這是持久化操作中很重要的一個方面,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程序的SQL 語句緊密耦合。


PersonTest.java

package junit.test; import java.util.List;

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;

import javax.persistence.Query; import org.junit.BeforeClass; import org.junit.Test;

import cn.itcast.bean.Person; public class PersonTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test

public void save() {

// 對實體bean進行操作,第一步應該獲取什麼對象啊? SessionFactory對象。

// 這裏用獲取的EntityManagerFactory對象,這可以把它看成跟Hibernate的SessionFactory EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); // 開啓事務。 em.persist(new Person("傳智播客"));

em.getTransaction().commit();

em.close(); factory.close();


// SessionFactory --> Session --> begin事務

}


/*

session.save(obj); persist這方法在Hibernate裏也存在,Hibernate的作者已經不太推薦大家用save方法,而是推薦大家用 why? 首先並不是代碼上的問題,主要是這個名字上的問題,因爲我們把這個ORM技術叫做持久化產品,那麼

*/


@Test

public void query1() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager();

// 只是想獲取數據,那麼創建的查詢語句可以不在事務裏創建,不需要開啓事務。

// 但是如果想通過語句去更新數據庫的話,就必須要打開事務了,否則不會保存成功。

Query query = em.createQuery("select o from Person o where o.id = ?1"); //?1採

// 和Hibernate一樣,都是面向對象的語句,不是sql語句。 裏面出現的都是實體的名稱和實體

//?1表示第一個參數,後面可以用Query對象的setParameter方法設置這個參數的內容。參數可以

// JPA規範的寫法前面是要加"select o"的(o是起的別名,名字可以任意),而Hibernate是可 query.setParameter(1, 1); //設置第一個參數的值爲1。

/*

不要直接在上面的語句裏寫值,因爲如果你用JDBC的話就會存在一個問題:注入sql工具,注入sql

*/

//防sql注入其實就是參數設置的地方只能允許設置爲一個參數,並且參數設置的方法還會去檢查 Person person = (Person) query.getSingleResult();

// getSingleResult方法相當於Hibernate裏的Session.createQuery("").uniqueResult();

// 使用getSingleResult方法的前提是必須保證記錄存在,如果記錄不存在,那麼會出錯的。這 System.out.println(person.getName());

em.close(); factory.close();

}

@Test

public void query2() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager();

Query query = em.createQuery("select o from Person o where o.id = ?1"); query.setParameter(1, 1);

List<Person> persons = query.getResultList(); //返回的是一個list,這裏指定了list


for (Person person : persons) { System.out.println(person.getName());

}

em.close(); factory.close();

}


@Test

public void deleteQuery() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); // 進行數據的更改操作,必須開啓事務。

Query query = em.createQuery("delete from Person o where o.id = ?1"); query.setParameter(1, 1);

query.executeUpdate(); em.getTransaction().commit(); em.close();

factory.close();


}


@Test

public void updateQuery() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); // 開啓事務。

Query query = em.createQuery("update Person o set o.name = :name where o.id = :

query.setParameter("name", "xxx");

query.setParameter("id", 2); query.executeUpdate(); em.getTransaction().commit(); em.close();

factory.close();

}

}

1.10 10、JPA中的一對多雙向關聯與級聯操作(一對多關係:一)


Order.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.OneToMany;


@Entity

public class Order {

private String orderId; private Float amount = 0f;

private Set<OrderItem> items = new HashSet<OrderItem>();


@Id //要注意:目前JPA規範並沒有提供UUID這種生成策略,目前主鍵值只提供了整型的生成方式,所

@Column(length = 12)

public String getOrderId() { return orderId;

}


public void setOrderId(String orderId) { this.orderId = orderId;

}


@Column(nullable = false) public Float getAmount() {

return amount;

}


public void setAmount(Float amount) { this.amount = amount;

}


@OneToMany(cascade = { CascadeType.REFRESH, CascadeType.PERSIST,

CascadeType.MERGE, CascadeType.REMOVE }) //設置成一對多的關係。 public Set<OrderItem> getItems() {

return items;


}


public void setItems(Set<OrderItem> items) { this.items = items;

}

}


OrderItem.java


package cn.itcast.bean;


import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;


@Entity

public class OrderItem {

private Integer id;

private String productName;

private Float sellPrice = 0f; //默認值爲0。 private Order order;


@Id

@GeneratedValue //id自增長方式生成主鍵。

public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 40, nullable = false) public String getProductName() {

return productName;


}


public void setProductName(String productName) { this.productName = productName;

}


@Column(nullable = false) public Float getSellPrice() {

return sellPrice;


}


public void setSellPrice(Float sellPrice) { this.sellPrice = sellPrice;

}


public Order getOrder() {

return order;


}


public void setOrder(Order order) { this.order = order;

}

}


在JPA裏面,一對多關係(1-n): 多的一方爲關係維護端,關係維護端負責外鍵記錄的更新(如果是條字段就負責字段的更新,如果是多對多關

系中的中間表就負責中間表記錄的更新),關係被維護端是沒有權力更新外鍵記錄(外鍵字段)的。


CascadeType的選項有,看圖:


CascadeType.REFRESH:級聯刷新,也就是說,當你剛開始獲取到了這條記錄,那麼在你處理業務過程 中,這條記錄被另一個業務程序修改了(數據庫這條記錄被修改了),那麼你獲取的這條數據就不是最新的數 據,那你就要調用實體管理器裏面的refresh方法來刷新實體,所謂刷新,大家一定要記住方向,它是獲取數 據,相當於執行select語句的(但不能用select,select方法返回的是EntityManager緩存中的數據,不是數據 庫裏面最新的數據),也就是重新獲取數據。

CascadeType.PERSIST:級聯持久化,也就是級聯保存。保存order的時候也保存orderItem,如果在數據 庫裏已經存在與需要保存的orderItem相同的id記錄,則級聯保存出錯。

CascadeType.MERGE: 級聯更新,也可以叫級聯合並;當對象Order處於遊離狀態時,對對象Order裏面 的屬性作修改,也修改了Order裏面的orderItems,當要更新對象Order時,是否也要把對orderItems的修改同 步到數據庫呢?這就是由CascadeType.MERGE來決定的,如果設了這個值,那麼Order處於遊離狀態時,會先 update order,然後for循環update orderItem,如果沒設CascadeType.MERGE這個值,就不會出現for循環 update orderItem語句。

所以說,級聯更新是控制對Order的更新是否會波及到orderItems對象。也就是說對Order進行update操作 的時候,orderItems是否也要做update操作呢?完全是由CascadeType.MERGE控制的。

CascadeType.REMOVE:當對Order進行刪除操作的時候,是否也要對orderItems對象進行級聯刪除操作 呢?是的則寫,不是的則不寫。


如果在應用中,要同時使用這四項的話,可以改成cascade = CascadeType.ALL


應用場合問題:這四種級聯操作,並不是對所有的操作都起作用,只有當我們調用實體管理器的persist方法 的時候,CascadeType.PERSIST纔會起作用;同樣道理,只有當我們調用實體管理器的merge方法的時候, CascadeType.MERGE纔會起作用,其他方法不起作用。 同樣道理,只有當我們調用實體管理器的remove方

法的時候,CascadeType.REMOVE纔會起作用。

注意: Query query = em.createQuery("delete from Person o where o.id=?1");這種刪除會不會起 作用呢?是不會起作用的,因爲配置裏那四項都是針對實體管理器的對應的方法。

1.11 11、JPA中的一對多延遲加載與關係維護(一對多關係:二)


order.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.OneToMany; import javax.persistence.Table;


@Entity

@Table(name="orders") //把表名改成orders(默認表名是order),防止默認表名order與數據庫的關鍵字"or public class Order {

private String orderId; private Float amount = 0f;

private Set<OrderItem> items = new HashSet<OrderItem>();


@Id //要注意:目前JPA規範並沒有提供UUID這種生成策略,目前主鍵值只提供了整型的生成方式,所

@Column(length = 12)

public String getOrderId() { return orderId;

}


public void setOrderId(String orderId) { this.orderId = orderId;

}

@Column(nullable = false)

public Float getAmount() {

return amount;


}


public void setAmount(Float amount) { this.amount = amount;

}


@OneToMany(cascade = { CascadeType.REFRESH, CascadeType.PERSIST,

CascadeType.MERGE, CascadeType.REMOVE },fetch=FetchType.LAZY,mappedBy="

//mappedBy="order",中的order是關係維護端的order屬性,這個order屬性的類型是這個bean。 public Set<OrderItem> getItems() {

return items;


}

/*

@OneToMany(fetch=FetchType.)的選項有,如下圖: FetchType.EAGER:代表立即加載; FetchType.LAZY:代表延遲加載。


當我們把fetch設置爲FetchType.LAZY的時候,什麼時候初始化items裏面的數據呢?當我們第一次訪問這 如果沒有設置fetch這屬性的話,會怎麼樣呢?是立即加載?還是延遲加載呢? 記住@OneToMany這個標籤最後的英文單詞,如果是要得到Many的一方,我不管你前面是什麼,只要後面的單 反過來,如果後面是One呢?因爲它是加載一的一方,這對性能影響不是很大,所以它的默認加載策略是立即


mappedBy:我們怎麼知道關係的維護端和被維護端呢?當然JPA規範規定多的一端應該是爲維護端(關係維 orderItem這邊由哪一個屬性去維護關係呢?是OrderItem類的order屬性。 mappedBy屬性對應Hibernate裏面的inverse屬性:<SET name="items" inverse="true"></SET>


*/


public void setItems(Set<OrderItem> items) { this.items = items;

}


//用這個方法會方便很多

public void addOrderItem(OrderItem orderItem){

orderItem.setOrder(this); //關係維護方orderItem加入關係被維護方(this)後,才能維

this.items.add(orderItem);

}

}



OrderItem.java


package cn.itcast.bean;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.JoinColumn; import javax.persistence.ManyToOne;


@Entity

public class OrderItem {

private Integer id;

private String productName;

private Float sellPrice = 0f; //默認值爲0。 private Order order;


@Id

@GeneratedValue //id自增長方式生成主鍵。 public Integer getId() {

return id;

}


public void setId(Integer id) { this.id = id;

}


@Column(length = 40, nullable = false) public String getProductName() {

return productName;


}


public void setProductName(String productName) { this.productName = productName;

}


@Column(nullable = false) public Float getSellPrice() {

return sellPrice;


}


public void setSellPrice(Float sellPrice) { this.sellPrice = sellPrice;

}


@ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)

@JoinColumn(name="order_id") //設置外鍵的名稱。

public Order getOrder() { //OrderItem是關係維護端,負責關係更新,它是根據它的order屬 return order;

}

/*

@ManyToOne的級聯保存(CascadeType.PERSIST)是不需要的,不可能說你保存某個訂單項OrderItem的時 CascadeType.MERGE:如果我們更新了訂單項orderItem產品的價錢,那麼整個訂單Order的總金額是會發 CascadeType.REFRESH:如果我們想得到目前數據庫裏orderItem最新的數據的話,我們也希望得到訂單or CascadeType.REMOVE:這個屬性這裏肯定不設。就好比現在有一個訂單,一個訂單裏面有3個購物項orderI

*/

//optional:說明order這個是否是可選的?是否可以沒有的?false表示必須的,true表示是可選的。

public void setOrder(Order order) {

this.order = order;


}

}


OneToManyTest.java


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


import cn.itcast.bean.Order; import cn.itcast.bean.OrderItem;


public class OneToManyTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test

public void save() {

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") EntityManager em = factory.createEntityManager();

em.getTransaction().begin();


Order order = new Order(); order.setAmount(34f);

order.setOrderId("992"); //orderId是數據庫裏面的主鍵,同時也是實體的標識。這裏並沒

OrderItem orderItem1 = new OrderItem();

orderItem1.setProductName("足球"); orderItem1.setSellPrice(90f); OrderItem orderItem2=new OrderItem(); orderItem2.setProductName("瑜伽球"); orderItem2.setSellPrice(30f); order.addOrderItem(orderItem1); order.addOrderItem(orderItem2);


em.persist(order); em.getTransaction().commit(); em.close();

factory.close();

}


}


在junit test裏面,可以通過下面代碼來生成數據庫表,根據數據庫表來確定元數據是否定義成功:


@Test

public void save() {

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast"); factory.close();

}


運行junit測試,發現保存訂單Order的時候,也保存了訂單項OrderItem.爲什麼呢?是因爲訂單Order和訂 單項OrderItem定義了級聯保存(CascadeType.PERSIST)關係,這個級聯關係在我們調用em.persist(order); 的persist方法時就會起作用。

1.12 12、JPA中的一對一雙向關聯


IDCard.java


package cn.itcast.bean;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.OneToOne;


@Entity

public class IDCard {

private Integer id; private String cardNo; private Person person;


public IDCard() {

}


public IDCard(String cardNo) { this.cardNo = cardNo;

}


@Id

@GeneratedValue

public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 18,nullable=false) public String getCardNo() {

return cardNo;


}


public void setCardNo(String cardNo) { this.cardNo = cardNo;

}


@OneToOne(mappedBy = "idCard", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH } /*,optional = false*/)

public Person getPerson() { return person;

}

/*

mappedBy:如何把IDCard指定爲關係被維護端? 就是通過這屬性。使用了這屬性的類,就是關係被維護端


cascade:

CascadeType.REMOVE:刪除身份證,需要把這個人幹掉嗎? 不用,所以這個屬性不設。 CascadeType.PERSIST:一出生就有身份證號。 CascadeType.MERGE:在遊離狀態的時候,修改了身份證號碼,需要對人員的信息進行修改麼?如 CascadeType.REFRESH:重新獲取idCard的數據的時候,需不需要獲取person的數據呢?

這些級聯的定義,一定是根據你們的業務需求來定的。用不用是根據你的業務來決定的,業務需要就用,業務 optional:是否可選,是否允許爲null?反映在業務上,就是有身份證,是否一定要有這個人呢? 因為在Person裏已經指定了idCard是必須要存在的,外鍵由person表維護,那麼這裏這個屬性就是可選的的 外鍵由Person的option屬性決定,就算你設置了這屬性,其實它也不看你這屬性。在設外鍵字段是否允許爲 fetch:加載行爲默認爲立刻記載,憑one。

*/


public void setPerson(Person person) { this.person = person;

}

}

Person.java


package cn.itcast.bean;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.JoinColumn; import javax.persistence.OneToOne;


@Entity

public class Person {

private Integer id; private String name; private IDCard idCard;


public Person() {

}


@Id

@GeneratedValue

// 採用數據庫Id自增長方式來生成主鍵值。 public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false) public String getName() {

return name;


}

public void setName(String name) { this.name = name;

}


@OneToOne(optional = false, cascade = CascadeType.ALL)

@JoinColumn(name = "idCard_id") public IDCard getIdCard() {

return idCard;


}


public void setIdCard(IDCard idCard) { this.idCard = idCard;

}


public Person(String name) { this.name = name;

}

}


OneToOneTest.java


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


import cn.itcast.bean.IDCard; import cn.itcast.bean.Person;

public class OneToOneTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test

public void save() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


Person person = new Person("老張"); // person是關係維護端。 person.setIdCard(new IDCard("1112222")); // 通過person把idCard放進去,這關 em.persist(person); // 先保存idCard,得到保存記錄的id,用id作爲外鍵的值,再保存p


em.getTransaction().commit(); em.close();

factory.close();

}


}


誰是關係維護端,誰就負責外鍵字段的更新。 Person是關係維護端,IDCard是關係被維護端,怎麼維護更新呢?往Person裏面設置idCard,這樣就相當於

把關係建立起來了;如果通過IDCard設置person的話,那麼這種關係是建立不起來的,因爲IDCard是關係被維 護端


idcard表結構,看圖:


person表結構,看圖:


1.13 13、JPA中的多對多雙向關聯實體定義與註解設置


Student.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany;


@Entity

public class Student {

private Integer id; private String name;

private Set<Teacher> teachers = new HashSet<Teacher>();


@Id @GeneratedValue //id作爲實體標識符,並且採用數據庫id自增長的方式生成主鍵值。 public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false)

public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}


@ManyToMany(cascade = CascadeType.REFRESH)

@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_i public Set<Teacher> getTeachers() {

return teachers;


}

/*

假如不對關聯表裏的字段做任何設定,那麼表裏面的字段默認由JPA的實現產品來幫我們自動生成 inverseJoinColumns:inverse中文是反轉的意思,但是覺得太噁心了,在JPA裏,可以理解爲被 inverseJoinColumns:被維護端外鍵的定義。

@JoinColumn:外鍵,設置中間表跟teacher表的主鍵關聯的那個外鍵的名稱。 joinColumns:關係維護端的定義。

*/


public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers;

}

}


Teacher.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.ManyToMany;


@Entity

public class Teacher {

private Integer id; private String name;

private Set<Student> students=new HashSet<Student>();


@Id @GeneratedValue //id作爲實體標識符,並且採用數據庫id自增長的方式生成主鍵值。 public Integer getId() {

return id;


}

public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false) public String getName() {

return name;


}

public void setName(String name) { this.name = name;

}


@ManyToMany(cascade=CascadeType.REFRESH,mappedBy="teachers")

//mappedBy="teachers",表示關係由Student對象維護。"teachers"與Student對象的teachers屬性對應 public Set<Student> getStudents() {

return students;


}

/*

cascade:

CascadeType.PERSIST:級聯保存不要,學生沒來之前,老師就已經在了。 CascadeType.MERGE:級聯更新不要,把學生的信息改了,沒必要修改相應的老師的信息,壓根就

CascadeType.REMOVE:級聯刪除更不要,如果雙方都設了級聯刪除,加入刪除學生,會刪除相應

這裏只需設置級聯刷新CascadeType.PERSIST就可以了,事實上refresh方法也很少使用。 mappedBy: 通過這個屬性來說明老師是關係被維護端。

fetch: 加載行爲默認是延遲加載(懶加載),憑Many。 這裏不需要設置。

*/


public void setStudents(Set<Student> students) { this.students = students;

}

}


ManyToManyTest.java


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


public class ManyToManyTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}

@Test

public void save() {

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") factory.close();

}

}


雙向多對多關係是一種對等關係,既然是對等關係,也就是說我們可以人爲決定誰是關係維護端,誰是關係 被維護端,這裏選學生做關係維護端。那麼以後就只能通過學生來維護老師跟學生的關係。


假設:

老師A id是1 學生B id是1

那通過什麼東西把他們的關係確立起來呢?採用什麼來存放他們的關聯關係呢?是中間表(關聯表)。


學生A和老師B建立起關係,首先要找到關係維護端,是學生,就要通過學生這個關係維護端,學生 A.getTeachers().add(Teacher);這樣就能把老師跟學生的關係確立起來了。確立起來後,反應在中間表裏面 就是insert into...一條語句

如果學生A要把老師B開掉,那就要解除關係,也是通過關係維護端學生A,反映在面向對象的操作就是 學 生A.getTeachers().remove(Teacher);執行這句代碼的時候,在底層JDBC它會對中間表做delete from...語句的 操作。

我們都是通過關係維護端來進行操作的,以後在雙向關係中一定要找準誰是關係維護端,誰是關係被維護端


@JoinTable的註解有:看圖,


1.14 14、JPA中的多對多雙向關聯的各項關係操作


目錄結構,看圖:



Student.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany;


@Entity

public class Student {

private Integer id; private String name;

private Set<Teacher> teachers = new HashSet<Teacher>();

public Student() {

}


public Student(String name) { this.name = name;

}


@Id

@GeneratedValue

// id作爲實體標識符,並且採用數據庫的id自增長方式生成主鍵值。 public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false) public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}


@ManyToMany(cascade = CascadeType.REFRESH)

@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_i public Set<Teacher> getTeachers() {

return teachers;


}


/*

假如不對關聯表裏的字段做任何設定,那麼表裏面的字段默認由JPA的實現產品來幫我們自動生成。 inverseJoinColumns:inverse中文是反轉的意思,但是覺得太噁心了,在JPA裏,可以理解爲被維護端

inverseJoinColumns:被維護端外鍵的定義。

@JoinColumn:外鍵名稱(中間表跟teacher表的主鍵關聯的那個外鍵名稱)。 joinColumns:關係維護端的定義。

*/


public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers;

}


public void addTeacher(Teacher teacher) { this.teachers.add(teacher);

}


public void removeTeacher(Teacher teacher) {

if(this.teachers.contains(teacher)){ //憑什麼判斷teacher在集合teachers中呢?是根 this.teachers.remove(teacher);

}

}

}


Teacher.java

package cn.itcast.bean; import java.util.HashSet;

import java.util.Set;


import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity;

import javax.persistence.GeneratedValue; import javax.persistence.Id;

import javax.persistence.ManyToMany;

@Entity

public class Teacher {

private Integer id; private String name;

private Set<Student> students = new HashSet<Student>();


public Teacher() {

}


public Teacher(String name) { this.name = name;

}


@Id

@GeneratedValue

// id作爲實體標識符,並且採用數據庫的id自增長方式生成主鍵值。 public Integer getId() {

return id;


}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false) public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}


@ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "teachers") public Set<Student> getStudents() {

return students;


}

/*

cascade: CascadeType.PERSIST:級聯保存不要,學生沒來之前,老師就已經在了。 CascadeType.MERGE:級聯更新不要,把學生的信息改了,沒必要修改相應的老師的信息,壓根就沒這業 CascadeType.REMOVE:級聯刪除更不要,如果雙方都設了級聯刪除,加入刪除學生,會刪除相應的老師, 所以在多對多關係中,級聯刪除通常是用不上的 這裏只需設置級聯涮新CascadeType.PERSIST就可以了 mappedBy: 通過這個屬性來說明老師是關係被維護端 fetch: 加載行爲默認是延遲加載(懶加載),憑

*/


public void setStudents(Set<Student> students) { this.students = students;

}


@Override

public int hashCode() {

final int prime = 31; int result = 1;

result = prime * result + ((id == null) ? 0 : id.hashCode());

//判斷的依據是,如果id不爲null的話,就返回id的哈希碼。 return result;

}


@Override

public boolean equals(Object obj) { if (this == obj)

return true; if (obj == null)

return false;

if (getClass() != obj.getClass()) return false;

final Teacher other = (Teacher) obj; if (id == null) {

if (other.id != null)

return false;

} else if (!id.equals(other.id)) return false;

return true;

}

}


ManyToManyTest.java


package junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


import cn.itcast.bean.Student; import cn.itcast.bean.Teacher;


public class ManyToManyTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test

public void save() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


em.persist(new Student("小張同學")); em.persist(new Teacher("李勇老師"));

em.getTransaction().commit();

em.close(); factory.close();

}


/*

* 建立學生跟老師的關係

*/

@Test

public void buildTS() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


Student student = em.find(Student.class, 1); // 首先要得到學生,因爲學生是關係 student.addTeacher(em.getReference(Teacher.class, 1)); //這方法在業務意義上,就

//所謂建立跟老師的關係,無非就是把老師加進集合裏面去。

//建立關係,體現在JDBC上面,就是添加一條記錄進中間表。


em.getTransaction().commit(); em.close();

factory.close();

}


/*

* 解除學生跟老師的關係

*/

@Test

public void deleteTS() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


Student student = em.find(Student.class, 1); // 首先要得到學生,因爲學生是關係 student.removeTeacher(em.getReference(Teacher.class, 1)); //這方法在業務意

//所謂解除跟老師的關係,無非就是把老師從集合裏面刪去。

//解除關係,體現在JDBC上面,就是在中間表刪除一條記錄。


em.getTransaction().commit(); em.close();

factory.close();

}


/*

* 刪除老師,老師已經跟學生建立起了關係(錯誤寫法)

*/

@Test

public void deleteTeacher1() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


em.remove(em.getReference(Teacher.class, 1));

//並不需要發生數據裝載行爲,只需要一個託管狀態的實體,所以用getReference可以提供性能


em.getTransaction().commit(); em.close();

factory.close();

}

/*


該方法會出錯,因爲中間表中已經有記錄了,會拋出以下錯誤: Caused by: java.sql.BatchUpdateException:

Cannot delete or update a parent row: a foreign key constraint fails (`itcast/student_teacher`, CONSTRAINT `FKD4E389DE1D49449D` FOREIGN KEY (`teacher_ REFERENCES `teacher` (`id`))

關係被維護端沒有權力更新外鍵,所以不會刪除中間表的記錄。

*/


/*

* 刪除老師,老師已經跟學生建立起了關係(正確寫法)

*/

@Test

public void deleteTeacher2() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


Student student = em.find(Student.class, 1);

Teacher teacher = em.getReference(Teacher.class, 1);

//並不需要發生數據裝載行爲,只需要一個託管狀態的實體,所以用getReference可以提供性能 student.removeTeacher(teacher);

//student是關係維護端,有權利刪除外鍵,只要在對象中刪除了teacher,那麼中間表中相關外

//想要刪除teacher記錄,必須先通過student解除關係才行。 em.remove(teacher);


em.getTransaction().commit(); em.close();

factory.close();

}


/*

* 刪除學生,老師已經跟學生建立起了關係

*/

@Test

public void deleteStudent() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


Student student = em.getReference(Student.class, 1);

em.remove(student); //這樣是可以刪除學生的,儘管目前是有關係,中間表有關聯記錄,


em.getTransaction().commit(); em.close();

factory.close();

}

}

1.15 15、JPA中的聯合主鍵


兩個或多個字段組成的主鍵,我們叫聯合主鍵。在面向對象中,我們用JPA怎麼定義這種情況呢? 怎麼定義聯合主鍵?用面向對象的思想來思考的話,聯合主鍵裏的複合主鍵(字段),可以把它看成一個整

體,然後採用一個主鍵類來描述這個複合主鍵的字段。

關於聯合主鍵類,大家一定要遵守以下幾點JPA規範: 1. 必須提供一個public的無參數構造函數。

2. 必須實現序列化接口。

3. 必須重寫hashCode()和equals()這兩個方法。這兩個方法應該採用複合主鍵的字段作爲判斷這個對象是 否相等的。

4. 聯合主鍵類的類名結尾一般要加上PK兩個字母代表一個主鍵類,不是要求而是一種命名風格。


ArtLinePK.java


package cn.itcast.bean; import java.io.Serializable;

import javax.persistence.Column; import javax.persistence.Embeddable;


@Embeddable

//這個註解代表ArtLinePK這個類是用在實體裏面,告訴JPA的實現產品:在實體類裏面只是使用這個類定義的屬性。

//簡單的理解爲:ArtLinePK裏的屬性可以看成是ArtLine類裏的屬性,好比ArtLinePK的屬性就是在ArtLine裏定義 public class ArtLinePK implements Serializable{

private String startCity; private String endCity;


public ArtLinePK() {

}

public ArtLinePK(String startCity, String endCity) { this.startCity = startCity;

this.endCity = endCity;


}


@Column(length=3)

public String getStartCity() { return startCity;

}


public void setStartCity(String startCity) { this.startCity = startCity;

}


@Column(length=3)

public String getEndCity() { return endCity;

}


public void setEndCity(String endCity) { this.endCity = endCity;

}


@Override

public int hashCode() {

final int prime = 31; int result = 1;

result = prime * result + ((endCity == null) ? 0 : endCity.hashCode()); result = prime * result

+ ((startCity == null) ? 0 : startCity.hashCode());


return result;

}


@Override

public boolean equals(Object obj) { if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass()) return false;

final ArtLinePK other = (ArtLinePK) obj; if (endCity == null) {

if (other.endCity != null)

return false;

} else if (!endCity.equals(other.endCity)) return false;

if (startCity == null) {

if (other.startCity != null) return false;

} else if (!startCity.equals(other.startCity)) return false;

return true;

}


}


這個聯合主鍵類,應該作爲實體類的一個主鍵(實體標識符,id)。 ArtLine.java

package cn.itcast.bean;


import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity;


@Entity

public class ArtLine {

private ArtLinePK id; //用面向對象的思想去思考的話,這個複合主鍵看成一個整體,由複合主鍵類

private String name;


public ArtLine() {

}


public ArtLine(ArtLinePK id) { this.id = id;

}


public ArtLine(String startCity, String endCity, String name) { this.id = new ArtLinePK(startCity, endCity);

this.name = name;


}


@EmbeddedId //按照JPA規範要求,我們並不是用@Id來標註它。

//@EmbeddedId 這個註解用於標註id這個屬性爲實體的標識符,因爲我們使用的是複合主鍵類,所以我們要 public ArtLinePK getId() {

return id;


}


public void setId(ArtLinePK id) { this.id = id;

}


@Column(length=20)

public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}

}

我們的複合主鍵類,目前爲止,已經定義好了。定義好了之後,接着我們就看它生成的數據庫表是否滿足複合

主鍵這麼一種情況。 ArtLineTest.javapackage junit.test;


import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence;


import org.junit.BeforeClass; import org.junit.Test;


import cn.itcast.bean.ArtLine; public class ArtLineTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test public void save(){

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();


em.persist(new ArtLine("PEK","SHA","北京飛上海"));


em.getTransaction().commit(); em.close();

factory.close();

}

}


字段生成了,看圖:



看主鍵,兩個字段,看圖:符合主鍵的定義就OK:



數據也添加進去了,看圖:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章