【问题标题】:removeif() not working on list returned by jparemoveif() 不适用于 jpa 返回的列表
【发布时间】:2023-11-13 20:13:01
【问题描述】:

我在理解为什么我不能在 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
}

【问题讨论】:

  • 这很难相信,因为 removeIf() 默认实现基本上是您发布的注释代码。顺便说一句,我不知道你在看哪个文档,但eclipse.org/eclipselink/api/2.6/org/eclipse/persistence/… 清楚地表明 IndirectList 实现了 List。它甚至扩展了 Vector。
  • @JBNizet 你说得对,它确实实现了列表,我刚醒来,我错过了它。
  • @JBNizet 但我很肯定 lambda expr 对我不起作用,而迭代器起作用。
  • 你有什么错误吗?实际结果是什么?恐怕“不起作用”在这里描述得不够充分。顺便说一句,您可以尝试使用TypedQuery 而不是Query 以避免在调用query.getSingleResult() 时进行强制转换
  • @Sva.Mu 感谢您的提示。不起作用我的意思是我没有错误并且列表的大小保持不变。我不确定如何检查内部发生的情况?

标签: java jpa jpql


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    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());
    

    【讨论】:

      【解决方案3】:

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

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

      假设你在 JPA 中有一个 OneToMany/ManyToMany 关系,所以一个 Collection - 让它成为一个有成员的 Group。组/用户都是正确的 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) 创建了一个实际的不可变 数组,其大小强制固定。在其上运行 remove 将失败并显示 OperationNotSupport

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

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

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

      请注意,如果您使用 JPA 工具,此问题可能会变得更加隐蔽,因为它需要一个可变列表,并且如果无法对其进行操作,则可以通过您获得更多加密异常。

      【讨论】: