你使用哪种Java类型来处理JPA集合,为什么?

79

在 JPA 领域模型中,你使用以下哪些集合类型?为什么选择这些类型:

  • java.util.Collection
  • java.util.List
  • java.util.Set

我想知道是否有一些基本规则。

更新 我知道 SetList 的区别。 List 允许重复,有顺序;Set 不允许重复,不保证顺序。我是在 JPA 的上下文中提问这个问题的。如果严格按照定义,那么应该始终使用 Set 类型,因为你的集合存储在关系型数据库中,不能有重复项,并且你需要自己定义顺序,即 Java 中 List 中的顺序不一定会被保留到数据库中。

例如,大部分时候我使用 List 类型,不是因为它有顺序或者允许重复(无论如何都不行),而是因为我的组件库中的某些组件需要一个列表。


我相信你会觉得@OrderBy注解很有用和有趣。这是有关它的谷歌的第一个链接:http://www.objectdb.com/api/java/jpa/OrderBy - Grzegorz Oledzki
@Grzegorz Oledzki,我知道@OrderBy注释,但它与您的List中的顺序无关。如果您检索带有@OrderBy注释的实体列表,更改其顺序,合并到数据库并再次检索它,那么您更改的顺序将被保留吗?不会!您将获得通过@OrderBy定义的相同顺序。 - Theo
我同意这将是很棒的。但你已经完成了一半。当你读取这样的实体时,你会得到正确的排序。 - Grzegorz Oledzki
@OrderColumn注释映射到数据库中的排序列,特别用于在内存中更改元素时保留List中元素的顺序。缺点是更改元素的顺序将导致更新潜在的所有行,以更新排序列并使其与内存中的顺序保持一致。 - German
6个回答

56

就像您的问题所示,关键在于域名,而不是JPA。 JPA只是一个框架,您可以(并应该)以最适合解决问题的方式使用它。由于框架(或其限制)选择次优解通常是警钟。

当我需要一个集合且无需关注顺序时,我使用Set。当因为某种原因顺序很重要(排序列表,按日期排序等)时,我会使用List

您似乎很清楚CollectionSetList之间的区别。使用其中之一与其他方式没有区别,这取决于您的需求。您可以使用它们来向API的用户(或将来的自己)传达集合的属性(可能是微妙或隐含的)。

这遵循了在代码的任何其他地方使用不同集合类型的完全相同规则。您可以将所有引用的类型都设置为ObjectCollections,但在大多数情况下,您会使用更具体的类型。

例如,当我看到一个List时,我知道它以某种方式排序,并且对于此情况,重复项是可接受的或无关紧要的。当我看到一个Set时,我通常希望它没有重复项并且没有特定顺序(除非它是 SortedSet )。当我看到一个Collection时,我不期望从中获得任何信息,只需包含一些实体即可。

关于列表排序...是的,它可以保留。即使没有保留并且您只是使用了@OrderBy,它仍然可能很有用。考虑默认按时间戳排序的事件日志示例。人为地重新排序列表几乎没有意义,但是它仍然可以很有用,因为它默认排序。


1
你不应该总是使用 Set 吗?因为你将实体存储在关系型数据库中,其中不能有任何重复元素,并且必须自己定义顺序(即当 Java List 持久化时,其顺序不会被保留)。那么什么时候使用 Collection - Theo
1
如果必要的话,您可以使用人工字段来保持List的顺序。但在大多数情况下,这是自然而然的。一个例子可以是基于时间的日志,您可以使用@OrderBy("eventDate")。至于Collection,我会在类似于Set的情况下使用它。 - Konrad Garus
是的,您可以使用索引列或使用@OrderBy注释,但是这些方式指定的顺序与Java列表中的顺序无关。您还可以在Set上使用@OrderBy注释或使用索引列。如果您更改Java列表中的顺序,则DB中的排序不会反映在DB中。 - Theo
请参考更新的答案。简而言之,在您的应用程序中,请遵循与其他地方完全相同的规则。 - Konrad Garus
3
我看到的一个让事情变得复杂的案例是JSF。它不支持实体集合的Set接口,只支持List。在视图方面这没问题,但在存储方面不太好处理。因此,在这种情况下,视图必须是一个List,不幸的是需要不断地将其翻译成Set。 - Darrell Teague
很棒的回答!我想听听你对于如何为实体类实现hashCode/equals方法的看法。我遇到的问题是,即使我有一个业务标识符并使用它来实现hashCode/equals方法,像Hibernate这样的JPA提供者仍然会尝试将未初始化的实体放入集合中,因此在这种情况下,hashCode方法返回的值与初始化后的值不同。这样做是否可接受?如果我的业务键没有被初始化,我应该返回super.hashCode()吗?谢谢! - Giovanni Botta

47

使用 Set 还是 List 的问题我认为比较难。尤其是在使用 hibernate 作为 JPA 实现时。如果在 hibernate 中使用 List,它会自动转换为“Bags”范式,其中可以存在重复项。

这个决定对 hibernate 执行的查询有重要影响。以下是一个小例子:

有两个实体,员工和公司,典型的多对多关系。为了将这些实体映射到彼此,存在一个 JoinTable(我们称之为“employeeCompany”)。

您在两个实体(Company/Employee)上选择 数据类型 List

因此,如果您现在决定从 CompanyXY 中 移除 Employee Joe,则 hibernate 会执行以下查询:

delete from employeeCompany where employeeId = Joe;
insert into employeeCompany(employeeId,companyId) values (Joe,CompanyXA);
insert into employeeCompany(employeeId,companyId) values (Joe,CompanyXB);
insert into employeeCompany(employeeId,companyId) values (Joe,CompanyXC);
insert into employeeCompany(employeeId,companyId) values (Joe,CompanyXD);
insert into employeeCompany(employeeId,companyId) values (Joe,CompanyXE);

现在的问题是:为什么Hibernate不仅执行了那个查询?

delete from employeeCompany where employeeId = Joe AND company = companyXY;
答案很简单(非常感谢Nirav Assar的博客文章):它无法做到。在一个袋子的世界里,删除所有并重新插入所有剩余物品是唯一正确的方法!要了解更多信息,请阅读链接:http://assarconsulting.blogspot.fr/2009/08/why-hibernate-does-delete-all-then-re.html 现在是最大的结论:

如果您在Employee/Company - Entities中选择Set而不是List,就不会遇到这个问题,并且只执行一个查询!

为什么呢?因为Hibernate不再处于一个袋子的世界中(如您所知,Set不允许重复),现在可以执行仅一个查询。
因此,在涉及查询和性能的情况下,选择List和Sets之间的决定并不是那么简单!

8

我通常使用 List。相比于 Set,我发现 List API 更有用且与其他库更兼容。List 更易于迭代,并且对于大多数操作和内存来说通常更有效率。

一个关系不能有重复项并且通常不排序的事实不应该要求使用 Set,您可以使用最适合您的应用程序的任何 Collection 类型。

这取决于您的模型,如果您需要经常进行包含检查,则 Set 将更有效率。

您可以在 JPA 中对关系进行排序,可以使用 @OrderBy@OrderColumn

See, http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Ordering

JPA通常不支持重复项,但某些映射(如ElementCollections)可能支持重复项。


4

我使用:

  • Set集合: 当集合中的项没有顺序并且是唯一的时候
  • List列表: 当集合中的项有顺序时

2
你不应该总是使用Set吗?因为你将实体存储在关系型数据库中,其中不能有任何重复元素,并且必须自己定义顺序(即当Java List被持久化时,其顺序不会保留)。那么什么时候使用Collection呢? - Theo
1
“不能有任何重复元素”是什么意思?当然可以。你只需要将Id字段设置为主键,其他字段就可以重复了。 - Shervin Asgari
1
@Theo 对象的相等性设置依赖于 equals 方法。数据库的相等性纯粹依赖于主键。它们不一定相同。 - GaryF
1
@Theo:列表“index”被映射到数据库索引列(反之亦然)-因此列表中的顺序得以保留。 - Ralph
1
@Theo,不是这样的。当您在JPA2中定义“索引列表”时,来自java.util.List的位置被放置在该人工列中,起始位置为0。因此,列表的顺序得以保留...这就是透明持久性的全部意义所在。当您需要订购事实时,无论持久化技术如何,您都可以使用List。 - DataNucleus
显示剩余9条评论

2

请问您能否扩展您的回答并简要解释一下影响是什么? - Maksym Rudenko
我看到了OpenJPA添加额外的Order By子句到生成的查询中的问题。这对于那些不需要任何排序的查询在数据库上有性能影响。从上面的链接中可以看出,只有当集合被定义为List时才会发生这种情况。如果它被定义为Set,则会省略此Order By子句。 但不幸的是,无论我选择什么数据类型,我都无法摆脱Order By子句。 - Dyutiman Chaudhuri

0

我认为在使用Netbeans生成实体时,将Collection作为通用默认值是一个很好的起点。当你弄清楚你的模型实际上是什么并需要更多功能时,你可以轻松地进行更改并保持向后兼容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接