如何使用 Hibernate 的 PersistentBag 不遵守 List equals 合同?

Jam*_*mes 7 hibernate list equals

我有一个带有列表的实体:

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)  
    @JoinColumn(name="orderId", nullable=false)
    private List<Item> items;
}

@Entity
@Data
public class Item {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @EqualsAndHashCode.Exclude
    private Long id;

    private String description;
}
Run Code Online (Sandbox Code Playgroud)

我有一项服务,可以检查两个订单是否具有相同的商品,如果是,则返回商品;否则返回空值:

public List<Item> getItemsIfSame(Order order1, Order order2) {
      if (order1.getItems() != null && order1.getItems().equals(order2.getItems())) {
           return order1.getItems();
     }
     return null;
 }
Run Code Online (Sandbox Code Playgroud)

我有一个单元测试,其中 order1 和 order2 具有相同的项目。正如预期的那样,项目列表从该getItemsIfSame方法返回。

但是当我运行我的应用程序并且它传递了两个具有相同项目的订单时,返回 null 。经过调试和研究,我发现该Order方法返回的实际类型getItemsorg.hibernate.collection.internal.PersistentBag. 它的文档指出:

Bag 不尊重集合 API 并进行 JVM 实例比较以进行相等。语义被破坏,不必为简单的 equals() 操作初始化集合。

并在源代码中确认,它只是调用Object的 equals 方法(即使它实现了List)。

我想我可以从PersistentBagto复制所有元素ArrayList然后进行比较,但有时我正在检查具有列表嵌套属性的对象的相等性。有没有更好的方法来检查实体之间列表的相等性?

pan*_*nts 3

解决方案#1:使用番石榴Iterables#elementsEqual

Iterables.elementsEqual(
            order1.getItems() != null ? order1.getItems() : new ArrayList<>(),
            order2.getItems() != null ? order2.getItems() : new ArrayList<>());
Run Code Online (Sandbox Code Playgroud)

解决方案#2:使用java.util.Objects#deepEquals

    Objects.deepEquals(
        order1.getItems() != null ? order1.getItems().toArray() : order1,
        order2.getItems() != null ? order2.getItems().toArray() : order2);
Run Code Online (Sandbox Code Playgroud)

解决方案#3:使用新ArrayList对象

(order1.getItems() != null ? new ArrayList(order1.getItems()) : new ArrayList())
        .equals(order2.getItems() != null ? new ArrayList(order2.getItems()) : new ArrayList());
Run Code Online (Sandbox Code Playgroud)

解决方案 #4使用 ApacheCollectionUtils#isEqualCollection

    CollectionUtils.isEqualCollection(
        order1.getItems() != null ? order1.getItems() : new ArrayList(),
        order2.getItems() != null ? order2.getItems() : new ArrayList());
Run Code Online (Sandbox Code Playgroud)

请注意,该方法的 JavadocsList#toArray声明如下:

返回一个数组,其中按正确顺序(从第一个元素到最后一个元素)包含此列表中的所有元素。返回的数组将是“安全的”,因为该列表不维护对它的引用。(换句话说,这个方法必须分配一个新的数组)。因此,调用者可以自由修改返回的数组。

因此,使用就地比较列表Iterables可能比解决方案 2、3 和 4 使用更少的内存,这些解决方案都隐式或显式分配新的列表或数组。

null 检查也可以移出三元组,但需要在两个对象上执行,order因为所有这些解决方案都涉及调用非 null 安全的方法(Iterables#elementsEqualLists#toArraynew ArrayList(Collection<?> collection)CollectionUtils.isEqualCollection当使用 null 调用时都会抛出 NullPointerExceptions )。

旁注:此问题是由长期存在的休眠错误跟踪的