使用 Hibernate 和 TreeSet 对 remove() 和 contains() 方法不起作用

Using Hibernate and TreeSet does not work the remove() and contains() methods

我想要一个按年龄排序的集合;

在这种情况下,方法 compareTo() 工作正常,但问题是 remove()contians () 方法 returns 总是 false;

有趣: 如果我取消对 compareTo() 方法行的注释,remove() 和 contains() 方法可以正常工作;但我想使用其他字段作为排序。

有人知道为什么不能正常工作吗?发现旧的 Hibernate 问题:https://hibernate.atlassian.net/browse/HHH-2634; 这已经修复了吗?

下面是使用过的 类:

@Entity(name = "CAMPAIGN")
public class Campaign implements Identifiable, Serializable {
    public static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Long id;


    @OneToMany(mappedBy = "campaign", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @OrderBy("age ASC")
    private SortedSet<MailingAddress> mailingAddresses = new TreeSet<>();

    ...

    public void removeMailingAddress(MailingAddress mailingAddress) {
        this.mailingAddresses.remove(mailingAddress);
        //this.mailingAddresses.contains(mailingAddress);

        mailingAddress.setCampaign(null);
    }
}

@Entity(name = "MAILING_ADDRESS")
public class MailingAddress implements Identifiable, Comparable, Serializable {
    public static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "CAMPAIGN_ID")
    private Campaign campaign;

    @Column(name = "AGE")
    private Integer age;

    @Override
    public int compareTo(Object o) {
        if (o == null) {
            return 1;
        }

        if (!(o instanceof MailingAddress)) {
            throw new ClassCastException("Cannot compare MailingAddress with " + o.getClass());
        }

        MailingAddress o1 = (MailingAddress) o;
        int comparison;

        // comparison for id
        /*comparison = compareFields(this.id, o1.id);
        if (comparison != 0) {
            return comparison;
        }*/

        // comparison for ageBand
        comparison = compareFields(this.age, o1.age);
        if (comparison != 0) {
            return comparison;
        }

        return 0;
    }

    private int compareFields(Comparable field1, Comparable field2) {
        if (field1 == null && field2 == null) {
            return 0;
        } else if (field1 == null && field2 != null) {
            return -1;
        } else if (field1 != null && field2 == null) {
            return 1;
        }
        return field1.compareTo(field2);
    }

    @Override
    public boolean equals(Object o) {
        return this.compareTo(o) == 0;
    }

}

更新:

发现使用 SortedSet 作为 TreeSet 的接口并结合 Hibernate 方法删除() 和 contains() 无法正常工作。 "SortedSet mailingAddresses = new TreeSet<>();"

将定义更改为 "Set mailingAddresses = new TreeSet<>();" 以及方法 remove()contains() 工作正常;此外,使用 compareTo() 的排序也适用于 id.

以外的其他字段

TreeSet、SortedSet 和 Hibernate 的组合可能存在错误。如果有人对此 "bug" 找到了解释,请告诉我。

这是一个工作版本:

@Entity
public class MailingAddress implements Identifiable, Comparable, Serializable {
    public static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Long id;

    private Integer age;

    @Override
    public int compareTo(Object o) {
        if (o == null) {
            return 1;
        }

        if (!(o instanceof MailingAddress)) {
            throw new ClassCastException("Cannot compare MailingAddress with " + o.getClass());
        }

        MailingAddress o1 = (MailingAddress) o;
        int comparison = compareFields(this.age, o1.age);
        if (comparison != 0) {
            return comparison;
        }

        return 0;
    }

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

        MailingAddress that = (MailingAddress) o;

        if (id != null ? !id.equals(that.id) : that.id != null) return false;
        return age != null ? age.equals(that.age) : that.age == null;
    }

    @Override
    public int hashCode() {
        return 31;
    }

    private int compareFields(Comparable field1, Comparable field2) {
        if (field1 == null && field2 == null) {
            return 0;
        } else if (field1 == null && field2 != null) {
            return -1;
        } else if (field1 != null && field2 == null) {
            return 1;
        }
        return field1.compareTo(field2);
    }
}

@Entity
public class Campaign implements Identifiable, Serializable {
    public static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Long id;


    @OneToMany(mappedBy = "campaign", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @OrderBy("age ASC")
    private Set<MailingAddress> mailingAddresses = new TreeSet<>();

    ...
}

这里的问题是你覆盖了 equals 而没有覆盖 hashCode.

此外,引用检查不适用于合并实体状态转换。

由于您在 MailingAddress 中没有自然业务键,您需要使用如下实体标识符:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof MailingAddress)) return false;
    MailingAddress ma = (MailingAddress) o;
    return getId() != null && Objects.equals(getId(), ma.getId());
}

@Override
public int hashCode() {
    return getClass().hashCode();
}

getClass().hashCode() returns 是所有实例的常量值,因此允许在 HashSet 中找到具有 null id 的实体,甚至在瞬态实体上调用 persist 后更改 id 之后。

但是,这还不是全部。

为什么要用 TreeSet@OrderBy("age ASC")。该顺序在查询时给出,然后您在 Java 中覆盖它。由于您使用 @OrderBy,因此使用 List 更有意义,因为排序是在执行 SELECT 语句时完成的。