Как работать с постоянной сумкой Гибернации, не подчиняющейся списку, равному контракту?

#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 amp;amp; order1.getItems().equals(order2.getItems())) {
           return order1.getItems();
     }
     return null;
 }
  

У меня есть модульный тест, в котором order1 и order2 имеют одинаковые элементы. И, как и ожидалось, список элементов возвращается из getItemsIfSame метода.

Но когда я запускаю свое приложение и ему передаются два заказа с одинаковыми элементами, возвращается значение null . После отладки и исследований я обнаружил, что фактический тип, возвращаемый Order методом getItems , равен org.hibernate.collection.internal.PersistentBag . В его документации указано:

Bag не учитывает API сбора и выполняет сравнение экземпляров JVM для выполнения equals. Семантика нарушена, чтобы не инициализировать коллекцию для простой операции equals() .

И, подтверждая в исходном коде, он просто вызывает Object метод equals (даже если он реализует List ).

Я полагаю, я мог бы скопировать все элементы из PersistentBag в ArrayList , а затем сравнить, но иногда я проверяю равенство объекта, который имеет какое-то вложенное свойство со списком. Есть ли какой-нибудь лучший способ проверить равенство списков между объектами?

Ответ №1:

Решение № 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());
  

Обратите внимание, что в Javadocs для List#toArray метода указано следующее:

Возвращает массив, содержащий все элементы в этом списке в правильной последовательности (от первого до последнего элемента). Возвращаемый массив будет «безопасным» в том смысле, что этот список не содержит ссылок на него. (Другими словами, этот метод должен выделить новый массив). Таким образом, вызывающий может свободно изменять возвращаемый массив.

Таким образом, сравнение списков на месте с использованием Iterables может использовать меньше памяти, чем решения 2, 3 и 4, которые все выделяют новые списки или массивы либо неявно, либо явно.

Проверка null также может быть удалена из троичного, но должна выполняться для обоих order объектов, потому что все эти решения включают вызов методов, которые не являются безопасными для null ( Iterables#elementsEqual , Lists#toArray , new ArrayList(Collection<?> collection) , CollectionUtils.isEqualCollection все будут вызывать NullPointerExceptions при вызове с null).

Примечание: эта проблема отслеживается из-за давней ошибки hibernate

Комментарии:

1. Не лучше ли было бы использовать коллекции. EMPTY_LIST вместо создания пустых экземпляров ArrayList, которые больше никогда не используются? Я чувствую, что это может иметь значение.