I want to have a sorted set by age;
The method compareTo() in this case works fine but the problem is that remove() and contians() methods returns always false;
INTERESTING: In case I uncomment the lines form compareTo() method, remove() and contains() methods works fine; but I want to use the other field as sorting.
Does someone have any idea why does not work properly; Found old Hibernate issue: https://hibernate.atlassian.net/browse/HHH-2634; is this already fixed?
Bellow are the used classes:
@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);
}
}
And
@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;
}
}
UPDATE:
Found that using SortedSet as interface for TreeSet in combination with Hibernate the methods remove() and contains() does not work properly. "SortedSet mailingAddresses = new TreeSet<>();"
Changed the definition to "Set mailingAddresses = new TreeSet<>();" and the methods remove() and contains() works fine; Also the sorting that is using compareTo() is working also for other fields than id.
Probably there is a bug in combination of TreeSet, SortedSet and Hibernate. If someone found an explanation for this "bug" please let me know.
Here is a working version:
@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);
}
}
AND
@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<>();
...
}
The problem here is that you override equals
without overriding hashCode
.
Also, the reference check does not work for the merge entity state transition.
Since you don't have a natural business key in MailingAddress
, you need to use the entity identifier like this:
@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();
}
The getClass().hashCode()
returns a constant value for allinstances, therefore allowing an entity that has a null
id
to be found in a HashSet
even after the id
is changed after calling persist
on the transient entity.
But, that's not all.
Why do you use a TreeSet
with @OrderBy("age ASC")
. The order is given at query-time, and then you override that in Java. Since you use @OrderBy
, it makes more sense to use a List
since the sorting is done when executing the SELECT statement.