【问题标题】:Exposing Hibernate criteria via service API通过服务 API 公开 Hibernate 标准
【发布时间】:2010-11-07 10:12:33
【问题描述】:

这更像是一个设计而不是实现问题,而且它会很长,所以请耐心等待。最好用一个例子来解释:

假设我有一个名为 Product 的业务实体,它具有一堆属性(namepricevendor 等...)。

它由一个接口(Product)和实现(ProductImpl,映射到Hibernate)以及基本的CRUD服务接口(ProductService)和实现来表示(ProductServiceImpl)。
ProductProductService 作为 API 公开,它们的实现不是。

我想向 ProductService 添加一个 List findProducts(QueryCriteria criteria) 方法,该方法将返回满足给定条件的产品列表。 要求是:

  1. 通过直接产品属性查询(例如product.price gt 50.0
  2. 按关联查询(例如product.vendor.name = "Oracle"
  3. 对结果进行排序(例如order by product.vendor.name desc, product.price asc"
  4. 应用其他过滤器。与以上 3 项均由 API 客户端指定的项目不同,服务可能会根据客户端的身份应用额外的过滤器(例如,调用此方法的客户端可能仅限于查看给定供应商生产的产品)。此类过滤器优先于客户端指定的任何条件(例如,如果过滤器设置为product.vendor.name = "Microsoft",则上面(2)中的查询应该产生空结果集。

因此,问题是,这种方法使用的 QueryCriteria 接口应该是什么样的?我能想到 3 种解决方案,但我不喜欢其中任何一种:

  • 允许客户端直接指定 HQL(以“where”子句开头)。 这是最直接的解决方案,但在安全方面也是最有问题的。即使假设过滤器(上面的#4)足够简单,可以通过 Hibernate 的会话过滤器实现,HQL 仍然需要被解析为 - 至少 - 确保查询参数被指定为参数而不是内联。
  • 使用薄包装的 Hibernate 的 DetachedCriteria 代替 QueryCriteria。 “精简包装”是因为不允许客户端直接创建 DetachedCriteria,因为无法控制创建它的映射实体。 此外,对于某些无法通过 Criteria API 轻松(或根本)表达的查询,这不会像 HQL 那样灵活。与 HQL 方法一样,过滤器(上面的#4)将仅限于 Hibernate 会话过滤器。
  • 编写我自己的 QueryCriteria 接口/实现,它将在幕后形成 DetachedCriteria 或 HQL。 虽然可能是最灵活的解决方案,但这将不得不从 Criteria API 复制大量代码,这似乎不太理想。

任何关于上述方法有效性的 cmets 或 - 祈祷 - 我没有想到的简单优雅的解决方案将不胜感激。

附:在我的具体情况下,所有 API 客户端都是内部的并且是“半可信的”——也就是说,我并不关心有人试图故意破坏某些东西,而是因为糟糕的编程导致 5 个表的笛卡尔积 :-) 但是,它如果能想出一个能够承受 API 向公众公开的解决方案,那就太好了。

【问题讨论】:

  • 这个问题好像有两个月了;我很好奇你选择了什么?我想我会采用 #3,“编写我自己的......”方法,并尝试将我需要的信息片段“扁平化”到 ProductService/criteria API 上,作为可查询的属性,具体取决于使用情况。
  • @RMorrisey - 我添加了一个描述我的解决方案的答案。

标签: java hibernate


【解决方案1】:

我实施的实际解决方案使用混合方法。

使用定义明确的查询的方法(例如,其他服务内部使用的方法、预定义的报告等)具有类似于 HibernateTemplate 的 findBy 方法的签名:

public List<Entity> findEntities(String queryName, QueryParameters parameters);

其中QueryParameters 是一个便利类,用于显式指定命名参数或从bean 中获取它们。示例用法是:

List<Product> products = findProducts("latestUpdates",
 new QueryParameters()
  .add("vendor", "Oracle")
  .add("price", "50.0")
);

List<Product> products = findProducts("latestUpdates",
 new QueryParameters(product, "vendor", "price"));

对此类方法的访问仅限于“受信任”代码;显然使用的查询显然必须在 Hibernate 映射中定义。过滤器内置在查询中或定义为会话过滤器。好处是代码更简洁(没有类似 Criteria 的东西散布在半页上)和明确定义的 HQL(在必要时更容易优化和处理缓存)。


暴露于 UI 或需要更动态的方法使用来自Hibernate-Generic-DAO 项目的Search 接口。它有点类似于 Hibernate 的 DetachedCriteria,但有几个优点:

  1. 可以在不绑定到特定实体的情况下创建它。这对我来说很重要,因为实体接口(用户可见的 API 的一部分)和实现(在 Hibernate 中映射的 POJO)是两个不同的类,并且在编译时用户无法使用实现。

  2. 这是一个经过深思熟虑的开放式界面;与 DetachedCriteria 完全不同,它几乎不可能从中提取任何东西(是的,我知道 DC 不是为此而设计的;但仍然如此)

  3. 内置分页/结果总计数/一堆其他小细节。

  4. 与 Hibernate 没有明确的联系(尽管我个人并不真正关心这一点;我不会突然放弃 Hibernate 而明天使用 EclipseLink);有可用的 Hibernate 和通用 JPA 实现。

过滤器可以添加到服务端的搜索;这也是指定实体类的时候。如果指定了无效的属性名称,唯一缺少的是客户端的快速失败,这可以通过编写我自己的 ISearch / IMutableSearch 实现来解决,但我还没有做到这一点。

【讨论】:

    【解决方案2】:

    选项一: 如果可以扩展你的 API,我建议让你的 API “更丰富”——添加更多方法,如下面的几个,让你的服务听起来更自然。使您的 API 变得更大而不显得臃肿可能很棘手,但如果您遵循类似的命名方案,使用起来似乎很自然。

    productService.findProductsByName("Widget")
    productService.findProductsByName(STARTS_WITH,"Widg")
    productService.findProductsByVendorName("Oracle")
    productService.findProductsByPrice(OVER,50)
    

    在使用 CollectionUtils 和 Predicates 收到结果集后,可以将结果组合(应用多个限制)留给客户端做。你甚至可以为你的 API 的使用者构建一些通用的谓词,只是为了更好。 CollectionUtils.select() 很有趣。

    选项二:如果无法扩展 API,我会选择您的第三个项目符号。

    • 编写我自己的 QueryCriteria 接口/实现,它将在幕后形成 DetachedCriteria 或 HQL...

    您可以尝试使用类似于 Builder 模式的方式将 DSL 样式方法应用于命名,以使内容更具可读性和自然的声音。这在 Java 中有点笨拙,带有所有的点和括号,但可能是这样的:

    Product.Restriction restriction = new Product.Restriction().name("Widget").vendor("Oracle").price(OVER,50) );
    productService.findProducts(restriction);
    

    选项三:结合这两种方法,提供限制式标准以及更丰富的 API。这些解决方案是干净的,因为它们对 API 的使用者隐藏了 Hibernate 实现细节。 (并不是说任何人都曾想过放弃 Hibernate。)

    【讨论】:

    • 感谢您的回答。虽然丰富的 API 适用于“已知”客户端(例如具有可预测/已知查询的其他服务),但它不适用于用户任意指定标准/排序顺序的 UI。一旦涉及关联/集合,QBE 样式的查询会迅速接近完整的 DetachedCriteria API,因此这也不是答案。所以共识似乎是在这里重新发明轮子,对吧?我真的不喜欢那样:-(
    • 也许评估客户是否需要绝对一切。例如,在类似情况下,我以默认排序顺序将结果提供给消费应用程序,如果他们希望结果以不同的方式排序,那么我让他们执行备用排序。
    【解决方案3】:

    嗯 - 有趣的问题。

    经过深思熟虑,编写自己的标准接口可能是要走的路。它不会将您与实现捆绑在一起,并且会降低安全问题。

    还取决于涉及的对象数量,已考虑退回整套产品(应用了必要的过滤器),然后让最终用户应用具有 lambdaj 或类似功能的过滤器。见:

    http://code.google.com/p/lambdaj/

    【讨论】:

    • 谢谢。 LambdaJ 是一种有趣的方法,用于应用无法通过 Hibernate 过滤器表达的后标准过滤器(上面的#4)。但正如你所说,由于数据量大,这对 find() 方法没有帮助。
    【解决方案4】:

    公开此类实现细节绝不是一个好主意。从那里开始,您将被绑定到该库。更糟糕的是,库的任何 api 更改都会导致您的服务发生 api i 更改。遗留的任何安全考虑...

    critera 中使用的 bean 属性名称怎么样(属性名称的三元组,带有 less、equal 和 more 的枚举和值)。使用模型上的 bean 包装器,您可以将其转换为休眠条件。

    还可以在模型更改后将此属性名称转换为新版本。

    【讨论】:

    • 我意识到你可以抽象所有东西,但在某处有一条线——我很确定我不会切换 ORM 层,我怀疑 Hibernate API 会以向后不兼容的方式改变可预见的未来。也就是说,我不建议在上面提出的 3 个解决方案中的任何一个中公开直接的 Hibernate API。不确定您所说的“模型 bean 包装器”是什么意思,但属性/比较/值只是一个简单的案例。添加 AND / OR、属性到属性的比较、集合等之后,您最终将重写整个 Criteria API。
    【解决方案5】:

    Hibernate 是一个低级的基础架构框架,因此应该隐藏在幕后。如果下个月你的应用程序由于某种原因需要切换到另一个 ORM 框架,那么你的 API 将毫无用处。即使是同一应用程序中的层之间的封装也非常重要。

    说了这么多,我认为您的方法应该接收执行搜索所需信息的抽象。我建议你创建一个Product 字段的枚举,并实现一两个简单版本的限制。

    该方法的参数可以是一个相等限制列表、另一个相对限制列表,当然还有一个顺序指示符(枚举值之一加上 asc/desc 的标志)。

    这只是一个大方向,我希望我的意思清楚=8-)

    【讨论】:

    • 层分离与对第三方库的依赖(或不存在)不同。如果我的应用程序要切换 ORM 层,那么这个 API 将是我的最后一个问题(在列表中远低于数百个 Hibernate 注释实体和数千个 HQL 查询,这些都必须重做)。撇开这一点不谈,您的建议是我自己的(更受限制的) DetachedCriteria 重新实现,其中属性名称进一步包裹在枚举中,以防我的产品界面下个月需要更改? :-) 还是我误会了?
    【解决方案6】:

    我认为query by example 在这里会很好用。

    【讨论】:

    • 我不同意。 Hibernate QBE 非常受限 - 仅“等于”/“类似”操作,没有条件分组等......并且自己实现与编写自己的 DetachedCriteria 版本实际上是相同的
    猜你喜欢
    • 2011-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 2017-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多