在你执行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 ……..”%