【问题标题】:Why does iterator.remove() on a HashSet of ebean (ORM) model-objects not work?为什么 ebean (ORM) 模型对象的 HashSet 上的 iterator.remove() 不起作用?
【发布时间】:2015-01-25 12:41:40
【问题描述】:

我在 Play 2.3 应用程序中使用 ebean 作为 ORM。当我遍历我的HashSet 以删除匹配的模型对象时,iterator.remove() 不起作用。为了确定要删除哪个模型,我什至不依赖modelObject.equals()-方法,而是简单地比较一个字符串:

public boolean deleteToken(final User user, final String token) {
    if (token == null || token.isEmpty()) return false;

    int previousTokenSetSize = user.tokens.size();
    Iterator<Token> iterator = user.tokens.iterator();
    while (iterator.hasNext()) {
      final Token tokenObj = iterator.next();
      if (tokenObj.token.equalsIgnoreCase(token)) { // simple String-comparison
        iterator.remove(); // this line is reached, but no effect!
        userRepository.delete(tokenObj);
        break;
      }
    }

    if (user.tokens.size() != previousTokenSetSize) {
      userRepository.update(user);
      return true;
    }
    return false;
  }

请注意:如果我在没有数据库的情况下进行单元测试,此方法确实有效。如果我对正在运行的假应用程序中的“实时”模型和测试数据库执行相同操作,则它不起作用。调试时,我看到迭代后没有任何内容被删除(当我将模型的删除移出迭代时,或者将它放在iterator.remove() 之前没有区别)。我真的不明白这一点,因为我没有传递另一个对象,只是一个字符串,只是试图删除迭代器的当前对象。我在迭代时也没有在 remove() 之前修改 Set,因此哈希码不应该改变(或者我错过了什么?)。

我确实为我的模型实现了 equals()hashCode()(见下文),甚至简化了这些模型并排除了 super,但它并没有改变任何东西。我的想法不多了,希望能提供任何帮助。

Token-model 类:

@Entity
public class Token extends Model {

    @Id
    public Long id;

    @ManyToOne()
    @JsonIgnore
    @Required
    @Column(nullable = false)
    public User user;

    @Column(length = 255, unique = true, nullable = false)
    @MaxLength(255)
    @Required
    public String token;

    public Token(User user, String token) {
        this.user = user;
        this.token = token;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Token token = (Token) o;

        if (id != null ? !id.equals(token.id) : token.id != null) return false;
        if (user.id != null ? !user.id.equals(token.user.id) : token.user.id != null) return false;
        if (!token.equals(token.token)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (user.id != null ? user.id.hashCode() : 0);
        result = 31 * result + token.hashCode();

        return result;
    }
}

User供参考(简体):

@Entity
public class User extends Model {

    @Id
    public Long id;

    // simplified to stress the relevant parts

    @OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
    @JsonIgnore
    public final Set<Token> tokens = new HashSet<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        // simplified, irrelevant variables excluded
        if (tokens != null ? !tokens.equals(user.tokens) : user.tkens != null) return false;
        if (id != null ? !id.equals(user.id) : user.id != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        // simplified, irrelevant variables excluded
        int result = 17;
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (tokens != null ? tokens.hashCode() : 0);
        return result;
    }
}

编辑

我确实找到了这个:HashSet.remove() and Iterator.remove() not working

所以,我猜这些对象在添加到HashSet 后发生了变化。仅仅因为使用 ebean,我创建了 Token-object,将其添加到用户的令牌集中,然后在 DB 中更新用户:

public String createToken(final User user) {
    final String newToken = generateToken();
    final Token token = new Token(user, newToken);
    user.tokens.add(token);

    userRepository.update(user);

    return newToken;
}

这意味着Token-object的ID在添加时是null,之后ebean会处理这个ID。如果这使令牌对象不可移动,我应该如何处理?我尝试从Tokenequals()hashCode() 方法中排除id,但这也不会改变任何东西..

【问题讨论】:

    标签: java iterator playframework-2.0 hashset ebean


    【解决方案1】:

    好吧,我陷入了基于哈希的集合中可变字段的陷阱。它缩小到我在编辑中的内容:当使用像 ebean 这样的 ORM 时,id-字段是可变的(以及其他字段,例如用 @CreatedTimestamp 注释的日期)。那么我们学到了什么:

    在 hashCode() 中使用可变字段是灾难的根源。和 当这个类的实例被放入一个基于哈希的 HashSet 或 HashMap 之类的集合(作为映射键)。

    来源:http://blog.mgm-tp.com/2012/03/hashset-java-puzzler/

    这意味着我必须在保存后从数据库中重新加载这些模型对象,因为只有在将它们添加到 HashSet 时才会填充 id 字段,然后才能删除它们。另一种方法是从hashCode()-方法中排除id-字段,但由于它们是唯一标识符,我会小心处理(我想如果你确保其他唯一字段是包括)。

    【讨论】:

    • 请注意,它在 Play 中的工作方式与纯 Ebean 之间存在差异。特别是对于 Ebean,hashCode 不会改变……而对于 Play Model,他们决定改变这种行为。
    • 有趣,很高兴知道谢谢。所以他们为 Play 使用了不同的版本?因为我只是从普通的 Ebean Model 扩展而来。但是知道究竟是什么导致它让我能够解决它。
    • Play 有它自己的模型对象(它覆盖了 equals 和 hashCode 方法)。 ...所以现在再次阅读您的问题,我想我误解了。再看一遍,我现在看到你有自己的 equals/hashCode 实现,它能够改变(基于 bean 值的变化)——是的,你必须小心。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-11
    • 1970-01-01
    • 2013-03-16
    • 1970-01-01
    • 2019-01-27
    相关资源
    最近更新 更多