removeif() 不适用于 jpa 返回的列表

removeif() not working on list returned by jpa

我无法理解为什么我不能对 jpa 返回的集合使用 removeIf() 但我可以使用迭代器来这样做。

@PersistenceContext(unitName = "my-pu")
private EntityManager em;
@Override
public void removeUserFromGroup(String username, Group group) {
    Query query = em.createNamedQuery("Group.getByName", Group.class);
    query.setParameter("name", group.getGroupName());
    Group qGroup = (Group) query.getSingleResult();
    // this works
    // Iterator<User> i = qGroup.getUsers().iterator();
    // while (i.hasNext()) {
    // User o = i.next();
    // if (o.getUsername().equals(username)) {
    // System.out.println("eqqq");
    // i.remove();
    // }
    // }
    System.out.println("class: " + qGroup.getUsers().getClass().getName());
    // org.eclipse.persistence.indirection.IndirectList
    qGroup.getUsers().removeIf(u -> u.getUsername().equals(username));// doesn't work
}

出现这种奇怪行为的典型原因是您有 hashCode(和 equals)的自定义实现。如果在这种情况下您更改了对象并导致 hashCode 不同,那么对于 JDK 集合,即使使用 iterator.remove() 也无法从 Set 中删除该对象。 JDK 集合通过重新计算 hashCode 并使用该哈希执行对象的删除来实现删除。如果散列已更改,则删除失败,并且 JDK 实现将忽略它,尽管它们 return 作为删除的结果为真,这意味着集合实际上已经更改,即使它没有更改。悲伤但真实。

完美,另一种方法是抛出一个新的列表来解决问题...例如:

if (!CollectionUtils.isEmpty(getEvento().getAtividade())) {
    Set<Atividade> listAtividade = getEvento().getAtividade();
    getEvento().setAtividade(new HashSet<>());
    getEvento().getAtividade().addAll(listAtividade);
}
getEvento().getAtividade().removeIf(a -> 
a.getEspecialidade().getId().equals(especialidade.getId()));
or
getEvento().getAtividade().removeAll(getListAtividadeNotSelected());

最有可能的实际答案通常既不是 Equals / Hash(请将 Lombok 与 @EqualsAndHashCode 一起使用)也不是创建新列表(只是隐藏了实际问题)。

事实上,很可能是 Arrays.asListCollections.singletonList 的用法创建了一个不可变的 ArrayList.

假设您在 JPA 中有一个 OneToMany/ManyToMany 关系,所以一个集合 - 假设它是一个有成员的组。组/用户都是正确的 JAP @Entity

var user1 = new User();
var user2 = new User();
var group = new Group();


group.addMembers(Arrays.asList(user1,user2));
group = groupRepository.save(group);

// will throw an exception
group.getMembers().removeIf((user) user.getId() == 1);

原因是,Arrays.asList(user1,user2) 创建了一个实际的 不可变 数组,其大小强制固定。 运行 删除它,将失败 OperationNotSupport

因为 Arryas.asListCollections.singletonList 无论如何都很笨重(因为你必须为 1+ 选择前者,为 1 元素选择后者)并且在这两种情况下你都可以使用你可能不知道的不可变数组的,而是使用 Guavas

Lists.newArrayList(user1,user2);
Lists.newArrayList(user1);

这适用于任何大小,并且数组是可变的。

请注意,如果您使用 JPA 工具,这个问题可能会变得更加隐蔽,因为它需要一个可变列表,并且如果它无法操作它,可能会通过您传递更多加密的异常。