2009-07-01 132 views
9

这是一个比实现问题更多的设计,它将会很长时间以待我。这是最好用一个例子说明: 通过服务API公开Hibernate标准

比方说,我有一个商业实体,称为产品了一堆属性(价格厂商,等...)。

它通过一个接口(产品)和执行(ProductImpl,在Hibernate中映射)以及基本的CRUD服务接口(ProductService)和执行(ProductServiceImpl)表示。
产品ProductService公开为API,但它们的实现不是。

我想添加一个列表findProducts(QueryCriteria标准)方法ProductService,将返回的产品满足给定标准的列表。 的要求是:通过直接产品性质(例如product.price gt 50.0

  • 查询通过关联(例如product.vendor.name = "Oracle"
  • 排序结果(例如order by product.vendor.name desc, product.price asc"

    1. 查询
    2. 应用附加的过滤器。与由API客户端全部指定的上述3个项目不同,服务可以基于客户端身份(例如,客户端调用该方法可能仅限于查看由给定供应商制造的产品)来应用额外的过滤器。这样的过滤器的优先级高于由客户端指定的任何标准(例如,如果过滤器被设置为product.vendor.name = "Microsoft",查询在上述(2)应当产生空结果集。

    的问题,因此,是什么应该QueryCriteria通过这样的方法使用的界面看起来像我能想到的3个解决方案,我也不喜欢他们中的一个:?

    • 允许客户端指定HQL(从“where”子句)直接 这是最直接的解决方案,但也是最有问题的安全方面。即使假定过滤器(上述#4)是简单的如果足够通过Hibernate的会话过滤器来实现,HQL仍然需要被解析为 - 至少 - 确保查询参数被指定为参数而不是内联。
    • 使用纤细包装的Hibernate的DetachedCriteria代替QueryCriteria。 “Thinly wrapped”,因为客户端不能被允许直接创建DetachedCriteria将无法​​控制它创建的映射实体。 此外,这不会像HQL那样灵活,因为某些查询不容易(或根本不能)通过Criteria API表示。与HQL方法一样,过滤器(上述#4)将仅限于Hibernate会话过滤器。
    • 写我自己的QueryCriteria接口/实现将在后台形成DetachedCriteria或HQL。尽管可能是最灵活的解决方案,但它必须从Criteria API中复制很多代码,这似乎不太理想。

    任何有关上述方法的有效性的评论或 - 手指交叉 - 我没有想到的简单优雅的解决方案将不胜感激。

    P.S.在我的具体情况中,所有的API客户端都是内部的和“半可信的” - 那就是我没有太在意那些试图故意破坏某些东西的人,因为糟糕的编程导致了5张表的笛卡尔积:-)然而,它如果能够提供一种能够承受API暴露于公众的解决方案,我们感到非常高兴。

  • +0

    看起来这个问题已经持续了两个月;我很好奇你定居在什么地方?我想我会采取#3,“写我自己的...”的方法,并试图将我需要的信息“扁平化”到ProductService/criteria API中作为可查询属性,具体取决于使用情况。 – RMorrisey 2009-09-22 00:20:30

    +0

    @Rorrisey - 我已经添加了一个描述我的解决方案的答案。 – ChssPly76 2009-09-22 04:04:42

    回答

    2

    我实施的实际解决方案使用混合方法。使用良好定义的查询(例如由其他服务在内部使用的方法,预先定义的报告等)

    方法有类似的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映射中定义。过滤器构建在查询中或定义为会话过滤器。好处是更干净的代码(没有像遍布半页的标准样东西)和明确定义的HQL(如果需要,更容易优化和处理缓存)。


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

    1. 它可以被创建而不被绑定到特定的实体。对我来说这是个大问题,因为实体接口(用户可见的API的一部分)和实现(在Hibernate中映射的POJO)是两个不同的类,编译时用户不能使用实现。

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

    3. 内置分页/结果总数/其他一些小细节。没有明确的关系到Hibernate(尽管我个人并不在意这一点;我不会突然放弃Hibernate,并且明天会与EclipseLink一起使用)。有Hibernate和通用的JPA实现可用。

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

    0

    公开此类实现细节绝不是一个好主意。从那里开始,你就被这个图书馆限制了。更糟糕的是,图书馆的任何api变更都会导致我改变你的服务。任何安全考虑遗留下来...

    那么在critera中使用的bean属性名称(属性名称的三元组,具有less,equal和more以及value的enum)如何呢?使用模型上的bean包装器,可以将其转换为休眠条件。

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

    +1

    我意识到你可以抽象出所有东西,但是某处存在一行 - 我确信我不会切换ORM层,我怀疑在可预见的将来,Hibernate API将以向后不兼容的方式进行更改。也就是说,我并没有建议在上面提出的3种解决方案中的任何一种中公开直接的Hibernate API。 不确定你的意思是“模型bean包装”,但属性/比较/值只是一个简单的例子。一旦添加了ANDs/ORs,属性到属性的比较,集合等等,您将最终重写整个Criteria API。 – ChssPly76 2009-07-01 20:38:07

    1

    嗯 - 有趣的问题。

    经过深思熟虑,编写你自己的标准接口可能是一条可行的路。它不会将您与实施绑定,并会降低安全问题。

    也取决于涉及多少个对象考虑返回整个产品集(应用必要的过滤器),然后让最终用户应用lambdaj或类似的过滤器。请参阅:

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

    +0

    谢谢。 LambdaJ是一个应用后置条件过滤器(上面#4)的有趣方法,不能通过Hibernate过滤器表示。但正如你所说,由于数据量的原因,这不会对find()方法有所帮助。 – ChssPly76 2009-07-01 21:21:34

    0

    Hibernate是一个低级别的基础设施框架,因此应保持在幕后隐藏。如果下个月你的应用程序需要切换到另一个ORM框架出于某种原因,你的API将是无用的。甚至在相同应用程序的层之间进行封装也非常重要。

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

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

    这只是一个大方向,我希望我做了我的观点明确= 8)

    +0

    图层分离与第三方库的依赖关系(或不存在)不同。如果我的应用程序要切换ORM层,那么这个API将是我的最后一个问题(列表远远低于数百个Hibernate注释实体和数千个HQL查询,这些查询都必须重做)。这一点不谈,你的建议是我自己的(更受限制的)DetachedCriteria重新实现与财产名称进一步包裹在枚举的情况下,我的产品界面需要改变下个月? :-)或者我误解了? – ChssPly76 2009-07-01 21:42:47

    2

    方案一: 如果可以扩大你的API,我建议让您的API“富” - 添加更多的方法,如下面的几个,使您的服务听起来更自然。在不增加看起来臃肿的情况下让API变大可能会非常棘手,但如果您遵循类似的命名方案,那么使用起来似乎很自然。

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

    结合的结果(应用多个限制)可以留下的东西的客户,他们收到的结果通过使用CollectionUtils和谓词设置后做。你甚至可以为你的API的消费者建立一些常见的谓词,只是为了好。 CollectionUtils.select()很有趣。

    选项二:如果无法扩展API,那么您的第三个项目符合我的要求。

    • 写我自己的QueryCriteria接口/实现将形成两种或的DetachedCriteria HQL幕后...

    你可以尝试使用的东西到DSL式的方法应用到命名类似于Builder模式,使事情更具可读性和自然的声音。这得到Java中的一个有点笨拙的所有点和括号,但也许是这样的:

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

    方案三:将二者结合起来的办法,具有更丰富的API一起提供的限制式的标准。这些解决方案将是干净的,因为它们隐藏了API的使用者的Hibernate实现细节。 (并不是说任何人都会想到从休眠状态切换到休眠状态。)

    +0

    谢谢你的回答。虽然丰富的API适用于“已知”客户端(例如具有可预测/已知查询的其他服务),但它不适用于UI由用户任意指定标准/排序顺序的用户。一旦涉及到关联/集合,QBE风格的查询会快速地处理完整的DetachedCriteria API,因此这也不是答案。所以共识确实似乎在重塑这里的轮子,是吧?我真的不喜欢那样:-( – ChssPly76 2009-07-01 21:34:34