专家评论: Roland Barcia:Java Persistence API 中带注释的命名查询是否真的非常有用?

发表于:2007-05-24来源:作者:点击数: 标签:javaRolandBarciaPersi评论
对于开发人员来说,注释的确使某些事情变得非常容易,但这又以丧失简洁性为代价。在 Java Persistence API (JPA) 中,注释用作将 Java 对象映射到底层数据库的一种机制,不过即使在注释没有任何意义时,开发人员也常常使用它。本文介绍通过 JPA 访问数据的一
对于开发人员来说,注释的确使某些事情变得非常容易,但这又以丧失简洁性为代价。在 Java™ Persistence API (JPA) 中,注释用作将 Java 对象映射到底层数据库的一种机制,不过即使在注释没有任何意义时,开发人员也常常使用它。本文介绍通过 JPA 访问数据的一些其他方法,以及这些方法作为最佳选择的时间和原因。

摘自 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 方法。下面是使用实体管理器通过主键查找对象的应用程序示例:

Customer customer = (Customer)em.find(Customer.class,customerId);

find 方法要求您知道主键和实际类的类型。

JPA-QL 查询

JPA 还拥有可以用于对象模型的全功能查询语言。JPA 查询语言包含许多用于更复杂查询的功能。可以通过动态方式将查询传递到实体管理器:

Query q = em.createQuery("SELECT c FROM Customer c WHERE c.name LIKE
            :custName");
            q.setParameter("custName", name);
            q.setMaxResults(10);
            List result = q.getResultList();

能够在运行时传递查询是某些动态情形(如未知条件)所必需的。不过,在大多数情形中,您希望基于整个性能测试来锁定查询。

本机查询

JPA 还使您能够对基础表使用本机 SQL 查询,并提供映射回结果的能力:

Query q = em.createNativeQuery(
            "SELECT o.id, o.quantity, o.item, i.id, i.name, i.description "+
            "FROM Order o, Item i " +
            "WHERE (o.quantity > 25) AND (o.item = i.id)",
            "OrderItemResults");

标准 SQL 在许多情形中都是必需的。我在以前的评论专栏中给出了许多理由。

命名查询

在大多数情形中,您希望定义可以重用的知名查询。命名查询使您能够在单个位置定义一个查询。命名查询有许多优点:

  1. 外部化或分离查询可以在更改查询时实现某些灵活性。这在优化查询和锁定查询时可能较为有用。
  2. 您可以从多个位置使用查询。
  3. 您可以将查询与知名的业务名称关联。

您可以将命名查询定义为注释,并在代码中的其他位置执行它。下面是执行此类命名查询的一个使用示例:

Query allOrders = em.createNamedQuery("getAllOrder");
            allOrders.setParameter("customerId",customerId);
            Collection <CustomerOrder>  results = allOrders.getResultList();
            return results;

下面是如何定义命名查询,并将其与实体类关联的一个示例:

@Entity
            @Table(name="ORDER")
            @NamedQuery(
            name="getAllOrder",
            queryString="SELECT OBJECT(o) FROM CustomerOrder as o WHERE
            o.customerId = :customerId")
            public class CustomerOrder implements Serializable {
            





回页首


使用带注释的命名查询所带来的问题

稍等片刻! 我刚才不是说过吗?命名查询是外部化查询的好方法。尽管将命名查询定义为注释可以使查询在代码中得到更多重用,但是优点很少,在本质上没有使用价值。我可以方便地将命名查询打包在一个方法中,并重复执行它。其主要优点是查询的外部化,因此,带注释的命名查询(如果有)几乎没有任何意义。

还可以使用 XML 元数据定义 NamedQuery:

<named-query name="getAllOrders" queryString="SELECT OBJECT(o)
            FROM CustomerOrder as o WHERE o.customerId = :customerId" />

现在,我可以方便地对命名查询进行更改,而无需更改源代码和重新编译。回忆我使用 CMP 的时候,查询位于 XML 部署描述符中的这一事实致使此因素无任何意义。所有查询都定义在单个 ejb-jar.xml 文件中,打包在 EJB JAR中,并进一步打包在 EAR 中。在 XML 文件中更改 JPA-QL 的工作量仍很大。(Martin Fowler 也说明了这一点。)

虽然可以采用备用描述符思想,但是单一元数据使它成为一个不引人注意的选择。

JPA 有三个重要的不同之处:

  • 只要映射文件在类路径中存在,那么它们就可以打包在任何 JAR 中(甚至松散打包)。
  • 可以有任意多个映射文件。可以进行设计,让关联实体映射到一个 XML 文件中,或将它们分离出去。可以在一个单独的 XML 文件中保存所有查询。这就是我要建议的内容。
  • 最后,您可以拥有命名本机查询,并将结果映射回 POJO。

例如,可以将怀疑可能更改的任何查询放在外部。在必须更改源、执行构建和安装应用程序时,考虑花费的时间量。如果能够仅更改查询并重新启动,则可以更快地进行测试。在性能测试过程中能够快速更改查询可以大大缩短测试周期。

另一种情况是销售软件的 ISV 需要针对他们销售的产品优化查询,以便与客户选择的特定数据库供应商合作。例如,更改某些子句的排序可以帮助提高某些数据库的性能。如果无法更改查询,则不能改进性能。

使用本机命名查询的能力更加强大,因为您可以使用本机 SQL 将复杂查询映射回 POJO,即使它们已通过其他方式映射。请看以下示例:

<?xml version="1.0" encoding="UTF-8"?>
            <entity-mappings xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm jpa.xsd ">
            <named-native-query name=" OrderItemResults " query="SELECT o.id,
            o.quantity, o.item, i.id, i.name, i.description FROM Order o, Item i WHERE
            (o.quantity > 25) AND (o.item = i.id)" result-class=""
            result-set-mapping="OrderItemResults" />
            <sql-result-set-mapping name="">
            <entity-result entity-class="com.acme.Order.class">
            <!--       <field-result column="" name=""/>
            <etc..> -->
            </entity-result>
            <entity-result entity-class="com.acme.Item.class">
            <!--        <field-result column="" name=""/>
            <etc..> -->
            </entity-result>
            </sql-result-set-mapping>
            </entity-mapping>

在上面的代码中,即使使用注释、单独的 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,他们对本文提出了宝贵意见。

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)