Странное поведение один-ко-многим с Spring Data JDBC - PullRequest
0 голосов
/ 29 ноября 2018

В итоге я столкнулся с чем-то действительно странным в агрегатной обработке Spring Data JDBC (использующей Spring Boot 2.1 с необходимыми стартерами).Позвольте мне объяснить этот случай (я использую Lombok, проблема может быть связана, однако) ...

Это выдержка из моей сущности:

import java.util.Set;
@Data
public class Person {
    @Id
    private Long id;
    ...
    private Set<Address> address;
}

Это связаноРепозиторий Spring Data:

public interface PersonsRepository extends CrudRepository<Person, Long> {
}

И это тест, который не проходит:

@Autowired
private PersonsRepository personDao;
...
Person person = personDao.findById(1L).get();
Assert.assertTrue(person.getAddress().isEmpty());
person.getAddress().add(myAddress); // builder made, whatever
person = personDao.save(person);
Assert.assertEquals(1, person.getAddress().size()); // count is... 2!

Дело в том, что при отладке я обнаружил, что коллекция адресов (которая является Set)содержащий две ссылки на один и тот же экземпляр присоединенного адреса.Я не вижу, как заканчиваются две ссылки, и, самое главное, как SET (на самом деле LinkedHashSet для записи) может обрабатывать один и тот же экземпляр ДВАЖДЫ!

person  Person  (id=218)    
    address LinkedHashSet<E>  (id=228)  
        [0] Address  (id=206)   
        [1] Address  (id=206)   

Кто-нибудь знает, что этоситуация?Thx

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

Объяснение Томаша Линковски в значительной степени верно.Но я бы поспорил с другим решением проблемы.

Внутри происходит следующее: сущность Person сохраняется.Это может или не может создать новый экземпляр Person, если Person является неизменным.

Затем Address сохраняется и тем самым получает новый id, который меняет свой хеш-код.Затем Address добавляется к Person, так как снова это может быть новый экземпляр Address.

Но это тот же экземпляр, но теперь с измененным хеш-кодом, что приводит к единственному набору, содержащемуто же самое Address дважды.

Что нужно сделать, чтобы это исправить:

Определите equals и hashCode, чтобы оба были стабильны при сохранении экземпляра

т.е. hashCode не должен изменяться при сохранении экземпляра или при выполнении чего-либо еще в вашем приложении.

Существует несколько возможных подходов.

  1. base equals и hashCode для подмножества полей, исключая Id.Убедитесь, что вы не редактируете эти поля после добавления Address к Set.По сути, вы должны обращаться с ним как с неизменным классом, даже если это не так.С точки зрения DDD это обрабатывает сущность как класс значений.
  2. base equals и hashCode на Id и устанавливает Id в конструкторе.С точки зрения домена это рассматривает класс как правильную сущность, которая идентифицируется его идентификатором.
0 голосов
/ 29 ноября 2018

A (Linked)HashSet может (в качестве побочного эффекта) сохранить один и тот же экземпляр дважды, если этот экземпляр был мутирован за это время (цитата из Set):

Примечание. Необходимо соблюдать осторожность, если в качестве заданных элементов используются изменяемые объекты.Поведение набора не указывается, если значение объекта изменяется таким образом, что это влияет на equals сравнений, пока объект является элементом в наборе.

Так вот что, вероятно, происходит:

  1. Вы создаете новый экземпляр Address, но его идентификатор не задан (id=null).
  2. Вы добавляете его в Set, и его хэш-кодрассчитывается как некоторое значение A.
  3. Вы вызываете PersonsRepository.save, который, скорее всего, сохраняет Address и устанавливает для него некоторый ненулевой идентификатор.
  4. Возможно, PersonsRepository.save также вызываетHashSet.add, чтобы убедиться, что адрес равен в наборе.Но поскольку идентификатор изменился, хеш-код теперь вычисляется как некоторое значение B.
  5. Хеш-коды A и B отображаются в разные сегменты в HashSet, и поэтому Address.equals метод даже не вызывается во время HashSet.add.В результате вы получаете один и тот же экземпляр в двух разных сегментах.

Наконец, я думаю, что ваши сущности должны иметь семантику equals / hashCode, основанную только на идентификаторе.Чтобы добиться этого с помощью Lombok, вы должны использовать @EqualsAndHashCode следующим образом:

@Data
@EqualsAndHashCode(of = "id")
public class Person {
    @Id
    private Long id;
    ...
}

@Data
@EqualsAndHashCode(of = "id")
public class Address {
    @Id
    private Long id;
    ...
}

Тем не менее, это не решит вашу проблему, так как меняется идентификатор, поэтому хэш-коды будут отличаться.

Один из способов справиться с этим - сохранить Address до , добавив его в Set.

...