我碰巧在 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 数据存储库:
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(实际上是一个 LinkedHashSet,为了记录)如何能够处理同一个实例两次!
person Person (id=218)
address LinkedHashSet<E> (id=228)
[0] Address (id=206)
[1] Address (id=206)
有人知道这种情况吗?谢谢
最佳答案
一个(Linked)HashSet
当该实例同时发生变化时,可以(作为副作用)存储同一个实例两次(引用自 Set
):
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects
equals
comparisons while the object is an element in the set.
所以这就是可能发生的情况:
- 您创建
Address
的新实例但其 ID 未设置(id=null
)。 - 您将其添加到
Set
,其哈希码计算为某个值A
. - 您调用
PersonsRepository.save
这很可能持续存在Address
并为其设置一些非空 ID。 PersonsRepository.save
可能还打电话HashSet.add
确保地址在集中。但由于 ID 更改,哈希码现在计算为某个值B
.- 哈希码
A
和B
映射到HashSet
中的不同存储桶,所以Address.equals
方法甚至在HashSet.add
期间都不会被调用。因此,您最终会在两个不同的存储桶中获得相同的实例。
最后,我认为您的实体应该有 equals
/hashCode
仅基于 ID 的语义。要使用 Lombok 实现它,您可以使用 @EqualsAndHashCode
如下:
@Data
@EqualsAndHashCode(of = "id")
public class Person {
@Id
private Long id;
...
}
@Data
@EqualsAndHashCode(of = "id")
public class Address {
@Id
private Long id;
...
}
不过,这并不能解决您遇到的问题,因为 ID 发生了变化,因此哈希码仍然会有所不同。
处理此问题的一种方法是保留 Address
在将其添加到 Set
之前.
关于java - Spring Data JDBC 的奇怪一对多行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53543604/