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

时间:2019-04-10 20:51:11

标签: list hibernate 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;
}

我有一个服务,用于检查两个订单是否具有相同的项目,如果有则返回这些项目;否则返回null:

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

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

但是,当我运行我的应用程序并通过两个带有相同项目的订单时,返回null。经过调试和研究之后,我发现Order方法getItems返回的实际类型是org.hibernate.collection.internal.PersistentBag。其文档指出:

  

包不遵守集合API,并且进行JVM实例比较来实现相等性。不必为简单的equals()操作初始化集合就可以打破语义。

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

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

1 个答案:

答案 0 :(得分:0)

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

Iterables.elementsEqual(
            order1.getItems() != null ? order1.getItems() : new ArrayList<>(),
            order2.getItems() != null ? order2.getItems() : new ArrayList<>());

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

    Objects.deepEquals(
        order1.getItems() != null ? order1.getItems().toArray() : order1,
        order2.getItems() != null ? order2.getItems().toArray() : order2);

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

(order1.getItems() != null ? new ArrayList(order1.getItems()) : new ArrayList())
        .equals(order2.getItems() != null ? new ArrayList(order2.getItems()) : new ArrayList());

解决方案#4 使用Apache的CollectionUtils#isEqualCollection

    CollectionUtils.isEqualCollection(
        order1.getItems() != null ? order1.getItems() : new ArrayList(),
        order2.getItems() != null ? order2.getItems() : new ArrayList());

请注意,List#toArray方法的Javadocs声明如下:

  

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

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

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

旁注:long-standing hibernate bug

跟踪了此问题