通过服务API公开Hibernate标准

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

假设我有一个名为Product的业务实体,其中包含许多属性( 名称价格供应商等)。

它由接口( 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. 应用其他filter。 与API客户端指定的上述3个项目不同,服务可以基于客户端的身份应用其他filter(例如,调用此方法的客户端可能仅限于查看由给定供应商制造的产品)。 此类filter优先于客户端指定的任何条件(例如,如果filter设置为product.vendor.name = "Microsoft" ,则上述(2)中的查询应生成空结果集。

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

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

任何关于上述方法的有效性的评论或 – 手指交叉 – 我没有想到的简单优雅的解决方案将受到高度赞赏。

PS在我的具体情况下,所有的API客户端都是内部的,并且是“半信任的” – 这就是我不太关心有人试图故意破坏某些东西,因为糟糕的编程导致了5个表的笛卡尔积:-)但是,能够提供一种能够承受公开API暴露的解决方案会很不错。

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

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

 public List findEntities(String queryName, QueryParameters parameters); 

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

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

要么

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

对这些方法的访问仅限于“可信”代码; 显然必须在Hibernate映射中定义使用的查询。 filter内置于查询中或定义为会话filter。 好处是更清晰的代码(没有类似Criteria的东西分布在半页)和明确定义的HQL(如果需要,更容易优化和处理缓存)。


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

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

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

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

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

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

方案一:如果可以扩展您的API,我建议您的API“更丰富” – 添加更多方法,例如下面的一些方法,以使您的服务听起来更自然。 如果你的API看起来很臃肿,那么让你的API变大可能会很棘手,但是如果你遵循类似的命名方案,那么使用它似乎很自然。

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

结合(应用多个限制)可以留给客户在使用CollectionUtils和Predicates收到结果集后要做的事情。 你甚至可以为你的API的消费者构建一些常见的Predicates就好了。 CollectionUtils.select()很有趣。

选项二:如果无法扩展API,那么您的第三个子弹就是我要使用的那个。

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

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

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

方案三:结合这两种方法,提供限制式标准以及更丰富的API。 这些解决方案很简洁,因为它们隐藏了API用户的Hibernate实现细节。 (并不是说任何人都会想到从Hibernate转移。)

嗯 – 有趣的问题。

经过深思熟虑后,编写自己的标准界面可能就好了。 它不会将您绑定到实现,并会降低安全性问题。

另外,根据涉及的对象数量,考虑返回整组产品(应用必要的filter),然后让最终用户应用lambdaj或类似的filter。 看到:

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

公开此类实现细节绝不是一个好主意。 你从那里开始就受限于那个图书馆。 更糟糕的是,图书馆的任何改变都会改变你的服务。 留下任何安全考虑因素……

如何在critera中使用bean属性名称(属性名称的三倍,enum用less,equal和more以及值)。 使用模型上的bean包装器,您可以将其转换为hibernate标准。

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

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

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

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

这只是一个大方向,我希望我明白我的观点= 8-)

我认为通过示例查询在这里工作得非常好。