1.10 10、JPA中的一對多雙向關聯與級聯操作(一對多關係:一)
1.11 11、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
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(本地事務)有什麼區別呢? 在某些應用場合,只能使用全局事務,比如:
有兩個數據庫:
-
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後給大家介紹。 企業面試時被問到就要注意了(事務類型有 哪幾種?分別用在什麼場景下?)
寫實體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 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),以後要學會怎樣排錯。
映射元數據是什麼樣的?不設置默認的情況下: 實體類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.) 的選項。 看圖:
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的教程裏也經常遇到這問 題。
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);
persist這方法在Hibernate裏也存在,Hibernate的作者已經不太推薦大家用save方法
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);這方法是用於把在遊離狀態時候 的更新同步到數據庫。
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實現代碼就可以觀察出來
查詢語言(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方法時就會起作用。
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表結構,看圖:
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的註解有:看圖,
目錄結構,看圖:
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();
}
}
兩個或多個字段組成的主鍵,我們叫聯合主鍵。在面向對象中,我們用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:
數據也添加進去了,看圖: