服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

jpa重整orm山河

1.jpa概述 

jpa(java persistence api)作为java ee 5.0平台标准的orm规范,将得到所有java ee服务器的支持。sun这次吸取了之前ejb规范惨痛失败的经历,在充分吸收现有orm框架的基础上,得到了一个易于使用、伸缩性强的orm规范。从目前的开发社区的反应上看,jpa受到了极大的支持和赞扬,jpa作为orm领域标准化整合者的目标应该不难实现。 

jpa通过jdk 5.0注解或xml描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中,图 1很好地描述了jpa的结构:

jpa重整orm山河(图一)

sun引入新的jpa orm规范出于两个原因:其一,简化现有java ee和java se应用的对象持久化的开发工作;其二,sun希望整合对orm技术,实现天下归一。 

jpa由ejb 3.0软件专家组开发,作为jsr-220实现的一部分。但它不囿于ejb 3.0,你可以在web应用、甚至桌面应用中使用。jpa的宗旨是为pojo提供持久化标准规范,由此可见,经过这几年的实践探索,能够脱离容器独立运行,方便开发和测试的理念已经深入人心了。目前hibernate 3.2、toplink 10.1.3以及openjpa都提供了jpa的实现。

jpa的总体思想和现有hibernate、toplink,jdo等orm框架大体一致。总的来说,jpa包括以下3方面的技术: 

orm映射元数据,jpa支持xml和jdk 5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中; 

jpa 的api,用来操作实体对象,执行crud操作,框架在后台替我们完成所有的事情,开发者从繁琐的jdbc和sql代码中解脱出来。 

查询语言,这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的sql语句紧密耦合。 

2.实体对象 

访问数据库前,我们总是要设计在应用层承载数据的领域对象(domain object),orm框架将它们持久化到数据库表中。为了方便后面的讲解,我们用论坛应用为例,建立以下的领域对象:

jpa重整orm山河(图二)

topic是论坛的主题,而polltopic是调查性质的论坛主题,它扩展于topic,一个调查主题拥有多个选项polloption。这三个领域对象很好地展现了领域对象之间继承和关联这两大核心的关系。这3个领域对象将被映射到数据库的两张表中:

jpa重整orm山河(图三)

其中,topic及其子类polltopic将映射到同一张t_topic表中,并用topic_type字段区分两者。而polloption映射到t_polloption中。

具有orm元数据的领域对象称为实体(entity),按jpa的规范,实体具备以下的条件: 

必须使用javax.persistence.entity注解或者在xml映射文件中有对应的元素; 

必须具有一个不带参的构造函数,类不能声明为final,方法和需要持久化的属性也不能声明为final;

如果游离状的实体对象需要以值的方式进行传递,如通session bean的远程业务接口传递,则必须实现serializable接口; 

需要持久化的属性,其访问修饰符不能是public,它们必须通过实体类方法进行访问。

3.使用注解元数据 

基本注解 

首先,我们对topic领域对象进行注解,使其成为一个合格的实体类: 

代码清单1:topic实体类的注解

entitymanagerfactory createcontainerentitymanagerfactory(persistenceunitinfo info, map map) 

javax.persistence.spi.persistenceunitinfo入参提供了创建实体管理器所需要的所有信息,这些信息根据jpa的规范,必须放置在meta-inf/persistence.xml文件中。 

persistenceunitinfo接口拥有了一个void addtransformer(classtransformer transformer)方法,通过该方式可以添加一个javax.persistence.spi.classtransformer,并通过persistenceprovider开放给容器,以便容器在实体类文件加载到jvm之前进行代码的增强,使元数据生效。jpa厂商负责提供classtransformer接口的实现。 

图4描述了创建entitymanager的过程:

jpa重整orm山河(图四)

点击查看大图

实体的状态 

实体对象拥有以下4个状态,这些状态通过调用entitymanager接口方法发生迁移: 

新建态:新创建的实体对象,尚未拥有持久化主键,没有和一个持久化上下文关联起来。

受控态:已经拥有持久化主键并和持久化上下文建立了联系;

游离态:拥有持久化主键,但尚未和持久化上下文建立联系;

删除态:拥有持久化主键,已经和持久化上下文建立联系,但已经被安排从数据库中删除。

entitymanager 的api 

下面是entitymanager的一些主要的接口方法: 

void persist(object entity) 

通过调用entitymanager的persist()方法,新实体实例将转换为受控状态。这意谓着当persist()方法所在的事务提交时,实体的数据将保存到数据库中。如果实体已经被持久化,那么调用persist()操作不会发生任何事情。如果对一个已经删除的实体调用persist()操作,删除态的实体又转变为受控态。如果对游离状的实体执行persist()操作,将抛出illegalargumentexception。

在一个实体上调用persist()操作,将广播到和实体关联的实体上,执行相应的级联持久化操作; 

void remove(object entity)

通过调用remove()方法删除一个受控的实体。如果实体声明为级联删除(cascade=remove 或者cascade=all ),被关联的实体也会被删除。在一个新建状态的实体上调用remove()操作,将被忽略。如果在游离实体上调用remove()操作,将抛出illegalargumentexception,相关的事务将回滚。如果在已经删除的实体上执行remove()操作,也会被忽略; 

void flush()

将受控态的实体数据同步到数据库中; 

t merge(t entity)

将一个游离态的实体持久化到数据库中,并转换为受控态的实体; 

t find(class entityclass, object primarykey)

以主键查询实体对象,entityclass是实体的类,primarykey是主键值,如以下的代码查询topic实体:

topic t = em.find(topic.class,1); 

query createquery(string qlstring)

根据jpa的查询语句创建一个查询对象query,如下面的代码:

◆object getsingleresult():执行select查询语句,并返回一个结果;

◆list getresultlist() :执行select查询语句,并返回多个结果;

◆query setparameter(int position, object value):通过参数位置号绑定查询语句中的参数,如果查询语句使用了命令参数,则可以使用query setparameter(string name, object value)方法绑定命名参数;

◆query setmaxresults(int maxresult):设置返回的最大结果数; 

◆int executeupdate():如果查询语句是新增、删除或更改的语句,通过该方法执行更新操作;

6.jpa的查询语言

jpa的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是hibernate hql的等价物。 

简单的查询 

你可以使用以下语句返回所有topic对象的记录: 

select t from topic t 

t表示topic的别名,在topic t是topic as t的缩写。 

如果需要按条件查询topic,我们可以使用: 

select distinct t from topic t where t.topictitle = ?1 

通过where指定查询条件,?1表示用位置标识参数,尔后,我们可以通过query的setparameter(1, "主题1")绑定参数。而distinct表示过滤掉重复的数据。 

如果需要以命名绑定绑定数据,可以改成以下的方式:

select distinct t from topic t where t.topictitle = :title 

这时,需要通过query的setparameter("title", "主题1")绑定参数。 

关联查询:从one的一方关联到many的一方 

返回polloptions对应的polltopic对象,可以使用以下语句: 

select distinct p from polltopic p, in(p.options) o where o.optionitem like ?1 

这个语法和sql以及hql都有很大的区别,它直接实体属性连接关联的实体,这里我们通过polltopic的options属性关联到polloption实体上,对应的sql语句为: 

entitymanagerfactory createcontainerentitymanagerfactory(persistenceunitinfo info, map map) 

javax.persistence.spi.persistenceunitinfo入参提供了创建实体管理器所需要的所有信息,这些信息根据jpa的规范,必须放置在meta-inf/persistence.xml文件中。 

persistenceunitinfo接口拥有了一个void addtransformer(classtransformer transformer)方法,通过该方式可以添加一个javax.persistence.spi.classtransformer,并通过persistenceprovider开放给容器,以便容器在实体类文件加载到jvm之前进行代码的增强,使元数据生效。jpa厂商负责提供classtransformer接口的实现。 

图4描述了创建entitymanager的过程:

jpa重整orm山河(图四)

点击查看大图

实体的状态 

实体对象拥有以下4个状态,这些状态通过调用entitymanager接口方法发生迁移: 

新建态:新创建的实体对象,尚未拥有持久化主键,没有和一个持久化上下文关联起来。

受控态:已经拥有持久化主键并和持久化上下文建立了联系;

游离态:拥有持久化主键,但尚未和持久化上下文建立联系;

删除态:拥有持久化主键,已经和持久化上下文建立联系,但已经被安排从数据库中删除。

entitymanager 的api 

下面是entitymanager的一些主要的接口方法: 

void persist(object entity) 

通过调用entitymanager的persist()方法,新实体实例将转换为受控状态。这意谓着当persist()方法所在的事务提交时,实体的数据将保存到数据库中。如果实体已经被持久化,那么调用persist()操作不会发生任何事情。如果对一个已经删除的实体调用persist()操作,删除态的实体又转变为受控态。如果对游离状的实体执行persist()操作,将抛出illegalargumentexception。

在一个实体上调用persist()操作,将广播到和实体关联的实体上,执行相应的级联持久化操作; 

void remove(object entity)

通过调用remove()方法删除一个受控的实体。如果实体声明为级联删除(cascade=remove 或者cascade=all ),被关联的实体也会被删除。在一个新建状态的实体上调用remove()操作,将被忽略。如果在游离实体上调用remove()操作,将抛出illegalargumentexception,相关的事务将回滚。如果在已经删除的实体上执行remove()操作,也会被忽略; 

void flush()

将受控态的实体数据同步到数据库中; 

t merge(t entity)

将一个游离态的实体持久化到数据库中,并转换为受控态的实体; 

t find(class entityclass, object primarykey)

以主键查询实体对象,entityclass是实体的类,primarykey是主键值,如以下的代码查询topic实体:

topic t = em.find(topic.class,1); 

query createquery(string qlstring)

根据jpa的查询语句创建一个查询对象query,如下面的代码:

◆object getsingleresult():执行select查询语句,并返回一个结果;

◆list getresultlist() :执行select查询语句,并返回多个结果;

◆query setparameter(int position, object value):通过参数位置号绑定查询语句中的参数,如果查询语句使用了命令参数,则可以使用query setparameter(string name, object value)方法绑定命名参数;

◆query setmaxresults(int maxresult):设置返回的最大结果数; 

◆int executeupdate():如果查询语句是新增、删除或更改的语句,通过该方法执行更新操作;

6.jpa的查询语言

jpa的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是hibernate hql的等价物。 

简单的查询 

你可以使用以下语句返回所有topic对象的记录: 

select t from topic t 

t表示topic的别名,在topic t是topic as t的缩写。 

如果需要按条件查询topic,我们可以使用: 

select distinct t from topic t where t.topictitle = ?1 

通过where指定查询条件,?1表示用位置标识参数,尔后,我们可以通过query的setparameter(1, "主题1")绑定参数。而distinct表示过滤掉重复的数据。 

如果需要以命名绑定绑定数据,可以改成以下的方式:

select distinct t from topic t where t.topictitle = :title 

这时,需要通过query的setparameter("title", "主题1")绑定参数。 

关联查询:从one的一方关联到many的一方 

返回polloptions对应的polltopic对象,可以使用以下语句: 

select distinct p from polltopic p, in(p.options) o where o.optionitem like ?1 

这个语法和sql以及hql都有很大的区别,它直接实体属性连接关联的实体,这里我们通过polltopic的options属性关联到polloption实体上,对应的sql语句为: 

select distinct t0.topic_id, t0.topic_type, t0.topic_title,

t0.topic_time, t0.topic_views, t0.multiple, t0.max_choices from t_topic t0,

t_poll_option t1 where (((t1.option_item like ?) and (t0.topic_type = ?))

and (t1.topic_id = t0.topic_id)) 

该查询语句的另外两种等价的写法分别是: 

select distinct p from polltopic p join p.options o where o.optionitem like ?1

select distinct p from polltopic p where p.options.optionitem like ?1 

关联查询:从many的一方关联到one的一方 

从many一方关联到one一方的查询语句和前面所讲的也很相似。如我们希望查询某一个调查主题下的所示调查项,则可以编写以下的查询语句: 

select p from polloption p join p.polltopic t where t.topicid = :topicid

对应的sql语句为:

select t0.option_id, t0.option_item, t0.topic_id from t_poll_option t0,

topic t1 where ((t1.topic_id = ?)

and ((t1.topic_id = t0.topic_id) and (t1.topic_type = ?))) 

使用其它的关系操作符 

使用空值比较符,比如查询附件不空的所有帖子对象: 

select p from post p where p.postattach is not null 

范围比较符包括between..and和>、>= 、<、<=、<>这些操作符。比如下面的语句查询浏览次数在100到200之间的所有论坛主题: 

select t from topic t where t.topicviews between 100 and 200 

集合关系操作符 

和其它实体是one-to-many或many-to-many关系的实体,通过集合引用关联的实体,我们可以通过集合关系操作符进行数据查询。下面的语句返回所有没有选项的调查论坛的主题: 

select t from polltopic t where t.options is empty 

我们还可以通过判断元素是否在集合中进行查询: 

select t from polltopic t where :option member of t.options 

这里参数必须绑定一个polloption的对象,jpa会自动将其转换为主键比较的sql语句。 

子查询 

jpa可以进行子查询,并支持几个常见的子查询函数:exists、all、any。如下面的语句查询出拥有6个以上选项的调查主题: 

select t from polltopic t where (select count(o) from t.options o) > 6

可用函数 

jpa查询支持一些常见的函数,其中可用的字符串操作函数有: 

◆concat(string, string):合并字段串;

◆length(string):求字段串的长度;

◆locate(string, string [, start]):查询字段串的函数,第一个参数为需要查询的字段串,看它在出现在第二个参数字符串的哪个位置,start表示从哪个位置开始查找,返回查找到的位置,没有找到返回0。如locate ('b1','a1b1c1',1)返回为3;

◆substring(string, start, length):子字段串函数;

◆trim([[leading|trailing|both] char) from] (string):将字段串前后的特殊字符去除,可以通过选择决定具体的去除位置和字符;

◆lower(string):将字符串转为小写;

◆upper(string):将字符串转为大写。

数字操作函数有:

◆abs(number):求绝对值函数;

◆mod(int, int):求模的函数;

◆sqrt(double):求平方函数;

◆size(collection):求集合大小函数。 

更改语句 

可以用entitymanager进行实体的更新操作,也可以通过查询语言执行数据表的字段更新,记录删除的操作:

下面的语句将某一个调查选项更新为新的值: 

update polloption p set p.optionitem = :value where p.optionid = :optionid 

我们使用query接口的executeupdate()方法执行更新。下面的语句删除一条记录: 

delete from polloption p where p.optionid = :optionid 

排序和分组

我们还可以对查询进行排序和分组。如下面的语句查询出主题的发表时间大于某个时间值,返回的结果按浏览量降序排列:

select t from topic t where t.topictime > :time order by t.topicviews desc

下面的语句计算出每个调查主题对应的选项数目: 

select count(p),p.polltopic.topicid from polloption p group by p.polltopic.topicid

这里,我们使用了计算数量的聚集函数count(),其它几个可用的聚集函数分别为: 

◆avg:计算平均值,返回类型为double;

◆max:计算最大值;

◆min:计算最小值;

◆sum:计算累加和;

我们还可以通过having对聚集结果进行条件过滤:

select count(p),p.polltopic.topicid

from polloption p group by p.polltopic.topicid having

p.polltopic.topicid in(1,2,3) 

7.小结 

在不久的将来,sun可能会将jpa作为一个单独的jsr对待,同时jpa还可能作为java se的一部分。不过这些都不太重要,重要的是,我们现在已经可以在脱离容器的情况下、在java se应用中使用jpa了。 

jpa已经作为一项对象持久化的标准,不但可以获得java ee应用服务器的支持,还可以直接在java se中使用。开发者将无需在现有多种orm框架中艰难地选择,按照sun的预想,现有orm框架头顶的光环将渐渐暗淡,不再具有以往的吸引力。

扫描关注微信公众号