如何克服 Hibernate Criteria 和示例 API 的限制?

发布于 2024-09-18 03:13:57 字数 1300 浏览 5 评论 0原文

我所处的位置是,我们公司拥有高度可配置的数据库搜索服务,因此以编程方式配置查询非常有用。 Criteria API 功能强大,但是当我们的一位开发人员重构其中一个数据对象时,标准限制不会表明它们已被破坏,直到我们运行单元测试,或者更糟糕的是,在我们的生产环境中运行时。最近,由于这个问题,我们有一个重构项目的工作时间基本上翻了一番,项目规划中存在一个差距,如果我们知道真正需要多长时间,我们可能会采取替代方法。

我想使用示例 API 来解决这个问题。如果我们在真实的 POJO 属性上指定“where”条件,Java 编译器会大声指出我们的查询被中断。然而,示例 API 中的功能有限,而且在很多方面都有限制。以下面的示例为例

 Product product = new Product();
 product.setName("P%");
 Example prdExample = Example.create(product);
 prdExample.excludeProperty("price");
 prdExample.enableLike();
 prdExample.ignoreCase();

,这里正在查询属性“name”(其中名称如“P%”),如果我要删除或重命名字段“name”,我们会立即知道。但房产的“价格”又如何呢?它被排除是因为 Product 对象有一些默认值,所以我们将“price”属性名称传递给排除过滤器。现在,如果删除“价格”,则此查询在语法上将无效,并且直到运行时您才会知道。瘸。

另一个问题 - 如果我们添加第二个 where 子句会怎样:

 product.setPromo("Discounts up to 10%");

由于调用了 enableLike(),此示例将匹配促销文本“Discounts up to 10%”,但也会匹配“Discounts up to 10,000,000 USD”或其他任何内容匹配。一般来说,Example 对象的查询范围修改(例如enableLike() 或ignoreCase())并不总是适用于要检查的每个属性。

这是第三个也是主要的问题——其他特殊标准呢?使用标准示例框架无法获得价格超过 10 美元的所有产品。无法按促销降序对结果进行排序。如果产品对象加入到某个制造商上,则也无法在相关制造商对象上添加条件。也没有办法安全地指定制造商标准上的 FetchMode(尽管这通常是 Criteria API 的一个问题 - 无效的获取关系会默默失败,甚至更像是一个定时炸弹)

对于上述所有示例,您将需要返回 Criteria API 并使用属性的字符串表示形式来进行查询 - 再次消除了示例查询的最大好处。

除了示例 API,还有哪些替代方案可以获取我们需要的编译时建议?

I'm in a position where our company has a database search service that is highly configurable, for which it's very useful to configure queries in a programmatic fashion. The Criteria API is powerful but when one of our developers refactors one of the data objects, the criteria restrictions won't signal that they're broken until we run our unit tests, or worse, are live and on our production environment. Recently, we had a refactoring project essentially double in working time unexpectedly due to this problem, a gap in project planning that, had we known how long it would really take, we probably would have taken an alternative approach.

I'd like to use the Example API to solve this problem. The Java compiler can loudly indicate that our queries are borked if we are specifying 'where' conditions on real POJO properties. However, there's only so much functionality in the Example API and it's limiting in many ways. Take the following example

 Product product = new Product();
 product.setName("P%");
 Example prdExample = Example.create(product);
 prdExample.excludeProperty("price");
 prdExample.enableLike();
 prdExample.ignoreCase();

Here, the property "name" is being queried against (where name like 'P%'), and if I were to remove or rename the field "name", we would know instantly. But what about the property "price"? It's being excluded because the Product object has some default value for it, so we're passing the "price" property name to an exclusion filter. Now if "price" got removed, this query would be syntactically invalid and you wouldn't know until runtime. LAME.

Another problem - what if we added a second where clause:

 product.setPromo("Discounts up to 10%");

Because of the call to enableLike(), this example will match on the promo text "Discounts up to 10%", but also "Discounts up to 10,000,000 dollars" or anything else that matches. In general, the Example object's query-wide modifications, such as enableLike() or ignoreCase() aren't always going to be applicable to every property being checked against.

Here's a third, and major, issue - what about other special criteria? There's no way to get every product with a price greater than $10 using the standard example framework. There's no way to order results by promo, descending. If the Product object joined on some Manufacturer, there's no way to add a criterion on the related Manufacturer object either. There's no way to safely specify the FetchMode on the criteria for the Manufacturer either (although this is a problem with the Criteria API in general - invalid fetched relationships fail silently, even more of a time bomb)

For all of the above examples, you would need to go back to the Criteria API and use string representations of properties to make the query - again, eliminating the biggest benefit of Example queries.

What alternatives exist to the Example API that can get the kind of compile-time advice we need?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一杯敬自由 2024-09-25 03:13:57

我的公司为开发人员提供了一些时间,让我们可以在宠物项目上进行试验和工作(类似于 Google),我花了一些时间开发一个框架来使用示例查询,同时克服上述限制。我想出了一些对其他对示例查询感兴趣的人也可能有用的东西。以下是使用产品示例的框架示例。

 Criteria criteriaQuery = session.createCriteria(Product.class);

 Restrictions<Product> restrictions = Restrictions.create(Product.class);
 Product example = restrictions.getQueryObject();
 example.setName(restrictions.like("N%"));
 example.setPromo("Discounts up to 10%");

 restrictions.addRestrictions(criteriaQuery);

这里尝试解决问题中的代码示例中的问题 - “价格”字段的默认值问题不再存在,因为该框架要求显式设置条件。具有查询范围的enableLike() 的第二个问题消失了——匹配器仅位于“名称”字段上。

问题中提到的其他问题在这个框架中也消失了。以下是示例实现。

 product.setPrice(restrictions.gt(10)); // price > 10
 product.setPromo(restrictions.order(false)); // order by promo desc
 Restrictions<Manufacturer> manufacturerRestrictions 
        = Restrictions.create(Manufacturer.class);
 //configure manuf restrictions in the same manner...
 product.setManufacturer(restrictions.join(manufacturerRestrictions)); 
 /* there are also joinSet() and joinList() methods
  for one-to-many relationships as well */

甚至还有更复杂的限制。

 product.setPrice(restrictions.between(45,55));
 product.setManufacturer(restrictions.fetch(FetchMode.JOIN));
 product.setName(restrictions.or("Foo", "Bar"));

在向同事展示该框架后,他提到许多数据映射对象都有私有设置器,这使得这种标准设置也变得困难(示例 API 的另一个问题!)。所以,我也考虑到了这一点。 getter 也可查询,而不是使用 setter。

 restrictions.is(product.getName()).eq("Foo");
 restrictions.is(product.getPrice()).gt(10);
 restrictions.is(product.getPromo()).order(false);

我还对对象添加了一些额外的检查,以确保更好的类型安全 - 例如,相对标准(gt、ge、le、lt)都需要一个值? extends Comparable 参数。另外,如果您使用上面指定的样式的 getter,并且 getter 上存在 @Transient 注释,它将引发运行时错误。

但是等等,还有更多!

如果您喜欢 Hibernate 的内置 Restrictions 实用程序可以静态导入,这样您就可以执行 criteria.addRestriction(eq("name", "foo")) 之类的操作,而不会让您的代码变得非常冗长,那么也有一个选项。

 Restrictions<Product> restrictions = new Restrictions<Product>(){
       public void query(Product queryObject){
         queryObject.setPrice(gt(10));
         queryObject.setPromo(order(false));
         //gt() and order() inherited from Restrictions
       }            
 }

现在就这样 - 提前非常感谢您的任何反馈!我们已将代码发布在 Sourceforge 上,供感兴趣的人参考。 http://sourceforge.net/projects/hqbe2/

My company gives developers days when we can experiment and work on pet projects (a la Google) and I spent some time working on a framework to use Example queries while geting around the limitations described above. I've come up with something that could be useful to other people interested in Example queries too. Here is a sample of the framework using the Product example.

 Criteria criteriaQuery = session.createCriteria(Product.class);

 Restrictions<Product> restrictions = Restrictions.create(Product.class);
 Product example = restrictions.getQueryObject();
 example.setName(restrictions.like("N%"));
 example.setPromo("Discounts up to 10%");

 restrictions.addRestrictions(criteriaQuery);

Here's an attempt to fix the issues in the code example from the question - the problem of the default value for the "price" field no longer exists, because this framework requires that criteria be explicitly set. The second problem of having a query-wide enableLike() is gone - the matcher is only on the "name" field.

The other problems mentioned in the question are also gone in this framework. Here are example implementations.

 product.setPrice(restrictions.gt(10)); // price > 10
 product.setPromo(restrictions.order(false)); // order by promo desc
 Restrictions<Manufacturer> manufacturerRestrictions 
        = Restrictions.create(Manufacturer.class);
 //configure manuf restrictions in the same manner...
 product.setManufacturer(restrictions.join(manufacturerRestrictions)); 
 /* there are also joinSet() and joinList() methods
  for one-to-many relationships as well */

Even more sophisticated restrictions are available.

 product.setPrice(restrictions.between(45,55));
 product.setManufacturer(restrictions.fetch(FetchMode.JOIN));
 product.setName(restrictions.or("Foo", "Bar"));

After showing the framework to a coworker, he mentioned that many data mapped objects have private setters, making this kind of criteria setting difficult as well (a different problem with the Example API!). So, I've accounted for that too. Instead of using setters, getters are also queryable.

 restrictions.is(product.getName()).eq("Foo");
 restrictions.is(product.getPrice()).gt(10);
 restrictions.is(product.getPromo()).order(false);

I've also added some extra checking on the objects to ensure better type safety - for example, the relative criteria (gt, ge, le, lt) all require a value ? extends Comparable for the parameter. Also, if you use a getter in the style specified above, and there's a @Transient annotation present on the getter, it will throw a runtime error.

But wait, there's more!

If you like that Hibernate's built-in Restrictions utility can be statically imported, so that you can do things like criteria.addRestriction(eq("name", "foo")) without making your code really verbose, there's an option for that too.

 Restrictions<Product> restrictions = new Restrictions<Product>(){
       public void query(Product queryObject){
         queryObject.setPrice(gt(10));
         queryObject.setPromo(order(false));
         //gt() and order() inherited from Restrictions
       }            
 }

That's it for now - thank you very much in advance for any feedback! We've posted the code on Sourceforge for those that are interested. http://sourceforge.net/projects/hqbe2/

奶茶白久 2024-09-25 03:13:57

API 看起来很棒!

Restrictions.order(boolean) 闻起来像控制耦合。布尔参数的值代表什么有点不清楚。

我建议用 orderAscending() 和 orderDescending() 替换或补充。

The API looks great!

Restrictions.order(boolean) smells like control coupling. It's a little unclear what the values of the boolean argument represent.

I suggest replacing or supplementing with orderAscending() and orderDescending().

无力看清 2024-09-25 03:13:57

看看 Querydsl。他们的 JPA/Hibernate 模块需要代码生成。他们的 Java 集合模块使用代理,但目前不能与 JPA/Hibernate 一起使用。

Have a look at Querydsl. Their JPA/Hibernate module requires code generation. Their Java collections module uses proxies but cannot be used with JPA/Hibernate at the moment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文