#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, которые больше никогда не используются? Я чувствую, что это может иметь значение.