Javers, сравнивая списки в объекте, давая противоречивые результаты - PullRequest
0 голосов
/ 26 апреля 2018

Я изо всех сил пытаюсь понять, почему следующее не дает мне таких же результатов.

Если я использую Javers, чтобы просто сравнить два списка (у которых есть элементы в разных порядках), то я не получу никаких различий, поскольку я указал сравнение списков AS_SET, чтобы игнорировать порядок элементов в списке.

Если я затем оберну эти списки как свойство объекта, то Javers вернет, что элементы списка отличаются, потому что или порядок элементов в списке.

Должно ли AS_SET применяться к спискам внутри объектов? Это как если бы оно игнорировалось

public class App {
    public static void main(String[] args) {

        List<ListItem> list1 = ImmutableList.of(
                ListItem.builder()
                        .itemName("item1")
                        .itemValue("value")
                        .build(),
                ListItem.builder()
                        .itemName("item2")
                        .itemValue("value2")
                        .build()
        );

        List<ListItem> list2 = ImmutableList.of(
                ListItem.builder()
                        .itemName("item2")
                        .itemValue("value2")
                        .build(),
                ListItem.builder()
                        .itemName("item1")
                        .itemValue("value")
                        .build()
        );

        TopLevelClass tlc1 = TopLevelClass.builder().items(list1).build();
        TopLevelClass tlc2 = TopLevelClass.builder().items(list2).build();

        Diff diff = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.AS_SET).build().compare(list1, list2);
        System.out.println(diff);

        Diff diffTlc = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.AS_SET).build().compare(tlc1, tlc2);
        System.out.println(diffTlc);
    }
}

Классы ниже:

package wibble;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Builder
@Getter
@Setter
@EqualsAndHashCode
public class ListItem {
    private String itemName;
    private String itemValue;
}
package wibble;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Builder
@Getter
@Setter
@EqualsAndHashCode
public class TopLevelClass {
    List<ListItem> items;
}

Вывод, когда работает выше:

Diff:

Diff:
    * changes on wibble.TopLevelClass/ :
    - 'items/0.itemName' changed from 'item1' to 'item2'
    - 'items/0.itemValue' changed from 'value' to 'value2'
    - 'items/1.itemName' changed from 'item2' to 'item1'
    - 'items/1.itemValue' changed from 'value2' to 'value'

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Есть разные способы сделать это (их много)

Метод 1 - Обновить код с @ Id

Это похоже на то, что предложил @kriegaex, и заключается в добавлении аннотации @Id к вашему коду и обновлении класса, как показано ниже

public class ListItem {
  @Id
  private String itemName;
  private String itemValue;
}

Метод 2 - Регистрация организации

У Method 1 есть и обратная сторона: ему нужны изменения кода для вашей реальной модели, и это не всегда возможно или желательно. В этом случае вам необходимо вручную зарегистрировать ваше лицо

public static void main(String[] args) {

    List<ListItem> list1 = ImmutableList.of(
            ListItem.builder()
                    .itemName("item1")
                    .itemValue("value")
                    .build(),
            ListItem.builder()
                    .itemName("item2")
                    .itemValue("value2")
                    .build()
    );

    List<ListItem> list2 = ImmutableList.of(
            ListItem.builder()
                    .itemName("item2")
                    .itemValue("value2change")
                    .build(),
            ListItem.builder()
                    .itemName("item1")
                    .itemValue("value")
                    .build(),
            ListItem.builder()
                    .itemName("item3")
                    .itemValue("value3")
                    .build()

    );

    TopLevelClass tlc1 = TopLevelClass.builder().items(list1).build();
    TopLevelClass tlc2 = TopLevelClass.builder().items(list2).build();

    Javers jvc = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.AS_SET)
            .registerEntity(new EntityDefinition(ListItem.class, "itemName"))
            .build();
    Diff diffTlc = jvc.compare(tlc1, tlc2);
    System.out.println(diffTlc);
}

Выход вышеупомянутого прогона ниже

* changes on com.javerstest.ListItem/item2 :
  - 'itemValue' changed from 'value2' to 'value2change'
* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.ListItem@306a04bf' removed
    . 'com.javerstest.ListItem@306a04fb' added
    . 'com.javerstest.ListItem@29f62baf' added
* new object: com.javerstest.ListItem/item3

А без .registerEntity(new EntityDefinition(ListItem.class, "itemName")) это

* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.ListItem@306a04bf' removed
    . 'com.javerstest.ListItem@306a04fb' added
    . 'com.javerstest.ListItem@29f62baf' added
  - 'items/0.itemName' changed from 'item1' to 'item2'
  - 'items/0.itemValue' changed from 'value' to 'value2change'
  - 'items/1.itemName' changed from 'item2' to 'item1'
  - 'items/1.itemValue' changed from 'value2' to 'value'

Метод 3 - Использование @ IgnoreDeclaredProperties

Итак, после разъяснения в следующем разделе, другой способ сделать это ниже

@IgnoreDeclaredProperties
public class ListItem {
    private String itemName;
    private String itemValue;
}

Это позволит List сравнивать элементы работы и отдыха, не добавленные. Но это не позволит вам сравнить ListItem напрямую.

Поэтому рекомендуется использовать только Method 2, если вы не хотите, чтобы изменения в ваших моделях, а также полная гибкость

Метод 4 - Использование @ ShallowReference

Можно добавить @ShallowReference к предметам, и тогда будет сделано правильное сравнение наборов

public class TopLevelClass {

    @ShallowReference
    List<ListItem> items;
}

В настоящее время с 02-мая-18 это не работает из-за ошибки, объясненной позже

Метод 5 - Использование множеств

Вы можете использовать Set вместо List в своем классе, если хотите

public class TopLevelClass {
   Set<ListItem> items;
}

И обновленный код сравнения будет

TopLevelClass tlc1 = TopLevelClass.builder().items(new HashSet<ListItem>(list1)).build();
TopLevelClass tlc2 = TopLevelClass.builder().items(new HashSet<ListItem>(list2)).build();

Javers jvc = JaversBuilder.javers().build();

С выводом, как показано ниже

Diff:
* new object: com.javerstest.TopLevelClass/#items/bd3fdf9ee4c8eb797ca392a1f8eb28c6
* new object: com.javerstest.TopLevelClass/#items/ad5b96d68b6742a92d330f0d98bae8b3
* object removed: com.javerstest.TopLevelClass/#items/a1961e7fd2e23b166e4d1b2acbe67263
* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.TopLevelClass/#items/a1961e7fd2e23b166e4d1b2acbe67263' removed
    . 'com.javerstest.TopLevelClass/#items/bd3fdf9ee4c8eb797ca392a1f8eb28c6' added
    . 'com.javerstest.TopLevelClass/#items/ad5b96d68b6742a92d330f0d98bae8b3' added

Метод 6 - Зарегистрировать ListItem в качестве значения

В этом вы можете зарегистрировать ListItem в качестве значения

Javers jvc = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.AS_SET)
            .registerValue(ListItem.class)
            .build();
Diff diffTlc = jvc.compare(tlc1, tlc2);
System.out.println(diffTlc);

И вывод

* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.ListItem@306a04bf' removed
    . 'com.javerstest.ListItem@306a04fb' added
    . 'com.javerstest.ListItem@29f62baf' added

Функция? / Ошибка? / ограничение

Теперь еще одна вещь, которая происходит здесь, в коде ниже, если вы посмотрите на вывод нашего Method2 без .registerEntity(new EntityDefinition(ListItem.class, "itemName"))

* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.ListItem@306a04bf' removed
    . 'com.javerstest.ListItem@306a04fb' added
    . 'com.javerstest.ListItem@29f62baf' added

Это в основном из-за ListCompareAlgorithm.AS_SET, если вы измените его на SIMPLE, выходной сигнал изменится ниже

  - 'items' collection changes :
    0. '...ListItem/item1' changed to '...ListItem/item2'
    1. '...ListItem/item2' changed to '...ListItem/item1'
    2. '...ListItem/item3' added

Так в нашем оригинальном коде

Javers jvc = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.AS_SET).build();
Diff diffTlc = jvc.compare(tlc1, tlc2);

Сначала он сравнивает list с использованием только set и добавляет изменение, как показано ниже

* changes on com.javerstest.TopLevelClass/ :
  - 'items' collection changes :
    . 'com.javerstest.ListItem@306a04bf' removed
    . 'com.javerstest.ListItem@306a04fb' added
    . 'com.javerstest.ListItem@29f62baf' added

Но затем он снова идет дальше и делает еще один diff для каждого элемента массива, поэтому добавляется дополнительный diff

  - 'items/0.itemName' changed from 'item1' to 'item2'
  - 'items/0.itemValue' changed from 'value' to 'value2change'
  - 'items/1.itemName' changed from 'item2' to 'item1'
  - 'items/1.itemValue' changed from 'value2' to 'value'

Таким образом, дело не в том, что AS_SET не выбран, а в том, что различие сначала выполняется для list как set, а также на уровне отдельного предмета. Я поднял вопрос о том же, чтобы лучше понять это

https://github.com/javers/javers/issues/669

0 голосов
/ 02 мая 2018

Как уже упоминалось @kriegaex, вы можете отобразить ListItem как сущность или значение, и это решит проблему. Но это был бы обходной путь, потому что ListItem выглядит как Value Object. Другим обходным решением было бы изменить список на Set.

Похоже, сейчас нельзя сравнивать объекты-значения в списке с алгоритмом AS_SET. В этом случае существует проблема с генерацией ValueObjectIds. Обсуждение продолжается здесь https://github.com/javers/javers/issues/669

0 голосов
/ 02 мая 2018

Отказ от ответственности: На самом деле я никогда не слышал о JaVers прежде, я только наткнулся на этот вопрос и стал любопытным. Так что я здесь настоящий зеленый рог.

Глядя на примеры diff в руководстве по JaVers, вы читаете что-то вроде:

Конфигурация

JaVers необходимо знать, что класс Employee является Entity (...) Достаточно аннотировать поле имени с помощью аннотации @Id (...)

Итак, давайте попробуем это:

package wibble;

import lombok.*;
import org.javers.core.metamodel.annotation.Id;

@Builder
@Getter
@Setter
@EqualsAndHashCode
@ToString
public class ListItem {
  @Id
  private String itemName;
  private String itemValue;
}

Журнал консоли становится:

Diff:

Diff:

Надеюсь, это то, что вы хотели.

...