摘自 IBM WebSphere 开发者技术期刊。
对注释的注释
Java Persistence API (JPA) 定义了访问数据的多种方法:通过实体管理器、通过 JPA-QL 或通过本机查询。在 JPA 中,注释用作将 Java 对象映射到底层数据库的一种机制。您还可以提供 XML 元数据作为映射注释的覆盖或备选机制。不过,我看到的大多数 JPA 使用情况都明显喜欢使用注释。规范文档使用注释,而不使用基于 XML 的映射示例(仅向您显示 XML 模式)来表示所有示例这一事实可能是覆盖的原因之一。创建对象关系映射,以便从 Java 对象模型抽象底层数据库的详细信息。不过,JPA 可以让数据库详细信息快速返回到 Java 源。在本文中,将检查 JPA 中的各种查询样式,解释它们存在的原因,并解释为什么对某些样式(如命名查询)进行注释没有任何意义。最后得出的结论是,这个小示例实际上是更大的问题的一部分。
|
使用 JPA 访问数据
让我们快速浏览一下使用 JPA 访问数据的各种方法,假定您非常熟悉在 JPA 中映射 Java 对象的方式。(有关详细信息,请参阅参考资料。)
EntityManager
应用程序在运行时与 Java 对象交互。通过使用称为实体管理器的特殊对象,应用程序可以查询或保持对象。EntityManager 实例与永久性上下文关联。在永久性上下文中,实体实例及其生命周期得到管理。可以认为 EntityManager 是底层永久性机制的 Facade。EntityManager 包含访问数据的必要方法。最简单的访问持久性数据的方法是使用 find 方法。下面是使用实体管理器通过主键查找对象的应用程序示例:
|
find 方法要求您知道主键和实际类的类型。
JPA-QL 查询
JPA 还拥有可以用于对象模型的全功能查询语言。JPA 查询语言包含许多用于更复杂查询的功能。可以通过动态方式将查询传递到实体管理器:
|
能够在运行时传递查询是某些动态情形(如未知条件)所必需的。不过,在大多数情形中,您希望基于整个性能测试来锁定查询。
本机查询
JPA 还使您能够对基础表使用本机 SQL 查询,并提供映射回结果的能力:
|
标准 SQL 在许多情形中都是必需的。我在以前的评论专栏中给出了许多理由。
命名查询
在大多数情形中,您希望定义可以重用的知名查询。命名查询使您能够在单个位置定义一个查询。命名查询有许多优点:
您可以将命名查询定义为注释,并在代码中的其他位置执行它。下面是执行此类命名查询的一个使用示例:
|
下面是如何定义命名查询,并将其与实体类关联的一个示例:
|
|
使用带注释的命名查询所带来的问题
稍等片刻! 我刚才不是说过吗?命名查询是外部化查询的好方法。尽管将命名查询定义为注释可以使查询在代码中得到更多重用,但是优点很少,在本质上没有使用价值。我可以方便地将命名查询打包在一个方法中,并重复执行它。其主要优点是查询的外部化,因此,带注释的命名查询(如果有)几乎没有任何意义。
还可以使用 XML 元数据定义 NamedQuery:
|
现在,我可以方便地对命名查询进行更改,而无需更改源代码和重新编译。回忆我使用 CMP 的时候,查询位于 XML 部署描述符中的这一事实致使此因素无任何意义。所有查询都定义在单个 ejb-jar.xml 文件中,打包在 EJB JAR中,并进一步打包在 EAR 中。在 XML 文件中更改 JPA-QL 的工作量仍很大。(Martin Fowler 也说明了这一点。)
虽然可以采用备用描述符思想,但是单一元数据使它成为一个不引人注意的选择。
JPA 有三个重要的不同之处:
例如,可以将怀疑可能更改的任何查询放在外部。在必须更改源、执行构建和安装应用程序时,考虑花费的时间量。如果能够仅更改查询并重新启动,则可以更快地进行测试。在性能测试过程中能够快速更改查询可以大大缩短测试周期。
另一种情况是销售软件的 ISV 需要针对他们销售的产品优化查询,以便与客户选择的特定数据库供应商合作。例如,更改某些子句的排序可以帮助提高某些数据库的性能。如果无法更改查询,则不能改进性能。
使用本机命名查询的能力更加强大,因为您可以使用本机 SQL 将复杂查询映射回 POJO,即使它们已通过其他方式映射。请看以下示例:
|
在上面的代码中,即使使用注释、单独的 XML 文件或不使用任何内容(必须映射每个字段)来映射 Order 或 Item 类,我仍能够使用此 XML 覆盖它。当需要 SQL 的灵活性时,通过 JPA 本机查询,可以将 JPA 用作 JDBC 框架(类似于 IBatis)。(在我的博客和以后的文章中,我将说明此示例。)
|
更大的问题
我选择使用命名查询来说明一个更大的问题。将对象关系域映射 (ORM) 创建为支持抽象的应用程序,以便使用面向对象的技术来满足他们的域。ORM 将抽象出数据库。带注释的映射不能满足此类抽象的要求。尽管此类抽象并不是一直需要,但它适合于许多情形。
JPA 规范本身就很有吸引力。JPA 使用外部 XML 映射文件支持映射您的域模型。不过,示例和文档中几乎没有这方面的说明。规范委员会有很大责任,因为他们仅提供带注释的实例,这暗示注释是 JPA 的首选机制。但是,他们有正当理由建议映射文件可能是首选机制:
客户端应用程序可以共享源代码,因此,它们完全了解底层数据库的知识。甚至可以尝试在客户端计算机上执行 SQL 操作。
如果希望将对象映射到多个数据库,又该如何操作呢?尽管可以使用 XML 覆盖注释,但是如果对象有几个映射,那么我可能不会喜欢任何一个。在 SOA 环境中,我可以跨 ESB 发送对象,每个服务都需要有指向自已环境的映射。
如果数据库发生更改——即使使用 XML 覆盖映射——则源代码无法在语义上映射底层映射。任何人都不知道 XML 覆盖会出现错误和假设。在正常部署过程中,信息(如数据库架构名称)会不断变化,而在源中具有此信息无疑会阻碍部署。
对于开发人员来说,注释的确使某些事情变得非常容易,但这又以丧失简洁性为代价。我认为全球的 JPA 编写者通常在进行一些不利于社区的活动,因为不能记录使用 JPA 的 XML 映射样式的清晰示例。
|
结束语
命名查询只是注释如何被过度使用的一个示例。注释有许多用途,但是我担心在 ORM 范围内滥用了它们。在我的网络日志中,我将花费一些时间来说明使用 JPA 的 XML 映射样式的示例。
|
致谢
感谢 Keys Botzum 和 Tom Alcott,他们对本文提出了宝贵意见。