EJB3.0筆記-1 Entity回調和監聽器
在你執行EntityManager的persist()、merge、remove和find方法時,或者在執行EJB QL查詢時,一系列預先定義好的生命週期事件會被觸發。Java Persistence規範允許在entity class上設置回調方法,當這些事件發生時,該entity會收到相應的通知。也可以註冊一些單獨的監聽類來攔截這些事件
回調事件
它們代表了entity生命週期中的一個階段(pre表示之前,post表示之後)
@javax.persistence.PrePersist; @javax.persistence.PostPersist;
@javax.persistence.PreUpdate; @javax.persistence.PostUpdate;
@javax.persistence.PreRemove; @javax.persistence.PostRemove; @javax.persistence.PostLoad(在查詢數據或refresh()時會觸發)
Entity Class上的回調方法
通過爲entity bean 的任何一個public、private、protected或package-protected方法添加註解,當某個接受託管的entity實例觸發事件時,entity manager會調用entity bean上被添加了註解的相應方法。回調方法必須返回void,不拋出checked exception,並無傳入參數。如:
@PostPersist void afterInsert(){………}
等價的ORM映射文件
<entity class=”com.titan.domain.Cabin”>
<post-persist name=” afterInsert”/>
</entity>
Entity 監聽器
Entity監聽器是一種能夠攔截entity回調事件的類。它本身不是entity class,它必須有一個公有無參構造函數,監聽方法必須返回void,並接受一個Object類型的參數,它就是觸發事件的entity實例
public class TitanAuditLogger{
@PostPersist void postInsert(Object entity){……..}
}
通過@javax.persistence.EntityListeners註解把監聽器應用於某個entity class
@Entity
@EntityListeners{ TitanAuditLogger.class,其它監聽器}
public class Cabin{….}
等價XML
<entity class=”com.titan.domain.Cabin”>
<entity-listeners>
<entity-listener class=”com.titan.listeners.TitanAuditLogger”>
<post-resist name=” postInsert”/>
</ entity-listener >
</entity-listeners >
</entity>
Entity監聽器的執行順序是它們在@EntityListeners註解或ORM XML映射文件中的聲明順序,而任何在entity class上聲明的回調方法則會在其後被調用
默認的Entity監聽器
可以在ORM映射文件中,通過頂層元素<entity-mappings>下的<entity-listeners>元素,爲persistence unit中的每一個entitty class指定一組默認的entity監聽器,如:
<entity-mappings>
<entity-listeners>
<entity-listeners>
<entity-listener class=”com.titan.listeners.TitanAuditLogger”>
<post-resist name=” postInsert”/>
</ entity-listener >
</entity-listeners >
</entity-listeners>
</entity-mappings>
如果想關閉默認的entity監聽器,可以使用@javax.persistence.ExcludeDefaultListeners註解
@Entity
@ExcludeDefaultListeners
public class Cabin{….}
等價的XML
<entity class=”com.titan.domain.Cabin”>
<exclude-default-listeners/>
</entity>
繼承與監聽器的關係
如果在一個實體的繼承層次中,entity監聽器被用於基類,則所有子類都會繼承基類的監聽器。基類的監聽器會先於子類的監聽器執行,如果想關閉從父類繼承下來的entity監聽器,可以使用@javax.persistence.ExcludeSupperclassListeners,如:
@Entity
@ExcludeSupperclassListeners
public class Customer extends Person{……}
EJB 3.0筆記-3資源管理和基本服務
EJB服務器
EJB服務器支持6種基本服務:併發(concurrency)、事務管理(transaction management)、持久化(persistence)、對象分佈(object distribution)、命名(naming)和安全(security)。它還提供兩個額外的服務:異步消息服務(asynchronous messaging)和定時服務(timer service)
資源管理
EJB提供了兩種管理大量bean實例的機制:實例池化(instance pooling)和激活(activation)
實例池化
通過建立數據庫連接池使系統中的業務對象可以共享數據庫訪問是一種常見的技術。實例池之所以可 行,是因爲客戶端從來不會直接訪問bean實例,服務器只需維護足以完成工作的少量bean實例,並重復 使用這些實例爲不同的請求提供服務就可以了。
Stateless session bean的生命週期
stateless session bean存在以下三種狀態
無狀態(No state):尚未被初始化
池狀態(Pooled state):已經被容器初紿化
就緒狀態(Ready state):已經與一個EJB請求關聯,做好響應方法調用的準備
EJB服務器爲已經部署在其中的每個stateless session bean都維護了一個實例池,池中的每個實例都是 等價的。容器接收到一個來自客戶端的請求,就從池中挑選一個實例爲其服務,一旦完成請求,容器就 會將此實例重新放回到實例池中
Message-driven bean和實例池化
由於MDB和stateless session bean一樣,不爲特定的客戶端請求維護狀態,所以MDB也可以使用實例池 技術。在大多數EJB容器中,每個MDB都有自己的實例池
激活機制
stateful session bean會在不同的方法調用之間維護會話狀態,它使用激活而不是實例池來降低資源的消 耗。當EJB服務器需要節省資源時,收回stateful session bean,釋放它所佔有的內在資源,並將其所保 持的會話狀態序列化到輔助存儲器中,如果客戶端對該EJB對象再次發出請求,EJB容器會重新實例化一 個stateful session bean,並從輔助存儲器中將之前的狀態恢復。
鈍化(passivation)
解除stateful bean實例與相關EJB對象間的關聯,並保存其狀態的一種操作,當bean被鈍化後,就可以將 它從EJB對象和內在中移除。而激活是將bean實例的狀態重新恢復到一個新的bean中。在EJB中,當 bean被激活之後,其非持久數據成員並不一定會被設置爲對應類型的默認值,有可能保留原值,也可能 是任意值,這取決於EJB的實現,在stateful bean中使用非持久數據成員需格外小心。可以使用
@javax.ejb.PostActivate註解,當bean被成功激活後,用它來重置非持久數據成員的初始值
Java EE 連接器體系結構
Java EE連接器體系結構定義了java EE容器和企業信息系統(EIS)之間的接口。EJB3.0要求容器支持推送 (客戶端沒有發送請求,容器向客戶端推送數據)模式的Java EE連接體系架構,即JCA1.5
基本服務
併發
併發控制是通過用鎖來幫助保護數據而實現的。鎖控制着多個用戶如何同時訪問和更改共享數據而不會 彼此衝突。不可能併發訪問到session bean,所以容器不用對他們做併發控制。EJB規範禁止在bean中 使用synchronized關鍵字控制併發,以提高bean的性能,同時禁止bean創建自己的線程,因爲這會影響 到容器跟蹤控制bean的能力。Entity bean代表可被共享和併發訪問的數據,EJB通過事務隔離設置來控 制對entity bean的併發訪問。
Message-driven bean的併發控制
對於MDB,併發意味着同時處理多條信息,EJB容器使用MDB實例池,併發處理接收到的信息
事務
事務是一個工作單元,它是原子性的,即事務中的所有任務必須全部完成,才認爲事務是成功的。EJB容 器是自動管理事務的。事務從一個方法調用開始,並會傳播到該方法內的其他方法
持久化
entity bean是持久化的,亦即它的狀態是保存到數據庫中的,bean通過EntityManager服務與持久存儲器 關聯。java Persistence模型是基於普通java模型的,它提供了豐富的關係數據庫映射、繼承映射、多表 映射來達到從對象到關係數據庫的持久化,它還爲entity bean定義了關係數據成員,用來描述bean之間 的關係,比如:一對一、一對多和多對多
分佈式對象
即可以處理分佈在不同地點的客戶端請求
EJB3.0要求所有服務器都必須支持java RMI-IIOP協議(使用CORBA IIOP協議的java RMI API),規範還要 求EJB服務器通過JAX-RPC API支持SOAP1.2協議。服務器必須支持java客戶端使用java EJB客戶端API對 EJB的訪問,EJB還允許服務器支持非JAVA的客戶端對bean的訪問(通過基於JAX-RPC的EJB-SOAP映 射)。
異步企業消息服務
支持企業級消息機制,要求EJB容器經過規定路線將消息可靠地從JMS客戶端發送到JMS-MDB。所謂的 異步是指客戶端向服務器發送信息後,不要求必須得到服務器的響應才進行下一步的工作。
EJB定時服務
EJB定時服務可以用於安排在特定的時刻向enterprise bean發送通知,它可以用於entity bean、stateless
session bean 和message-driven bean
命名服務
命名服務本質是爲客戶端提供一種定位分佈式對象和資源的機制。命名服務必須支持對象綁定,並提供 查找API。Enterprise JavaBeans規定java客戶端使用JNDI作爲查找API
安全
EJB服務器支持三種類型的安全
認證(authentication):檢驗用身份。最常見的就是要求輸入用戶名和密碼。
授權(authorization):授權也叫訪問控制,用於管理用戶可以訪問的資源
安全通信(secure communication):對客戶端和服務器之間的通信內容進行加密。
基本服務和互操作性
EJB規範要求容器支持java rmi-iiop遠程調用,還要求支持JAX-RPC遠程調用,使用JAX-RPC需要支持 SOAP和WSDL,這是web service的行業規範。這些遠程調用期間,要保證對事務處理、命名服務和安全 服務之些基本服務的支持。IIOP提供了可行的操作能力,但並沒有獲得很大成功,目前業界更傾向於使用 基於SOAP和WSDL的web service
事務提交互操作可以確保java ee web組件發起的事務可以被正確傳播到其他容器的enterprise bean上。
EJB規範指定容器使用CORBA CosNaming作爲可互操作的命名服務。
EJB爲安全服務提供了互操作能力,可以指定容器間如何建立信任關係,以及如何交換安全憑證。
EJB 3.0筆記-4編寫你的第一組bean
開發Entity bean
在Java Persistence中,實體是一個標註了O/R映射元數據的普通java對象。Bean class實現了java.io.Serializable接口,但這並非是必須的,如果實體實現了Serializable,就可以用作session bean中遠程接口方法的參數和返回值。註解@Table和@Column這樣的表映射並非是必需的,如果省略,其默認值分別爲類的非限定名稱和成員屬性名稱,但是主鍵標識是必需的(使用@Id或使用XML聲明)。
persistence.xml文件
配置與EntityManager相關的信息。位於METAINF目錄下。session bean使用註解@javax.persistence.PersistenceContext來獲得對EntityManager服務的訪問
開發session bean
如果要bean允許客戶端遠程調用,可以先定義一個遠程接口,標註@javax.ejb.Remote,然後再實現這個接口。
titan.jar:JAR文件
JAR(java Archive)是基於zip文件格式和zlib壓縮標準的,他將組件和其他軟件進行“壓縮-包裝”以便發佈給第三方。打包命令:jar cf titan.jar com/tian/domain
Object ref = jndiContext.lookup("TravelAgentRemote");
TravelAgentRemote dao = (TravelAgentRemote)
PortableRemoteObject.narrow(ref,TravelAgentRemote.class);
//遠程接口要使用narrow()方法。得到這個引用後,就可以使用它的業務方法。
} catch (javax.naming.NamingException ne){
ne.printStackTrace();
}
}
//使用jndi.properties文件,不用顯式設置屬性
public static Context getInitialContext()throws javax.naming.NamingException {
return new javax.naming.InitialContext();
}
}
EJB 3.0筆記-5持久化服務EntityManager
在舊版本的Java EE中,EJB規範涵蓋了持久層的相關內容,而在Java EE 5中,持久層已經被剝離出來了,成爲一個獨立的規範:java Persisence 1.0。它可以自動完成Java對象與關係數據庫的映射。所有的持久化操作現在都要藉助 javax.persistence.Entity.Manager服務來完成。EntityManager在一組固定的實體類與底層數據源之間進行O/R映射的管理,它提供了創建、查找、同步對象以及將對象插入數據庫的API,java Persisence可以與java EE及EJB結合在一起,也可以在普通的java程序中使用。
實體即POJO
實體即普通的Java對象。當它與EntityManager關聯時,entity bean class的實例纔會變成持久對象。
託管與非託管實體
一個entity bean要麼受entity manager託管(也稱attached),要麼不受託管。一旦對象解除了與entity manager的關聯,entity manager就不再跟蹤entity class的狀態。
持久上下文
Persistence context是由一組受託管的實體對象實例所構成的集合。Entity manager追蹤persistence context中所有對象的修改和更新情況。一旦對象從persistence context中脫離,或persistence context關閉,對象就不再受entity Manager管理了。persistence context有兩種類型,分別是transaction-scoped persistence context和extended persistence context。transaction-scoped persistence context只在事務範圍內存在,它們會在事務結束後被關閉。可以使用@PersistenceContext註解標註EntityManager實例,表示該entity manager管理事務規範的persistence context。extended persistence context表示超出事務範圍的持久化上下文,與此關聯的對象會在多個事務間保持託管狀態。
遊離實體(datached entities)
當transacton scope persistence context或extended persistence context結束之後,實體的就會不受託管而處於遊離狀態。
Persistence Unit
每個Entity Manager負責管理一組類映射到數據庫中,這組類就是persistence unit。它是通過persesence.xml文件定義的,persesence.xml文件位於META-INF目錄中。下面是一個persistence.xml各元素解釋:
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<!—name定義了unit的引用名; transaction-type 表示事務類型,可以取值JTA(java transaction api,Java EE環境默認取值)或RESOURCE_LOCAL(java SE環境使用javax.persistence.EntityTransaction API管理事務)
-->
<persistence-unit name="titan" transaction-type="RESOURCE_LOCAL">
<description>PU的描述
<provider>指定實現javax.persistence.PersistenceProvider接口的類
<jta-data-source>事務類型爲JTA時的數據源
<non-jta-data-source>事務類型爲RESOURCE_LOCAL時的數據源
<properties>persistence provider廠商的專有屬性,可以配置數據源等
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/
</properties>
<mapping-file>映射文件
</persistence-unit>
</persistence>
Persistence Unit中類的集合
PU管理的類的集合。集合中的類由以下因素決定:
1.包含persistence.xml的JAR文件中標有@Entity註解的類。如果指定<exclude-unlisted-classes/>persistence provider將不掃描此jar包
2.<jar-file>../lib/customer.jar</jar-file>指定JAR包中標有@Entity註解的類
3.在META-INF/orm.xml文件中映射的類
4<mapping-file>指定xml文件中映射的類
5.<class>com.tian.domain.Customer</class>列出的類
獲取Entity Manager
得到EM,就可以使用它來完成對PU中類的數據庫操作。
EntityManagerFactory
在java SE中,可以使用它來獲得EM。它使用方法EntityManager createEntityManager();或EntityManager createEntityManager(java.util.Map map);來創建EM,其中map用來覆蓋或擴展persistence.xml中的聲明的廠商專有屬性。
在java SE中獲取EntityManagerFactory
使用javax.persistence.Persistence來創建EMF。它使用方法public static createEntityManagerFactory(String unitName)或public static createEntityManagerFactory(String unitName,java.util.Map properites)來創建EMF,其中properites用於覆蓋或添加在persistence.xml的<properties>元素中定義的廠商專有屬性
在Java EE中獲取EntityManagerFactory
可以使用@javax.persistence.PersistenceUnit標註EntityManagerFactory成員變量,由容器把EMF實例注入到成員變量。
@javax.persistence.PersistenceUnit(unitName=”CRM”)
private EntityManagerFactory factory;
獲取persistence context
使用EntityManagerFactory. createEntityManager()方法,得到一個extended persistence context。如果使用JTA管理事務,要調用EntityManager.jonTransaction()方法把EM參與事務,如果使用EJB容器管理的persistence context,就不用這一步了。可以使用@javax.persistence.PersistenceContext註解,把EM實例注入EJB中。
@PersistenceContext(unitName=”titan” type=PersistenceContextType.EXTENDED)
private EntityManager manager;
操作EntityManager
使用EntityManager API包含了有關實體的一系列數據庫操作方法。
持久化實體
使用entityManager.persist(cust);方法持久化一個對象,當它被調用時,java persistence可以自動生成主鍵。在transaction-acoped persistence context裏,事務範圍外調用persist()會引起TransactionRequiredException異常
查找實體
使用find()和getReference()方法。在無法從數據庫中找到指定的實體時,find()方法會返回null;而getReference()會拋出javax.persistence.EntityNotFoundException異常
查詢
可以使用EJB QL語句來查詢,這時要調用EntityManager的createQuery()、createNamedQuery()或createNativeQuery()來創建查詢
更新實體
在實體處於託管狀態時,任何更改都將被自動(取決於flush模式)或手工(通過調用flush()方法)地同步到數據庫中。
合併實體
使用EntityManager的merge()方法,可以將遊離實體合併到當前EntityManager管理的persistence context中。如果EM沒有管理過與傳入的實體主鍵相同的entity,則merger()會返回這個傳入實體的拷貝,這份拷貝受EM的管理;如果EM管理着與傳入參數相同的ID的實體,則把傳入的參數內容複製到託管的對象實例中。這兩種情況,傳入的參數都不受託管。
刪除實體
調用EntityManager.remove()可以將實體從數據庫中刪除,實例將不受entity manager託管而成爲遊離對象
刷新實體
調用refresh()方法根據數據庫的情況刷新內存中實體的狀態,如果entity bean有關聯的實體,根據設置的級聯策略,決定關聯的實體是否刷新
contains()方法與clear()方法
contains()方法判斷persistence context中是否包含該實體對象。EntityManager.clear()方法,把persistence context中所有的託管實體變成遊離對象
flush()方法和FlushModeType
調用persist()、merge()和remove方法之後,這些更改操作只有執行flush操作時才同步到數據庫中。flush操作會在相關查詢執行之前和事務提交之時自動執行,也可以手工調用flush()方法強行同步。但是通過主鍵來查詢實體(如find()和getReference())並不會引起flush。FlushModeType默認行爲爲AUTO,表示自己flush,COMMIT則表示,僅當事務提交時纔對更改做flush操作,有時這個方式會減少數據庫訪問量。可以調用setFlushMode()方法來指定EntityManager的FlushModeType
鎖定lock()
EentiyManager API同時支持讀鎖和寫鎖,鎖定行爲與事務的概念緊密關聯。
getDelegate()
該方法允許你獲得一個指向底層persistence provider對象的引用,該persistence provider實現了EntityManager接口。該方法提供了一種獲取並使用廠商專有API的途徑
Resource Local事務
在非java EE環境中無法使用JTA,因此Java Persistence API規範通過EntityTransaction接口提供了與JTA事務類似的編程接口,它可以使用EntityManager.getTransaction()獲得
EJB 3.0筆記-6映射持久對象
實體代表了數據庫中的數據,因而對entity bean的更改會導致對數據庫的更改,這便是entity bean的根本目的:爲程序員提供一種訪問和更改數據的更爲簡單的機制(相對使用SQL而言)。
保持bean實例所代表的數據與數據庫中的數據協調一致的過程被稱爲持久化。
編程模型
在java Persistence中,實體就是普通的Java類
Bean Class
一個映射到關係數據庫的普通Java對象,它必須至少擁有一個無參的構造函數,還要求有兩段元數據:註解@javax.persistence.Entity表示將該類映射到數據庫,註解@javax.persistence.Id則表示在類中哪個成員屬性會被用作主鍵。
Persistence provider會假定,類中所有其餘的成員屬性都將映射到具有相同名稱相同類型的數據庫字段上。如果將@Id註解置於getter成員函數上,那麼必須將任何用於映射的其他註解都置於類中的gettert和setter成員函數之上(有getter方法的成員才認爲是要持久化的成員);如果將@Id註解置於類的數據成員之上,Persistence provider會假定,類中的任何其他數據成員也是持久化成員屬性,並根據它們的名稱和類型自動對其進行映射
XML映射文件
XML映射文件可以替代類中的註解。缺省情況下,persistence provide會在META-INF目錄下查找名爲orm.xml的映射文件。也可以使用persistence provider的<mapping-file>元素聲明映射文件
<entity-mapping>
<entity class=”com.tian.domain.Customer” access=”PROPERTY”>
<attributes>
<id name=”id”/>
</attributes>
</entity>
</entity-mapping>
persistence provider會假定,在類中的任何其他成員屬性都是持久化成員屬性,不必都顯式定義。
基本的Schema映射
如果不能使用原有的默認映射方法,比如類成員名稱與表的名稱不相同,或者要爲類成員添加約束,就要到更爲詳細的註解。
@Table
@Table(name=”CUSTOMER_TABLE”,catalog=”TITAN” ,schema=”TITAN” ,UniqueConstraint={“ATTRIBUTE”})
XML方式
<table name=”CUSTOMER_TABLE” catalog=”TITAN” schema=”TITAN” >
<unique-constraint>
<column-name>ATTRIBUTE</column_name>
</unique-constraint>
<table/>
其中:name表示表名,catalog表示關係數據庫的catalog,shema表示關係表所屬的schema(方案),UniqueConstraint表示唯一性約束
@Column
以XML方式爲例
<column name=”CUST_ID” 列名
unique=”true”唯一性
nullable=”true”是否可以爲空
insertable=”true”允許插入該字段
updateable=”false”不允許更新
column-definition=”integer”字段類型
table=””用於多表映射
length=””string類型的成員長度
precision=””數值的總位數
scale=””小數位數
/>
主鍵
每個entity bean必須有一個主鍵,並且是唯一的。
@Id
id可以手工生成,也可以讓persistence provider自動生成,這時要使用以下註解:
@GeneratedValue
以XML爲例
<attributes>
<id name=”id”>
<generated-value strategy=”AUTO” >
<!--
strategy有四種取值
策略AUTO指明由persistence provider自動生成主鍵
IDENTITY來創建主鍵,不過要數據庫的支持
TABLE這種類型還要指定generator屬性
SEQUENCE這種類型還要指定generator屬性
-->
</id>
</attributes>
表生成器
策略TABLE要先指定一張用戶定義的關係表。如:
create table GENERATOR_TABLE(
PRIMARY_KEY_COLUMN VARCHAR not null,要生成值的主鍵名
VALUE_COLUMN long not null用於保存計數器的值
)
然後使用@javax.persistence.TableGenerator註解定義一個表生成器,如
@TableGenerator(name=”CUST_GENERATOR”
table=” GENERATOR_TABLE”生成器依賴的表名
pkColumnName=” PRIMARY_KEY_COLUMN” 對應表生成器的字段
valueColumnValue=” VALUE_COLUMN” 對應表生成器的字段
pkColumnValue=”CUST_ID”要自動生成主鍵的字段名
allocationSize=10 每次生成10個id,緩存起來使用
)
使用方式
@Id
@TableGenerator(strategy=GenerationType.TABLE,generator=” CUST_GENERATOR”)
Sequence生成器
Oracle數據庫可以使用這各生成策略。
@SequenceGenerator(name=”CUSTOMER_SEQUENCE”,生成器的名稱
sequenceName=”CUST_SEQ”,數據庫中的Sequence對象
initialValue=1,主鍵初始值
allocationSize=50遞增值
)
使用方式
@Id
@TableGenerator(strategy=GenerationType.SEQUENCE,generator=”
CUSTOMER_SEQUENCE”)
以上兩種生成器都是定義在bean class上。即@Table之下。主鍵在執行persist()方法時自動生成。
主鍵類和複合鍵
映射覆合主鍵,java persistence提供了兩種方式:@javax.persistence.IdClass註解和@javax.persistence.EmbeddedId
@IdClass
它是一個類級別的註解,指定了當與entity manager進行交互時,使用什麼主鍵類。假如客戶由姓氏和社會保險號碼組成的複合主鍵,那麼主鍵類如:
1.
public class CustomerPK implements java.io.Serializable { //1. 主鍵類必須是可以序列化的
private String lastName; //lastName 和ssn組成複合主鍵
private long ssn;
public CustomerPK() {} //2. 主鍵類必須有公有無參構造函數
public CustomerPK(String lastName, long ssn){
this.lastName = lastName;
this.ssn = ssn;
}
public String getLastName() { return this.lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public long getSsn() { return ssn; }
public void setSsn(long ssn) { this.ssn = ssn; }
public boolean equals(Object obj){//3主鍵類必須實現equals()方法
if (obj == this) return true;
if (!(obj instanceof CustomerPK)) return false;
CustomerPK pk = (CustomerPK)obj;
if (!lastName.equals(pk.lastName)) return false;
if (ssn != pk.ssn) return false;
return true;
}
public int hashCode(){//4主鍵類必須實現hashCode ()方法
return lastName.hashCode() + (int)ssn;
}
}
2.
Customer類
@Entity
@IdClass(CustomerPK.class)
public class Customer implements java.io.Serializable {
private String firstName;
private String lastName;
private long ssn;
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
@Id
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Id
public long getSsn() { return ssn; }
public void setSsn(long ssn) { this.ssn = ssn; }
}
3.
映射方式
@IdClass(CustomerPK.class)
public class……..
@Id
public String getLastName()……
@Id
public String getSsn()……..
或者
<id-class>…….CustomerPK</id-class>
<attributes>
<id name=”lastName”/>
<id name=”ssn”/>
</attributes>
4.
查詢方法
CustomerPK pk=new CustomerPK(“Burke”,9999999);
Customer cust=entityManager.find(Customer.class,pk);
@EmbeddedId
此方法將主鍵類直接嵌入到bean class中。
1.
@ EmbeddedId
首先聲明主鍵類(青色部分表示和上面的CustomerPK不同的地方)
@Embeddable
public class CustomerPK implements java.io.Serializable {
…….
@Column(name=”CUSTOMER_LAST_NAME”)
public String getLastName() { return this.lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Column(name=”CUSTOMER_SSN”)
public long getSsn() { return ssn; }
public void setSsn(long ssn) { this.ssn = ssn; }
……
}
2.在Customer 類中
@Entity
public class Customer implements java.io.Serializable {
private CustomerPK pk;
……….
@ EmbeddedId
public CustomerPK pk getPk(){……..}
public void setPK(CustomerPk pk){….}
}
如果要覆蓋原有的@Column定義,可以使用@AttributeOverrides,這時的Customer類如:
@Entity
public class Customer implements java.io.Serializable {
private CustomerPK pk;
……….
@ EmbeddedId
@ AttributeOverrides{
@ AttributeOverride(name=”lastName”,column=@Column(name=”LAST_NAME”))
}
public CustomerPK pk getPk(){……..}
public void setPK(CustomerPk pk){….}
}
成員屬性映射
Java Persistence針對Blob和Clob、可序列化對象、可內嵌對象、以及帶有成員屬性version的樂觀併發控制,都提供了映射
@Transient(瞬時成員)
對於不打算持久化的成員屬性,可以使用@ Transient註解標準成員屬性,等價的XML文件如:
<transient name=”lastName”/>
@Basic和FetchType
這是成員屬性的默認映射方式,可以映射基本數據類型及其封裝類型的成員屬性。
@Basic(fetch=FetchType.LAZY,optional=false) LAZY表示延遲加載該屬性,還可以使用EAGER表示立即加載。optional=false表示在persistence provider爲你生成數據庫schema時,允許字段爲null。等價的XML爲:
<basic name=”firstName” fetch=”LAZY” optional=”false”/>
@Temportal(表示時間的)
用於映射java.util.Date或java.util.Calendar類型的成員屬性的附加信息(@Basic也可以),它可以將這些類型映射到數據庫中date,time或timestamp類型的字段上。
@ Temportal(TemporalType.TIME)還有兩種類型DATE、TIMESTAMP,等價的XML格式爲
<temporal>TIME</temporal>
@Lob
類型java.sql.Blob代表二進制數據,而java.sql.Clob代表字符串數據。註解@Lob用來映射這兩種大對象。如果java類型是 byte[]、Byte[]或java.io.Serializable可以看作是Blob類型的;如果java類型是char[]、 Character[]或java.lang.String可以看作是Clob的。
@Lob
@Basic(fetch=FetchType.LAZY)與@Basic結合使用,以提醒該字段延遲加載
public JPEG getPicture(){…}
等價的XML
<basic name=”picture”><lob/></basic>
@Enumerated
用於映射java的java.lang.Enum枚舉類型。
public enum CustomerType{
UNREGISTERED,
REGISTERED,
BIG_SPENDAH
}
@ Enumerated(EnumType.STRING)表示映射成字符串型式,還有ORDINAL(枚舉值的數據序號)選項 public CustomerType getCustomerType(){…}
等價的XML
<enumerated>STRING</enumerated>
@SecondaryTable 進行多表映射
使用註解@javax.persistence.SecondaryTable可以將一個entity bean class映射到一張或多張表中。比如顧客信息由顧客、地址和信用卡三張表保存
@Table(name=”CUSTOMER_TABLE’)第一張表
@SecondaryTable(name=”ADDRESS_TABLE” 第二張表
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CUST_ID”)}) 分別表示第二張表和第一張表join的字段,referenceedColumnName默認爲第一張表的主鍵,可以不寫
………..
@Column(name=”STREET” table=”ADDRESS_TABLE”)這個字段寫到第二張表中。
如果是多張表,使用以下註解
@SecondaryTable({
@SecondaryTable(name=”ADDRESS_TABLE”
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CUST_ID”)}),
@SecondaryTable(name=”CREDIT_CARD_TABLE”
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CC_ID”)})
})
等價的XML爲
<secondary-table name=” ADDRESS_TABLE”>
<primary-key-join-column name=”ADDRESS_ID”>
</secondary-table>
<column name=”STREET” name=”ADDRESS_TABLE”/>
@Embedded對象
如果entity bean包含一個自定義javabean,並要將該內嵌值對象的成員屬性映射到實體表的字段上,使用@Embedded註解。如果顧客中包含地址對象
@Embeddable
public class Address implements java.io.Serializable{…….}
@entity
@Table(name=”CUSTOMER_TABLE”)
public class Customer implements java.io.Serializable{
private Address address;
………..
@Embedded
@AttributeOverrides({@AttributeOverrides(name=”street”
column=@Column(name=”SUST_STREET”)).....}) //@AttributeOverrides用於覆蓋字段定義
public Address getAddress(){}
}
等價的XML
<embeddable class=”” access=”PROPERTY”/>
<entity class=””>
<attributes>
<id name=”id”/>
<enbedded name=”address”>
<attribute-override name=”street”>
<column name=”CUST_STREET”/>
</attribute-override>
</enbedded>
</attributes>
……..
</entity>
EJB3.0筆記-7關聯實體
實體與實體之間的關係,實體之間的關係是由系統要實現的業務來決定,而不一定要反映真實的世界,比如可以讓一個人只有一個住址,也可以讓一個人有多個住址。所以確定實體與實體的關係可以通過要實現的表形式確定,比如一條A實體記錄可以被多少條B實體記錄使用,反過來一條B實體記錄可以被多少條A實體記錄使用,回答這兩個問題後,就可以解釋實體與實體的關係。
7種關聯類型
one-to-one、one-to-many、many-to-one和many-to-many。每一種關聯又分爲雙向的或單向的,似乎有8種組合,但是雙向ont-to-many和雙向many-to-one關聯是一回事。
單向one-to-one關聯
假設Customer(顧客)和Address(地址)是一對一關聯
數據庫實現:CUSTOMER表中有一個外鍵ADDRESS_ID引用ADDRESS表的主鍵ID,我認爲還應同時定義這個字段是唯一的。
類的實現:Customer中包含Address對象
註解:
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name=” ADDRESS_ID”)如果不是引用主鍵,可以使用referencedColumnName指定
public Address getAddress(){……}
等價XML:
<one-to-one name=”address”
target-entity=”com.titan.domain.Address” 目標類
fetch=”LAZY”獲取方式
optional=”true”>關聯兩端是否可以爲null
<cascade-all/>
<join-column name=” ADDRESS_ID”/>
</one-to-one>
主鍵連接字段
Customer主鍵連接Address的主鍵,可以實現one-to-one
@OneToOne(cascade={CascadeType.ALL})
@PrimaryKeyJoinColumn(name=”ID”, referencedColumnName=”ID”)
分別代表兩個實體的主鍵,可以使用默認
public Address getAddress(){……}
雙向one-to-one關聯
假設Customer和CreditCard(信用卡)一對一雙向關聯
雙向的作用是關聯的兩個實體,可以從任意一個對象訪問到另一個對象
數據庫實現:Customer表中有引用CreditCard的外鍵。在關係數據庫模型中,關係是沒有方向的,所以不用兩個表相互引用
類的實現:Customer實體中包含CreditCard類型的成員變量;CreditCard包含Customer類型的成員變量
@Entity
public class CreditCard implements java.io.Serializable{
private Customer customer;
@OneToOne(mappedBy=”creditCard”)
mappedBy用於設置雙向關聯,對應於Customer的creditCard成員
public Customer getCustomer(){……..}
}
@Entity
public class Customer implements java.io.Serializable{
private CreditCard creditCard;
@OneToOne
@JoinColumn(name=”CREDIT_CARD_ID”)
public CreditCard get CreditCard (){……..}
}
等價的XML
Customer類XML一對一映射部分:
<one-to-one name=”creditCard”
target-entity=”com.titan.domain.CreditCard”
fetch=”LAZY”
optional=”true”>
<cascade-all/>
<join-column name=”CREDIT_CARD_ID”/>
</one-to-one>
CreditCard類XML映射部分:
<one-to-one name=”customer” target-entity=”com.titan.domain.Customer”
mapped-by=” creditCard”/>
</one-to-one>
one-to-Many關聯
假設一個顧客可以擁有多個電話Phone
關係數據庫schema:PHONE中有一個指向CUSTOMER的非唯一外鍵。
程序模型:Customer中有Phone的集合,可以使用java.util包中的一些數據結構,像Collection、List、Map和Set
@Entity
public class Customer implements java.io.Serializable{
private Collection<Phone> phoneNumbers=new ArrayList<Phone>();
@OneToMany(cascade={CascadeType.ALL,targetEntity=”com.titan.domain.Phone”})
如果使用泛型,targetEntity可以不用。
@JoinColumn(name=”CUSTOMER_ID”)
public Collection<Phone> getPhoneNumbers(){….}
}
等價XML
<one-to-many name=”phones” target-entity=”com.titan.domain.Phone” >
<join-column name=”CUSTOMER_ID”>
</one-to- many >
使用:刪除Phone
cust.getPhones().remove(phone);//從集體中刪除,釋放內存
em.remove(phone);//從數據庫中刪除
關聯表映射(Join table mapping)
映射one-to-many的另一種方法是使用關聯表(association table),它包含指向CUSTOMER和PHONE表的外鍵,其中指向PHONE的外鍵是唯一的。
@Entity
public class Customer implements java.io.Serializable{
private Collection<Phone> phoneNumbers=new ArrayList<Phone>();
@OneToMany(cascade={CascadeType.ALL})
@JoinTable(name=”CUSTOMER_PHONE”,
joinColumns={@JoinColumn(name=”CUSTOMER_ID”)},引用支配端的主鍵
inverseJoinColumns={@JoinColumn(name=”PHONE_ID”)})引用到非支配端的主鍵
public Collection<Phone> getPhoneNumbers(){….}
}
等價XML
<one-to-many name=”phones” target-entity=”com.titan.domain.Phone” >
<join-table name=” CUSTOMER_PHONE”>
<join-column name=”CUSTOMER_ID”/>
<inverse-join-column name=”PHONE_ID”/>
</join-table>
</one-to- many >
單向many-to-one關聯
航程Cruise與船Ship之間就是多對一關係。
關係數據庫設計:CRUISE表維護着一個指向SHIP表的外鍵。
@Entity
public class Cruise implements java.io.Serializable{
private Ship ship;
@ManyToOne
@JoinColumn(name=”SHIP_ID”)
public Ship getShip(){……..}
}
等價XML
<many -to-one name=”ship” target-entity=”com.titan.domain.Ship” fetch=”EAGER”>
<join-column name=”SHIP_ID”>
</many -to- one >
雙向one-to-many關聯
雙向one-to-many和雙向many-to-one是一樣的。
Cruise航行和預訂Reservation艙位是一對多關係
數據庫設計:RESERVATION表包含指向CRUISE表的外鍵CRUISE_ID
@Entity
public class Reservation implements java.io.Serializable{
private Cruise cruise;
@ManyToOne
@JoinColumn(name=”CRUISE_ID”)
public Cruise getCruise(){……..}
}
@Entity
public class Cruise implements java.io.Serializable{
private Collection<Reservation> reservation=new ArrayList< Reservation >;
@OneToMany(mappedBy=”cruise”)
public Collection< Reservation > getReservations(){……..}
}
等價XML
Cruise內的映射
<one-to-many name=”Reservations” target-entity=”com.titan.domain. Reservations”
fetch=”LAZY” mapped-by=”cruise”>
</one-to- many >
Reservation內的映射
<many -to-one name=”cruise” target-entity=”com.titan.domain.Cruise” fetch=”EAGER”>
<join-column name=”CRUISE_ID”>
</ many -to- one >
雙向many-to-many關聯
Reservations(預訂艙位)和Customer是多對多關係。Reservations記錄預訂某艙位的所有乘客;而 Customer可以預訂多個艙位
數據庫設計:使用關聯表RESERVATION_CUSTOMER,它維護兩個外鍵字段,一個指向預訂表,另一個指向 顧客表
程序設計:Customer中有Reservation的集合,而Reservation中也保存Customer的集合
@Entiy
public class Reservation implements java.io.Serializable{
private Set<Customer> customers=new HashSet<Customer>();
Set表示一個預訂艙位中的每個顧客都是不同的
@ManyToMany
@JoinTable(name=” RESERVATION_CUSTOMER”,
joinColumns={@JoinColumn(name=”RESERVATION_ID”)},//Reservation是支配端
inversJoinColumns={@JoinColumn(name=”CUSTOMER_ID”)})
public Set<Customer> getCustomer(){…..}
}
@Entity
public class Customer implements java.io.Serializable{
private Collection< Reservation > reservations=new ArrayList< Reservation >();
@ManyToMany(mappedBy=”customers”) //處於關聯關係的反轉端
public Collection< Reservation > getReservations(){…….}
}
等價的XML
在Reservation映射中
<many -to-many name=”customers” target-entity=”com.titan.domain.Customer” >
<join-table name=” RESERVATION_CUSTOMER”>
<join-column name=”RESERVATION_ID”/>
<inverse-join-column name=” CUSTOMER_ID”/>
</join-table>
</many -to- many >
在Customer映射中
<many -to-many name=”reservation” target-entity=”com.titan.domain.Reservaton”
fetch=”LAZY” mapped-by=”customers”>
</many -to- many >
單向many-to-many關聯
reservation可以預訂多個艙位,每個艙位可以重複預訂,因爲乘客不只一人。但是艙位不需要知道有那些預訂與其相關.單向many-to- many的數據庫和雙向many-to-many一樣,但在程序中Cabin沒有引用Reservations的引用,XML中也沒有相關映射,其他和雙向many-to-many一樣
雙向的java代碼
始終要對雙向關聯的兩端同時進行設置,纔會所改變反應到數據庫中。如刪除預訂某艙位的一個顧客
reservation.getCustomers.remove(customer);
customer.getReservations.remove(reservation);
映射集合型關係
還可以使用java.util.List或java.util.Map來表示關聯關係
基於有序列表的關聯關係
使用java.util.List可以包含重複的對象,還可以對集合進行排序,默認是按主鍵做升序。
用法,在定義註解時,添加@OrderBy,如
@ManyToMany
@OrderBy(“lastName ASC”)。如果按多列@OrderBy(“lastName ASC,firstName ASC”)
等價XML
<many-to-many…….>
<order-by>lastName ASC</order-by>……..
</ many-to-many >
基於Map的關聯關係
使用java.util.Map可以將關聯關係的某個成員屬性作爲key,實體本身作爲value
用法,在定義註解時,添加@MapKey註解,如
@OneToMany
@JoinColumn
@MapKey(name=”number”)//以電話號碼爲key,默認爲主鍵,這個key一定是唯一的。
public Map<String,Phone> getPhoneNumbers(){….}
等價XML
<one-to-many……..>
<map-key name=”number”/>
<join-column name=”CUSTOMER_ID”>
</one-to-many >
遊離實體和FetchType
每個描述關聯的註解都包含一個fetch屬性,它指定關係型成員屬性在接受查詢時是否被讀入。如果定義爲FetchType.LAZY,則只有在代碼訪問到該關聯時,它才被初始化。這種延遲加載僅在bean受persistence context託管時才生效,如果是遊離對象,試圖訪問涉及的關聯關係會引起異常。
級聯
當通過entity manager操作entity bean實例時,同樣的操作會自動地作用於該實體所擁有的任何關係型成員屬性上,這就叫級聯。級聯可以通過註解 @javax.persistence.CascadeType聲明,它有ALL、PERSIST、MERGE、REMOVE和REFRESH選項,分別代表所有操作、persist()、merge()、remove()和refresh()。如果使用cascade= {CascadeType.PERSIST}或<cascade-persist/>表示執行persist()操作時,會發生級聯
PERSIST:用於在數據庫中創建實體
MERGE:用於處理實體的同步,亦即數據庫插入和更新操作。合併是一種將遊離對象的狀態同步到持久 化存儲設備的過程
REMOVE:刪除實體
REFRESH:根據數據庫來刷新對象實例的狀態
何時使用級聯:級聯僅僅是一種減少EntityManager API調用次數的便捷工具而已,如果出於性能考慮,減少數據庫訪問量,而同時又滿足業務要求,可不用級聯
EJB3.0筆記-8實體繼承
Java Persistence規範支持實體繼承(entity inheritance),多態關係/關聯,以及多態查詢。這些特性在EJB CMP2.1規範中完全沒有的。
假設存在一個層次結構:PersonàCustomeràEmpoyee(僱員預訂有折扣,也可以是顧客)
映射繼承層次到關係數據庫,有三種方式(這三種方式的類文件除了聲明映射策略的註解(如:Inheritance)不一樣外,其他都是一樣的):
每個類層次結構一張表
Person,Customer和Employee實體的屬性都體現在同一張表中,表通過一個額外字段(如DISCRIMINATOR)的值來區別記錄屬於哪個類。註解方式:
@Table(name=”PERSON_HIERARCHY”)表名
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)單表繼承策略
@DiscrimiinatorColumn(name=”DISCRIMINATOR”,區分記錄類別的字段名
discriminatorType=DiscriminatorType.STRING)該字段的類型,有STRING、CHAR和INTEGER
@DiscriminatorValue(“PERSON”)表中DISCRIMINATOR字段的值爲”PERSON”的記錄表示Person
public class Person{…….}
@Entity
@DiscriminatorValue(“CUST”) //discriminator字段值爲”CUST”的記表示Customer類
public class Customer extends Person{……..}
@Entity //discriminator字段使用默認值”Employee”
public class Employee extends Customer{…......}
等價XML
<entity class=”com.titan.domain.Person”>
<inheritance strategy=”SINGLE_TABLE”/>
<discriminator-column name=”DISCRIMINATOR” discriminator-type=”STRING”/>
<discriminator-value>PERSON</ discriminator-value >
….
</entity>
<entity class=”com.titan.domain.Customer”>
<discriminator-value>CUST</ discriminator-value >
</entity>
<entity class=”com.titan.domain.Employee”/>
優點:SINGLE_TABLE映射策略最容易實現,由於是單表操作,所以性能在三種繼承方式中是最佳的。
缺點:所有代表子類的成員屬性都必須允許爲null。比如保存Customer信息,因爲Customer沒有Employee特有字段,如果該字段不允許爲空,保存Customer就不可能通過數據庫的約束檢查。同時每一行記錄都有與其不直接相關的字段,該策略不遵循數據庫範式。
每個具體類一張表
有三張表,每張表都擁有代表該具體類的所有成員屬性(包含本身定義的和從父類繼承而來的)的字段
註解
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Person{…..}
@Entity
public class Customer extends Person{………}
@Entity
public class Employee extends Customer{……..}
等價XML
<entity class=”com.titan.domain.Person”>
<inheritance strategy=” TABLE_PER_CLASS”/>
….
</entity>
<entity class=”com.titan.domain.Customer”/>
<entity class=”com.titan.domain.Employee”/>
優點:可以在子類的成員屬性上定義它特有的約束。對於映射遺留的schema,些策略更爲容易
缺點:每張表都有冗餘字段,代表基類的成員屬性,所以不遵循數據庫範式。爲了實現這種策略,容器可以在加載實體或多態關係時使用多次查詢,這會影響性能;或者使用SQL UNION,但這種方式不是所有數據庫都支持。所以不推薦使用這種策略
每個子類一張表
有三張表,表中僅包含在該類定義的成員屬性(不包含父類的屬性)
以EMPLOYEE、CUSTOMER和PERSON共享同一個主鍵值爲例
註解
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Person{…….}
@Entity
public class Customer extends Person{….. }
@Entity
@PrimaryKeyJoinColumn(name=”EMP_PK”)
//name表示EMPLOYEE用於join的字段,默認爲父類CUSTOMER的主鍵,如果子類與基類主鍵字段名相同,
//可以不用該註解。referencedColumnName父類用於join的字段,默認爲父類對應表的主鍵
public class Employee extends Customer{…. }
等價XML
<entity class=”com.titan.domain.Person”>
<inheritance strategy=” JOINED”/>
….
</entity>
<entity class=”com.titan.domain.Customer”/>
<entity class=”com.titan.domain.Employee”>
<primary-key-join-column name=”EMP_PK”/>
</entity>
優點:遵循數據庫範式,可以在任何表字段定義特定約束
缺點:性能遜於SINGLE_TABLE策略
非實體基類
Person基類存在於領模型中,但是不打算將其作爲實體保存到數據庫中。
數據庫設計:沒有PERSON表,CUSTOMER表中含有person類的屬性,EMPLOYEE只含有它特有的屬性
註解:
@MappedSuperclass
public class Person{……..}
@Entity
@Table(name=”CUSTOMER”)
@Inheritance(strategy=InheriatanceType.JOINED)
@AttributeOverride(name=”lastName” ,column=@Column(name=”SURNAME”))
//覆蓋父類聲明的默認字段映射
public class Customer extends Person{…..}
@Entity
@Table(name=” EMPLOYEE”)
@PrimaryKeyJoinColumn(name=”EMP_PK”)
public class Employee extends Customer{….}
等價XML
<mapped-superclass class=”com.titan.domain.Person”>
……..
< /mapped-superclass >
<entity class=”com.titan.domain. Customer”>
<inheritance strategy=” JOINED”/>
<attribute-override name=”lastName”>
<column name=”SURNAME”/>
</attribute-override>
</entity>
<entity class=”com.titan.domain.Employee”>
<primary-key-join-column name=”EMP_PK”/>
</entity>
EJB3.0筆記-9實體查詢與EJB QL
在java persistence中,可以同時使用EJB QL查詢語言和原生的SQL來完成查詢操作。由於EJB QL是一種描述Java對象的查詢語言,而entity manager會爲你處理由EJB QL向原生SQL轉換的工作,因此它具有跨數據庫廠商實現的移植能力。EJB QL比SQL更加緊湊和更容易閱讀,但是EJB QL不可能都支持數據庫所有私有特性,所以功能上沒有廠商原生SQL那麼功能全面。
Query API
通過javax.persistence.Query接口來實現查詢功能,而Query接口可以通過javax.persistence. EntityManager得到。
參數
EJB QL支持具名參數(named parameters)和指示參數(positional parameters),如:
Query query=entityManager.createQuery(“from Customer c where c.firstName=:first and
c.lastName=:last”);
query.setParameter(“first”,first);
query.setParameter(“last”,last);
return query.getResultList();
或者
Query query=entityManager.createQuery(“from Customer c where c.firstName=?1 and
c.lastName=?2”);
query.setParameter(1,first);
query.setParameter(2,last);
return query.getResultList();
日期型參數
如果要將java.util.Date或java.util.Calendar參數傳入查詢中,就得使用特殊的setParameter方法, 如下:
setParameter(String name,java.util.Date value,TemporalType temporalType);
setParameter(String name,Calendar value,TemporalType temporalType);
其中temporalType表示將Date或Calendar參數轉換成原生SQL類型時,使用的是何種數據庫類型
對結果分頁
Query query=entityManager.createQuery(“from Customer c”);
return query.setMaxResults(max).setFirstResult(index).getResultList();
Hints
有些java.persistence廠商會爲你提供一些能在執行查詢時使用的附加功能,比如Jboss的EJB3.0允許 爲查詢定義超時。
Query query=entityManager.createQuery(“from Customer c”);
query.setHint(“org.hibernate.timeout”,1000);
FlushMode
默認的flush模式,會在em查詢之前,執行flush動作(把更新同步到數據庫)。如果希望在查詢前不執行 flush,可以通過
Query query=entityManager.createQuery(“from Customer c”);
query.setFlushMode(FlushModeType.COMMIT);
EJB QL
EJB QL是按照實體的抽象持久結構來表達的,包括:抽象結構名稱、基本成員屬性和關係型成員屬性。抽象結構名,默認是非限定類名,如果使用了
@Entity(name=”Cust”)
public class Customer{….}
那麼查詢Customer 的EJB QL爲 select c from Cust AS c;
簡單查詢
select object(c) from Customer as c 等價於select c from Customer as c
選擇實體和關係型成員屬性
對於
@Entity
public class Customer{
private int id;
private String first;
private Address address;
@Id
public int getId(){…..}
public getFirstName(){return first;}
…………
}
選擇成員屬性可以使用:
select c.firstName from Customer as c;或者
select c.first from Customer as c;
選擇關聯的實體可以使用:
select c.address from Customer as c;
選擇關聯實體的屬性可以使用:
select c.address.city from Customer as c;
但是如果Address不是持久型成員,只是普通的class,上面的語句是非法的。可以使用
@Embedded private Address address;讓語句變回合法。
構造函數表達式
可以在select子句是指定一個構造函數,用於創建普通的JAVA對象(而非實體),如:
select new com.tian.domain.Name(c.firstName,c.lastName) from Customer c;
IN操作符和INNER JOIN
返回所有乘客的所有預訂信息
select r from Customer as c ,in(c.reservations) r;
等效於
select r from Customer as c inner join c.reservations r;//inner可以省略
LEFT JOIN
查詢所有顧客及他們的電話號碼,如果顧客沒有電話號碼以null代替
select c.firstName ,p.number from Customer c left join c.phoneNumbers p;
//left join可以寫成left outer join
Fetch Joins
如果關係型成員屬性FetchType在定義XML時(或註解)設爲LAZY,那麼在程序訪問到這些成員時才從數據庫加載數據,有時會增加數據庫訪問量,可以通過JOIN FETCH強制加載這些關係成員,如:
select c from customer c left join fetch c.phones;這時會提前加載Phone關聯
使用DISTINCT
關鍵字distinct確保查詢不會返回重複項。如查詢所有預訂艙位的顧客,由於顧客可以預訂多次,所有distinct可以派上用場。
select distinct cust from Reservation as res, in (res.customers) cust;
where 子句與字面常量
where 子句用於縮小選擇範圍。如果使用到字符串常量,可以使用單引號括起來,如果字符串中又有一個單引號,請用兩個單引號表示;如果是數值型的常量,就直接書寫;如果是布爾型,常量取值用true和false。如
where name=’capital one’; where name=’wendy’’s’;where hasGoodCredit=true;
如果不想涉及這些細節,可以直接使用查詢參數,讓查詢API處理這些問題
where 子句與運算符的優先級
點號(.)à數學運算符(+-*/)à比較運算符(=,>,like,between,in,is null,is empty,member of)à邏輯運算符(not,and or)
where子句與數學運算符
允許查詢操作在做比較時執行算術運算。運算過程中,數值也許被放寬或提升,如int 與double相乘,先把int 變成double,結果也是double的
where子句和邏輯運算符
and 和or運算符的行爲與java語言中的&&和||有所不同。&&只有左操作數爲true時纔會對右操作數求值,and要由轉換出的原生語言決定
where 子句和in
IN用來檢驗是否與一組字面常量中的元素相匹配,如:
where c.address.state IN(‘FL’,’TX’,’WI’);
也可以使用參數
where c.address.state IN(?1,?2,?3,5.7);
where 子句與is null
比較運算符is null允許檢驗路徑表達式是否爲null。它也可以用來檢驗輸入參數,如
select c from Customer as c
where :city is not null and :state is not null
and c.address.state=:state
where 子句與is empty
is empty檢驗集合類型的關係是否爲空(沒有元素),集體類型的關係是從不會爲null的。對from子句已經被賦予標識符的集合型關係使用is empty是非法的,如:
select r from Reservation as r inner join r.customers as c //已經賦予標識符c,表明c一定不爲空
where r.customers is not empty;
where 子句與member of
member of用於判斷某個實體是否是集合型關係的一個成員,如
where cust not member of res.customers
where子句與like
可以使用兩個特殊的字符“%”(表示任意字符序列)和“_”(表示單個字符),如果字符串本來就有%或_,可以使用“/”字符來避免衝突
函數表達式
字符串處理函數
LOWER(String):轉換成小寫
UPPER(String):轉換成大家
TRIM([[leading|trailing|both][trim_char]from)]String):去除指定字符trim_char,默認爲空格
CONCAT(String1,String2):連接字符串
LENGTH(String):求字符串長度
LOCATE(String1,String2[,start]):String1在String2的什麼位置
SUBSTRING(String1,sart,length):截取字符串
數字函數
ABS(number):絕對值
SORT(double):平方根
MOD(int,int):求餘數
返回日期和時間的函數
CURRENT_DATE:返回當前日期
CURRENT_TIME:返回當前時間
CURRENT_TIMESTAMP:返回當前時間戳
聚合函數
COUNT():返回查詢結果集中的條目數
MAX():找出最大值
MIN():找出最小值
AVG(numeric):平均值
SUM(numerc):求和
ORDER BY子句
對返回集合進行排序。在EJB QL中有一個使用限制:order by子句中出現的屬性,必須出現在select子句中,或者是select子句選中實體的一個屬性。如:
select c from Customers as c order by c.address desc;select addr.zip from Address as addr order by add.zip;
以下是非法的
select c from Customers as c order by c.address.city desc;// city不是c的直接屬性
GROUP BY 與HAVING
group by 用於分組,having對分組進一步篩選。group by子句中指定的字段必須出現於查詢的返回結果中,這和SQL的要求是一致的。
子查詢
子查詢是內嵌於主查詢的另一個查詢。EJB QL支持在where和having子句中使用子查詢
查詢費用超過$100,000的所有航程
from Cruise cr where 100000<(select sum(res.amountPaid) from cr.reservations res)
ALL,ANY,SOME
如果子查詢返回多項結果,可以使用ALL、ANY和SOME對結果做進一步限定
如果子查詢中的所有內容都與條件表達式相匹配,那麼操作符ALL就返回true
查詢所有預付了艙位預訂定金的航程
from Cruise cr where 0< all(select res.amountPaid from cr.reservations res)
如果子查詢中的有任意一項與條件表達式相匹配,那麼操作符ALL或SOME就返回true
EXISTS
如果子查詢包含一項或多項結果,那麼操作符EXISTS返回true
批量的UPDATE與DELETE
給所有名叫Bill Burke的乘客增加$10
update Reservation res set res.amountPaid=(res.amountPaid+10)
where exists(
select c from res.customers c
where c.firstName=’Bill’ and c.lastName=’Burke’
);
是否可以寫成?
update Reservation res set res.amountPaid=(res.amountPaid+10)
where res.customers.firstName=’Bill’ and res.customers.lastName=’Burke’
刪除所有Bill Burke的艙位預訂記錄
delect from Reservation res
where exist(
select c from res.customers c
where c.firstName=’Bill’ and c.lastName=’Burke’
)
原生查詢
EntityManager接口有三個創建原生查詢的方法:一個返回標量值,一個返回實體類型,還有一個返回多個實體與標量值組合
標量原生查詢
createNativeQuery(String sql);
簡單實體的原生查詢
createNativeQuery(String sql,Class entityClass);根據某個實體定義好的映射元數據,將返回值映射到該實體
複雜原生查詢
createNativeQuery(String sql,String mappingName)
返回多個實體
@Entity
@SqlResultSetMapping(name=”customerAndCreditCardMapping”,//映射名
entities={@EntityResult(entityClass=Customer.class),//entities指定映射的實體
@EntityResult(entityClass=CreditCard.class,
fields={@FieldResult(name=”id”,column=”CC_ID”),//指定實體內成員與返回字段的映射
@FieldResult(name=”number”,column=”number”)})
})
public class Customer{………}
等價XML
<entity-mappings>
<sel-result-set-mapping name=” customerAndCreditCardMapping”>
<entity-result entity-class=”com.titan.domain.Customer”/>
<entity-result entity-class=”com.titan.domain.CreditCard”>
<field-result name=”id” column=”CC_ID”/>
<field-result name=”number” column=”number”/>
</entity-result>
</sel-result-set-mapping>
</entity-mappings>
使用方法
String sql=”select c.id,c.firstName,cc.id As CC_IC,cc.number from CUST_TABEL
c,CREDIT_CARD_TABLE cc….”
manager.createNativeQuery(sql,”customerAndCreditCardMapping”);
返回結果既有標量也有實體
@SqlResultSetMapping(name=”reservationCount”,//映射名
entities={@EntityResult(entityClass=Cruise.class,
fields={@FieldResult(name=”id”,column=”id”) })},
columns={@ColumnsResult(name=”resCount”)}//標量
)
@Entity
public class Cruise{……}
等價XML
<entity-mappings>
<sel-result-set-mapping name=” reservationCount”>
<entity-result entity-class=”com.titan.domain.Cruise”>
<field-result name=”id” column=”id”/>
</entity-result>
<column-result name=”resCount”>
</sel-result-set-mapping>
</entity-mappings>
使用方法
String sql=”select c.id,count(Resrvation.id) as resCount from Cruise c left join
Reservation on c.id=………..”;
manager.createNativeQuery(sql,” reservationCount”);
具名EJB QL查詢
預先定義好EJB QL或原生的SQL查詢
使用@javax.persistence.NamedQuery用來預定EJB QL的,NamedQuerys用於定義多條查詢
@NamedQuerys({
NamedQuery(name=”getAverageReservateion”,//名字
query=”select AVG(r.amountPaid) from Cruise As c Join c.reservatons r where
c=:cruise”),//EJB QL
NamedQuery(……)
})
@Entity
public class Cruise{…….}
等價XML
<entity-mapping>
<named-query name=” getAverageReservateion”>
<query>
EJB QL
</query>
</ named-query >
</entity-mapping >
使用方式
Query query=em.createNamedQuery(“getAverageReservateion”);
query.setParameter(“cruise”.cruise);
具名原生查詢
使用@javax.persistence.NamedNativeQuery註解來預定義具名原生SQL查詢
@NamedNativeQuery(
name=”findCustAndCCNum”,
query=”select ……..”%