级别: 初级 Stephen B. Morris, CTO, Omey Communications
2009 年 9 月 14 日 如今,面向对象映射(ORM)工具在全球得到了广泛应用和部署,您通常不必为组合键这样的难题绞尽脑汁。一般情况下,设计键时可选择简单的整数,放心地将问题留给工具处理。但有时会遇到一些特殊的情况,需要使用组合键,此时就需要一种相应的战略。本文介绍的技巧使您能够使用 JPA 和 Hibernate 实现组合键。
问题定义
本技巧从问题的简单的描述开始:定义组合数据库键。这个键组合了多列,唯一地定义一个数据库表的行。有时,组合键也称为自然键 或业务键。某些时候使用组合键的原因是所选键在某些方面与最终用户的业务领域相关联。要定义组合键,只需从该领域中选择一些属性,并将其组合在一起,提供所需的行唯一性程度。组合键的缺点是设计和编码略有难度。此外,组合键倾向于将您的数据库和 ORM 设计绑定到原始领域。后者可能会成为严重的问题。
实体代码
清单 1 展示了一个名为 BillingAddress 的 Java 类。该类建模一名个人或一个组织的账单邮寄地址。账单本身与另一个名为 PurchaseOrder 的 Java 类关联。这里并没有什么出人意料之处 — 下置购买订单,接下来进行账单邮寄流程。
清单 1. BillingAddress 类
import javax.persistence.*;
import java.io.Serializable;
@Embeddable
public class BillingAddress implements Serializable {
private String street;
private String city;
BillingAddress() {}
public BillingAddress(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
private void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
private void setCity(String city) {
this.city = city;
}
} |
这里有必要说明一个要点,该类实现了 Java Serializable 接口。另外还要注意带有注释 @Embeddable 的一行。此注释是组合键拼图中的第一块。带有 @Embeddable 注释的 Java 类本身可作为其他类的子组件。这听起来似乎有点复杂,而实际并非如此。作为演示,清单 2 展示了 PurchaseOrder 类,它使用清单 1 中的 BillingAddress 类。
清单 2. PurchaseOrder 类
import javax.persistence.*;
@Entity
@Table(name = "PURCHASE_ORDERS")
@IdClass(BillingAddress.class)
public class PurchaseOrder {
PurchaseOrder() {}
PurchaseOrder(BillingAddress billingAddress) {
street = billingAddress.getStreet();
city = billingAddress.getCity();
}
@Id
@AttributeOverrides({
@AttributeOverride(name = "street",
column = @Column(name="STREET")),
@AttributeOverride(name = "city",
column = @Column(name="CITY"))
})
private String street;
private String city;
private String itemName;
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
} |
注释总是有些难以理解,清单 2 也不例外。因此,我会将其拆分成便于管理的块。第一个注释是 @Entity,指明该类是一个数据库实体(也就是说,它将构成 ORM 解决方案的一部分)。通常,看到 @Entity 注释就等于看到了对应的数据库表。后者由清单 2 中名为 @Table 的相应注释表明。我发现,以这种方式拆分程序更容易理解。
清单 2 中的下一个注释是 @IdClass,它定义组合键类引用。您可能已经注意到了,该类引用清单 1 中的 BillingAddress 类。跳过清单 2 中的构造方法,注意 @Id 注释。组合键就是在这里使用嵌套 @AttributeOverrides 注释定义的。这些注释用于定义组合键列:分别是 “STREET” 和 “CITY”。
就在清单 2 的注释之后,您是否看到了来自清单 1 的两行重复的代码?当然,重复的代码是表示街道和城市的两个私有数据成员。这样的重复是创建组合键所必需的。
数据库模式
至此讨论的内容都是技术层面上的。现在我们将以更加具体的方式表述此技巧,我们将生成一个数据库模式。清单 3 展示了来自这个非常简单的 ORM 数据库设计的模式。
清单 3. 数据库模式
drop table PURCHASE_ORDERS if exists;
create table PURCHASE_ORDERS (
street varchar(255) not null,
city varchar(255) not null,
itemName varchar(255),
primary key (street, city)
);
|
可以看到,主键实际上是由 street 和 city 字段组合而成的。在真实的数据库中 — 例如,带有图形用户界面(GUI)工具的数据库中,这将得到怎样的效果?在给出答案之前,我编写了一段简单的客户端代码,用于将一个或两个实体保持到数据库中。
清单 4 展示了一段代码摘录,实例化上文所定义类的对象。
清单 4. ORM 客户端代码摘录
// Start EntityManagerFactory
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("helloworld");
// First unit of work
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
PurchaseOrder purchaseOrder =
new PurchaseOrder(new BillingAddress("Broad Street", "Boston"));
purchaseOrder.setItemName("My new computer");
em.persist(purchaseOrder);
tx.commit();
em.close();
|
清单 4 中的代码展示了从 PurchaseOrder 对象的实例化和设置一直到此对象在数据库中的持久化这个完整的过程。这实实在在地体现了 ORM 的魔力。让我们来看看发生了什么。
首先创建一个 EntityManagerFactory 的实例,随后使用此实例创建 EntityManager 的一个名为 em 的实例。后者随后用于将 PurchaseOrder 对象实例写入数据库。保持到数据库中的实际过程是在事务中完成的。
事务就是一组原子操作,可能全部成功完成,或在出现错误时回滚。如清单 4 所示,EntityManager 对象用于创建 EntityTransaction 的一个名为 tx 的实例。后面这个对象将工作单元打包到事务中。
请注意对 persist() 和 commit() 的调用。必须牢记,除非同时出现这两个调用,否则不能更改数据库。这是 Java 持久 API(JPA)的简单模式。
为了完成 ORM 之旅,图 1 展示了运行清单 4 中的代码之后数据库的状态。代码是使用称为 HSQLDB 的内存数据库测试的,这种产品包含一个简单的 GUI 工具。图 1 显示了 HSQLDB 数据库的状态。可以看到,我对 PURCHASE_ORDERS 表运行了 SQL 查询。该表是通过模式创建的,模式本身是使用本文前面给出的清单创建的。
图 1. 填充后的数据库
在图 1 中,可以看到清单 4 中的这行代码产生的效果:purchaseOrder.setItemName("My new computer")。对 setter 代码的调用使用 String 数据 “My new computer” 填充了数据库行中的相关列。就工作流而言,可认为整个程序都是在创建新计算机的购买订单时运行的,随后就是发票邮寄过程。工作流中的所有步骤都隐式地存储在数据库中。
结束语
清单 1 和清单 2 中定义的组合键允许您将多个列绑定在一起。列的组合能提供所需的唯一性,使您的数据库表中能拥有任意数量的行。我们已经介绍了如何实现这一目标。现在,您需要大致了解为什么要选择这种有些奇特的数据库设计方法。
使用组合键的最常见的原因或许就是为了向后兼容。换句话说,在您需要将新数据库代码整合到遗留环境中的时候。我认为,如今故意以这样一种方式设计数据库的做法并不常见,您只需要在符合惯例的场合创建组合键。
在这种情况下,我认为对于这种方法的讨论都是毫无意义的。如果组合键是标准,那么就不可能很快发生变化。在很多时候,都有大量现有数据是使用这种方法构造的。因而,如果组合键被数字键所取代,那么遗留数据就必须进行迁移。此外,也有许多业务流程是对应组合键数据的。所有这些因素结合在一起,使组合键成为一种必不可少的技术。
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Stephen Morris 是爱尔兰的 Omey
Communications 的 CTO。在过去 20 年中,Stephen 曾在一切全球最大的网络公司参与过各种软件项目,包括基于 J2EE/J2SE 的网络管理系统、账单编制应用程序、移植和开发 SNMP 实体、网络设备技术和 GSM 移动网络应用程序。他是 Network Management, MIBs and MPLS: Principles, Design and Implementation(Prentice Hall PTR,2003 年)一书的作者,同时也在 InformIT 和 OnJava.com 发表过多篇有关网络管理和其他主题的文章。 |
对本文的评价
|