Java标准版的EJB Persistence(二)

本教程的上篇里,我们讲到了使用EJB3 persistence——现在也叫做Java Persistence API(JPA)——保持对象的基础知识。我们利用Hibernate的EntityManager/Annotations实现让简单的Person和Address类保持到嵌入的HSQLDB里。但是Person和Address这两个类之间是单向关系:一个Person指向一个Address,所以让我们来看看如何实现双向映射。在Address里,我们准备加入一系列住在该地址的Person——居民(residents):
public class Address {
...
private Set<Person> residents=new HashSet<Person>(); 
...
}
现在一个Address可能指向多个Person,所以我们加入一个@OneToMany批注来访问这些居民所对应的方法:
@OneToMany
public Set<Person> getResidents() {
return residents;
public void setResidents(Set<Person> residents) {
this.residents = residents;
} 
当我们给某个个人设置地址的时候,为了维护它们之间的关系,我们要获得这些居民,并把每个个人添加到set里。除非你想要通过上篇里的示例代码访问一个地址上的多个居民,就像下面这样……
Person p=new Person();
p.setName("John Doe");
p.setAddress(address);
savePerson(p);
address.getResidents().add(p);
——否则甚至在你想要保持更改之前,你会碰到一个暂缓初始化(lazy initialisation)错误。当你检索一个Address对象时集合没有被取回;当你真正访问该字段的时候,它们才会被取回。
这就是暂缓初始化。但是只有当对象还没有被从EntityManager里分离开的时候才会出现暂缓初始化。利用上篇里的代码,我们忽略掉被分离的对象,因为我们创建和关闭了每个数据访问方法里
的EntityManager。现在,你可以通过更改对residents的批注来实现这一目的……
@OneToMany(fetch=FetchType.EAGER)
public Set<Person> getResidents() {… 
……但是这会迫使persistence层在对象被检索的时候总是取得所有的相关数据,一般来说我们不推荐这么做:过度使用它会导致大量的对象树被放到内存里。如果想要强制加载,你可以使用
EJBQL的fetch关键字来实现这一目的。我们可以更改对findByPostcode的查询,并添加“left join fetch address.residents”来强制加载residents属性,就像下面这样:
Query q=em.createQuery("select address from Address as addressleft join fetch address.residents where postcode=:param "); 
集合一般都会被暂缓取回,而大多数的其它字段都会被立即取回,这就是为什么当我们检索一个Person的时候,它的address属性会包含一个address对象。
即使我们做了这些改变,但还是存在另外一个问题;我们会碰到来自Person的瞬态对象异常。这是因为我们的savePerson方法没有控制好赋予它的Person,即使它通过EntityManagermerge()来
保持它。在被给予一个新建立的实例时,merge()创建了一个新的受控对象,并把数据复制到这个新的受控对象里。
这就是对象的生命周期:当你创建一个全新的对象时,它就处于新的/瞬时状态;当你保持它的时候,它就会在你保持EntityManager时进入受控状态;当EntityManager被关闭的时候,实例就被
分离。你可以利用合并把实例重新附加到另一个EntityManager上,或者通过分离对象的id使用EntityManager的find方法获得一个全新的版本。还有一个状态——删除,即当对象被从EntityManager
的挂起删除的时候。现在,你可能会疑惑,为什么会有这些不同的状态?嗯,有了它们就不需要再有数据传输对象(Data Transfer Object,DTO)和那些专门用来把返回的数据移动到应用程序更高层
次的类了。这样能够被分离的只有那些保持类了,而不会有数据传输对象和保持类。
回到示例代码,我们可以通过更改savePerson()来使用persist(),并添加一个非相异方法(not dissimilar method)updatePerson(),它使用合并和updateAddress()为Address进行合并。
现在我们可以完成这个地址分配代码:
Person p=new Person();
p.setName("John Doe");
p.setAddress(address);
savePerson(p);
address.getResidents().add(p);
updateAddress(address); 
在从每个address的居民里删除或者添加Person之后,利用updateAddress()就可以把一个Person从一个Address移动到另外一个Address。但是移动应该是一个原子操作,所以我们要实现一个moveTo方
法。我们就从“取得EntityManager和事务”这个前提开始吧:
private void moveTo(Person p,Address a){
EntityManager em=emf.createEntityManager();
EntityTransaction tx=em.getTransaction();
tx.begin(); 
我们要做的是使用EntityManager的find方法来获得实体的可控版本;
Person managedperson=em.find(Person.class,p.getId());
Address managedaddress=em.find(Address.class,a.getId()); 
在事务里检索可控实例的时候,我们从中检索到的内容也是可控的,所以可以获得当前地址的一个可控版本:
Address managedoldaddress=managedperson.getAddress(); 
现在,由于已经有了可控版本,所以我们可以像下面这样操控它们:
managedoldaddress.getResidents().remove(managedperson);
managedaddress.getResidents().add(managedperson);
managedperson.setAddress(a); 
现在我们必须进行这些改变。由于我们只操控了可控的版本,所以我们需要做的就是进行事务以保持更改,地方就是从当前的EntityManager和事务里:
tx.commit();em.close();} 
现在让我们看看在数据库里创建了哪些表格。正如你所预料的,有Person和Address表格。你可能没有料到的是还有一个Address_Person表格,生成它是为了映射居民set和resident_id以及address_id
字段。所有这些表格和字段名都源于类的命名。在声明实体的时候,你可以用@Table批注来替代它,例如;
@Entity@Table(name="Location")public class Address{ 
这段代码会把Address类保持到一个名为Location(地点)的表格里。如果看一下数据库里生成的字段,你会发现数据列的名字都来源于类的属性名。类似的,你可以用@Column批注来替代保持字段的名字。
如果是在Address类里,你可以插入
@Column(name="postalcode")
public String getPostcode() { return postcode; } 
那么postcode(邮政编码)属性将被保持在一个叫做postalcode的数据列里。我们还可以用@Columnal设置保持字段的其它数据库属性,包括唯一性、长度、和真正数据库数据列的精度。它有用的时候
(即使是要依靠默认的名字生成)是当所生成的数据列名字与SQL的关键字不相符的时候:例如,我们有一个叫做“select”的字段,那么你会碰出错,因为数据库会拒绝格式不正确的SQL,所以把数据
列的名字替换为“my_select”就能够解决这个问题。
现在让我们回到先前生成的Address_Person表格。我们能够通过向Address里的@OneToMany批注加入一个mappedBy参数来删除这个表格,就像下面这样:
@OneToMany(mappedBy="address")public Set<Person> getResidents() { 
这是在告诉persistence层要使用Person的address字段来映射居民set,而且不要生成Address_Person表格。Address和Person之间的关系已经得到了加强。有了这种映射,我们可以用address属性集来
保持Person。在检索Address的时候,我们通过选择相关Person记录的映射来填充它的居民set。当然,你可能不想看到两个实体之间保持如此亲密的关系。
现在QuickExample里有一个功能更多的映射,所以就让我们转到另外一个不同的话题上吧。EJB3/JPA的优势应该是我们能够很容易就移动到一个不同的persistence库。要做到这一点,就让我们修改一下前面
的示例代码,以便使用EJB3/JPA的Glassfish参考实现。
从理论上讲,我们应该只需要修改persistence.xml文件就可以使用Glassfish了,就像下面这样:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence">
 <persistence-unit name="example">
  <provider>
   oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
  </provider>
  <class>quick.Person</class>
  <class>quick.Address</class>
  <properties>
   <property name="jdbc.driver" value="org.hsqldb.jdbcDriver" />
   <property name="jdbc.connection.string"
    value="jdbc:hsqldb:data/example" />
   <property name="toplink.platform.class.name"
    value="oracle.toplink.essentials.platform.database.HSQLPlatform" />
   <property name="jdbc.user" value="sa" />
   <property name="jdbc.password" value="" />
   <property name="ddl-generation" value="dropandcreate" />
   <property name="toplink.logging.level" value="FINE" />
  </properties>
 </persistence-unit>
</persistence>
 
提供程序被改为toplink版本,而属性被改为用于驱动程序、连接、用户名和密码的toplink变量。替代Hibernate的dialect属性的是“toplink.plafrom.class.name”属性,而“ddl-generation”属
性则替换掉Hibernate的“hbm2ddl.auto”属性,“toplink.logging.level”属性被设置为“fine”,并替换了Hibernate的“showsql“属性,而SQL语句被作为普通记录的一部分记录下来。
对于构建库,我们删掉了所有的Hibernate库,用来自Glassfish的库替换它们;antlr.jar、asm.jar、asm-attrs.jar、javaee.jar和toplink-essentials.jar等可以在 Glassfish的lib目录下找
到。你可以从Glassfish的网站上下载和安装它。由于我们只能够使用来自Glassfish的库,所以就不需要运行Glassfish应用服务器了。但是你在运行代码的时候还需要进行一个调整,这是Toplink独特的要
求——你需要注入代理,你需要toplink-essentials-agent.jar,而且在运行的时候,你需要在Java运行命令行里将“-javaagent:{path to the jar}/toplink-essentials-agent.jar”加为Java
虚拟机的参数。
现在就应该准备好运行了。就同以往一样,由于我们还处在确定标准的阶段,所以TopLink和Hibernate的EntityManager/Annotations的行为之间可能还存在一些差别。在Glassfish的示例代码里,你会发
现我们进行了下面的改变。
我们给Person.address的批注加入了@JoinColumn("address_id"),以解决Toplink无法正确处理默认这个很显著的问题。我们不得不用@Column(name="ADDRESS_ID")和@Column(name="PERSON_ID")明
确地指定Address和Person的Id属性,否则Toplink就会同时调用这两个“ID”,并为Address_Person表格建立一个奇怪的“ID”数据列表格,而且连错误或者警告的提示都没有。
我们还需要修改“findBy”方法,这样它们在where语句的参考里就更加具体了:例如,“where person.name=:param”,而不是“where name=:param”,因为Toplink对命名方式更加挑剔。
最后,我们必须用“for(Person pi:ax.getResidents()) System.out.println(pi);”替换掉exercise方法里的“System.out.println(ax.getResidents())”。这是因为Toplink的暂缓加载实现有
一个toString方法,它不会打印出set的内容,所以我们必须在set里迭代。通过这个变化,我们就能够运行示例代码。
要真正运行代码,我们就要把底层的数据库从嵌入的HSQL改为MySQL服务器。我们会假设你已经设置好了MySQL服务器,而且已经下载了MySQL JDBC驱动器,并且把它添加到你的库里了。在这种情况下,我们要做的就
是更改persistence.xml里的属性:
…<property name="jdbc.driver" value="org.gjt.mm.mysql.Driver"/>
<property name="jdbc.connection.string"value="jdbc:mysql://localhost:3306/example"/>
<property name="toplink.platform.class.name"value="oracle.toplink.essentials.platform.database.MySQL4Platform"/>
<property name="jdbc.user" value="root"/><property name="jdbc.password" value=""/>… 
我们告诉persistence层要使用MySQL JDBC驱动程序,并使用来自Toplink的MySQL4Platform的类。这里的设定是本地MySQL服务器,并带有一个没有密码的根帐号;你应该根据实际需要替换掉用户名和密码。有了我
们的简单示例代码,值得注意的是,运行数据之后的延迟是可以解决的;没有必要担心嵌入的数据库会在外部SQL服务器接手的情况下丢失数据。正如你看到的,选择使用什么数据库是可以在部署阶段进行的。
处于比较的目的,Hibernate的Entitymanager/Annotations使用MySQL的相应属性是:
<property name="hibernate.connection.driver_class"value="org.gjt.mm.mysql.Driver"/>
<property name="hibernate.connection.url"value="jdbc:mysql://localhost:3306/example"/>
<property name="hibernate.dialect"value="org.hibernate.dialect.MySQLDialectDialect"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value=""/>
本月,我们探讨了EJB3/JPA里可用实体之间的更多批注和映射,把示例扩展到了Glassfish/Toplink,而且切换了数据库。下一个月,我们将研究一下如何让保持对象变得更容易,并告诉你如何使用EJB3/JPA
来测试企业类,而不需要在单个的测试内调用整个框架。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值