如何处理不遵守列表相等规则的Hibernate PersistentBag?

12

我有一个包含列表的实体:

@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具有相同的items。并且从getItemsIfSame方法返回了items列表。

但是当我运行应用程序并传递具有相同items的两个订单时,将返回null。经过调试和研究,我发现Order方法getItems返回的实际类型是org.hibernate.collection.internal.PersistentBag。其文档如下:

Bag不尊重集合API,并进行JVM实例比较以执行equals操作。语义被打破,因为不必初始化一个集合即可进行简单的equals()操作。

通过查看源代码,我们可以确认它只调用Object 的 equals方法(即使它实现了List)。

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

1个回答

3

解决方案1: 使用Guava的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中说明如下:
返回一个数组,该数组按正确的顺序包含此列表中的所有元素(从第一个到最后一个元素)。返回的数组将是“安全的”,因为此列表未保留对它的任何引用。 (换句话说,此方法必须分配一个新数组)。因此,调用者可以自由地修改返回的数组。
因此,使用Iterables在原地比较列表可能比解决方案2、3和4更节省内存,这些解决方案都隐式或显式地分配新的List或Array。
null检查也可以移出三元运算符,但需要对order对象进行两次检查,因为所有这些解决方案都涉及调用不是空值安全的方法(Iterables#elementsEqualLists#toArraynew ArrayList(Collection<?> collection)CollectionUtils.isEqualCollection在使用null时都会抛出NullPointerException)。
副笔:这个问题被追踪到一个长期存在的Hibernate bug

1
使用Collections.EMPTY_LIST不是比创建从未再次使用的空ArrayList实例更好吗?我觉得这可能是有关紧急性的问题。 - Octribin

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