为什么 ebean (ORM) 模型对象的 HashSet 上的 iterator.remove() 不起作用?
Why does iterator.remove() on a HashSet of ebean (ORM) model-objects not work?
我在我的 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;
}
请注意:如果我在没有数据库的情况下进行单元测试,此方法确实有效。如果我在 运行 假应用程序中对 "live" 模型和测试数据库执行相同操作,它就不起作用。调试时,我发现迭代后没有删除任何内容(当我将删除模型移出迭代或将其放在 iterator.remove()
之前时没有区别)。我真的不明白这一点,因为我没有传递另一个对象,只是一个字符串,而只是试图删除迭代器的当前对象。我在迭代时也没有在 remove()
之前修改 Set,因此散列码不应该改变(或者我遗漏了什么?)。
我确实为我的模型实现了 equals()
和 hashCode()
(见下文),甚至简化了它们并排除了 super,但它没有改变任何东西。我 运行 没有想法,希望得到任何帮助。
Token
-型号class:
@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
-对象的 ID 在添加时是 null
,之后 ebean 会处理该 ID。如果这使令牌对象不可移动,我应该如何处理?我尝试从 Token
的 equals()
和 hashCode()
方法中排除 id,但这也没有改变任何东西..
好吧,我落入了基于散列的集合中可变字段的陷阱。它缩小到我在编辑中输入的内容:当使用像 ebean 这样的 ORM 时,id
字段是可变的(以及其他字段,例如用 @CreatedTimestamp
注释的日期)。那么我们学到了什么:
Using mutable fields in hashCode() is a recipe for disaster. And
disaster strikes when instances of this class are put in a hash-based
collection like HashSet or HashMap (as map keys).
来源:http://blog.mgm-tp.com/2012/03/hashset-java-puzzler/
这意味着我必须在保存后从数据库中重新加载这些模型对象,因为只有这样 id
字段才会在它们被添加到 HashSet
时填充,并且只有然后他们可以被删除。另一种方法是从 hashCode()
方法中排除 id
字段,但由于它们是唯一标识符,我会小心处理(我想如果你确保包括其他唯一字段)。
我在我的 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;
}
请注意:如果我在没有数据库的情况下进行单元测试,此方法确实有效。如果我在 运行 假应用程序中对 "live" 模型和测试数据库执行相同操作,它就不起作用。调试时,我发现迭代后没有删除任何内容(当我将删除模型移出迭代或将其放在 iterator.remove()
之前时没有区别)。我真的不明白这一点,因为我没有传递另一个对象,只是一个字符串,而只是试图删除迭代器的当前对象。我在迭代时也没有在 remove()
之前修改 Set,因此散列码不应该改变(或者我遗漏了什么?)。
我确实为我的模型实现了 equals()
和 hashCode()
(见下文),甚至简化了它们并排除了 super,但它没有改变任何东西。我 运行 没有想法,希望得到任何帮助。
Token
-型号class:
@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
-对象的 ID 在添加时是 null
,之后 ebean 会处理该 ID。如果这使令牌对象不可移动,我应该如何处理?我尝试从 Token
的 equals()
和 hashCode()
方法中排除 id,但这也没有改变任何东西..
好吧,我落入了基于散列的集合中可变字段的陷阱。它缩小到我在编辑中输入的内容:当使用像 ebean 这样的 ORM 时,id
字段是可变的(以及其他字段,例如用 @CreatedTimestamp
注释的日期)。那么我们学到了什么:
Using mutable fields in hashCode() is a recipe for disaster. And disaster strikes when instances of this class are put in a hash-based collection like HashSet or HashMap (as map keys).
来源:http://blog.mgm-tp.com/2012/03/hashset-java-puzzler/
这意味着我必须在保存后从数据库中重新加载这些模型对象,因为只有这样 id
字段才会在它们被添加到 HashSet
时填充,并且只有然后他们可以被删除。另一种方法是从 hashCode()
方法中排除 id
字段,但由于它们是唯一标识符,我会小心处理(我想如果你确保包括其他唯一字段)。