The Problem with PersistentBag is that it violates the Collections API equals() contract, which breaks unit test assertions via AssertJ's containsExactlyInAnyOrder(). Hibernate also has different wrapper implementations such as PersistentList, is there a way to force Hibernate (version 5.6.7) to use those instead of PersistentBag?
#Entity
data class AnalysisType(
#Id
name: String,
#Column(name = "reference_type")
#OneToMany(fetch = LAZY, cascade = [])
val referenceTypes: List<ReferenceType>
)
#Entity
data class ReferenceType(
#Id
val id: Long,
#Column
val name: String
)
I think there's something wrong with the unit tests. Comparing two collections using an equals doesn't seem the right approach. In particular, when using unordered lists, elements might not always be in the same order.
You also need to make sure that you've implemented the correct hashcode/equals for the entities.
In java, for ReferenceType, it should look like:
#Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( !( o instanceof ReferenceType ) ) {
return false;
}
ReferenceType rt = (ReferenceType) o;
return Objects.equals( name, rt.name );
}
#Override
public int hashCode() {
return Objects.hash( name );
}
That said, if you want to have a PersistentList, the mapping needs to reflect an ordered list. You can achieve this using #OrderColumn:
#Entity(name = "Person")
public static class Person {
#Id
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#OrderColumn
private List<Phone> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
}
You can find more in the Hibernate ORM documentation in the section about collections of entities.
If you are using containsExactlyInAnyOrder from assertj the order of the elements does not matter. You can check for duplicates. If the PersistentBag contains the same element twice you should use containsOnly which does not care about the order and is ignoring duplicates. The equals method on the PersistentBag should not matter too because the contains... assertions in assertj are comparing element by element for the collections.
It looks like they may be sth wrong in your tests. Can you post assertj message?
Related
I need to compare two objects of the same class excluding some fields.
public final class Class1 {
private String a;
private String b;
private String c;
:
:
:
private String z;
private Date createdAt;
private Date updatedAt;
}
How can i find if the two objects of the above class are equal excluding createdAt and updatedAt values? Since there are a lot of fields in this class, i don't want to compare each of them one by one.
Please don't give AssertJ's recursive comparison solution as I don't need it for UnitTests.
Thank you in Advance!
If overriding Object::equals and Object::hashCode is not an option, we can use the Comparator API to construct a corresponding comparator:
final Comparator<Class1> comp = Comparator.comparing(Class1::getA)
.thenComparing(Class1::getB)
.thenComparing(Class1::getC)
.
.
.
.thenComparing(Class1::getZ);
Unfortunately, there is no way to do this without comparing all fields that should be equal.
The quickest way without writing any code is Lombok
Lombok is one of the most used libraries in java and it takes a lot of Boilerplate code off your projects. If you need to read more on what it can and does, go here.
The way to implement what you need is pretty straightforward:
// Generate the equals and HashCode functions and Include only the fields that I annotate with Include
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Getter // Generate getters for each field
#Setter // Generate setters for each field
public class Class1
{
#EqualsAndHashCode.Include // Include this field
private Long identity;
private String testStr1; // This field is not annotated with Include so it will not be included in the functions.
// ... any other fields
}
Lombok can do a lot more than this. For more information on #EqualsAndHashCode refer to this.
You can always use #EqualsAndHashCode.Exclude for a quicker solution to your use case:
#EqualsAndHashCode
#Getter // Generate getters for each field
#Setter // Generate setters for each field
public final class Class1 {
private String a;
private String b;
private String c;
:
:
:
private String z;
#EqualsAndHashCode.Exclude
private Date createdAt;
#EqualsAndHashCode.Exclude
private Date updatedAt;
}
Try overriding equals method like below :
import java.util.Date;
import java.util.Objects;
public final class Class1 {
private String a;
private String b;
private String c;
private String z;
private Date createdAt;
private Date updatedAt;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Class1 class1 = (Class1) o;
return Objects.equals(a, class1.a) && Objects.equals(b, class1.b) && Objects.equals(c, class1.c) && Objects.equals(z, class1.z);
}
#Override
public int hashCode() {
return Objects.hash(a, b, c, z);
}
}
I addition to the Comparator and hashCode()/equals method, you could also use Reflections.
Create an annotation to exclude certain fields:
Blacklisting Example:
#Retention(RetentionPolicy.RUNTIME) //
#Target(ElementType.FIELD) //on class level
public #interface IngoreForEqualCheck { /* tagging only */ }
Use Reflection to analyze the objects you want to compare, by using pClass.getFields() and/or pClass.getDeclaredFields() on the objects' class. This may be even different classes.
Iterate over all fields that are NOT tagged to be ignored, compare values.
Optimizations
To extend on the blacklisting from above, also introduce whitelisting: also create an annotation UseForEqualCheck to only check those fields.
For improved speed, when analyzing the respective class and its fields, you can create iterable lists of the fields to check, and instead of doing the Reflection fields analysis each time, simply use the lists.
Normally you would use equals() on the detected field values. You could also a) tag the class with another custom annotation, or b) check the fields for any whitelisting/blacklisting annotations, so that you will reliably use your new method for embedded/inherited/delegated annotated classes.
Warning
As with all reflections, you might get into trouble when analyzing hierarchies of classes, that have been modified during the compile process (javac) by annotation preprocessors, or by bytecode weaving. This mostly refers to Java EE aka Jakarta, but can happen anywhere where behind-the-scenes functionality is incorporated in your code, or runtime behavior is changed, like with injections, aspect oriented libraries etc.
#Renis1235 's Lombok solution is the easiest.
However if for some reason in different contexts the equals can mean different things and you don't want to change the default Equals and Hashcode behaviour, I would advice to assign default values to the fields you want to exclude, and then use equals.
(Assuming you can change the objects)
For example:
Class1 a = ...;
Class1 b = ...;
a.createdAt = null;
b.createdAt = null;
a.updatedAt = null;
b.updatedAt = null;
boolean isEqualExcludingTimestamps = a.equals(b);
Use Apache Commons-Lang
CompareToBuilder.reflectionCompare(expected, argument, "someField");
#Entity
#Table(name = "STUDENT")
public class Student {
private long studentId;
private String studentName;
private List<Phone> studentPhoneNumbers;
....
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_PHONE", joinColumns = { #JoinColumn(name = "STUDENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "PHONE_ID") })
public List<Phone> getStudentPhoneNumbers() {
return this.studentPhoneNumbers;
}
public void setStudentPhoneNumbers(List<Phone> studentPhoneNumbers) {
this.studentPhoneNumbers = studentPhoneNumbers;
}
}
1)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
for (Phone p : phoneList) {
...
}
2)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
Iterator itr = phoneList.iterator();
while(itr.hasNext()) {
...
}
I read the answer from here: difference between query.list and query.iterate
Obviously there is difference between list() and iterator() (in Query). What if I use it in the OneToMany list? like the example above, is there difference in term of performance? memory?
It has nothing to do with Hibernate.
When Java Compiler encounters
for (Phone p : phoneList) {
....
}
it automatically generate code equivalent to
for (Iterator<Phone> itr = phoneList.iterator(); itr.hasNext();) {
Phone p = itr.next();
....
}
So it is essentially the same for that two examples you are showing.
I read up this Hibernate chapter which explain the proxy performance in detail.
The entity's mapping FetchType by default is lazy, which, hibernate will create a proxy around the attribute.
Upon calling list.size() (and etc), hibernate will start to load all the children objects.
If we don't want to load all, we can use the new feature called extra lazy. It will issue select statement for specific record only, for example, list.get(3) select fourth row only.
If we annotate the attribute with eager, then hibernate will load all the children objects (use outer join, which will have duplicate issue) upon it load the parent object. In this case, there is no proxy wrapping around the attribute. It will not have performance difference, not matter we use it as a list or iterator.
i'm a beginner with Hibernate, Spring, JPA frameworks. For moment, i'm trying to make a simple architecture with Spring 3.1.1 - JPA with Hibernate 4 Implementation.
For the moment, in order to not be dependant of my database, i created some ids with TableGenerator :
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
After my research, and after reading "dont-let-hibernate-steal-your-identity" article, i don't really understood how to manage my ids.
Should i modify my entities to replace them with an assigned value (how to do it in JPA ?) and should i generate an UUID to affect the ids directly at the creation of the transient object ?
In many tables, i have some easy datas (id, name). I thought i could manage the hashcode and equals methods on name properties which are unique, but not affected at the creation of the object too....(so i think same pb with id which is null ?).
For information, i have an entity which represent a multi join table (3 FK in this join table).
So what do you advice to me ?
Is it not bad of generate UUID for performance ?
EDIT :
Is this entity viable ?
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
#Column(name = "AIR_BUSINESS_ID", unique = true, nullable = false)
private String uuid = IdGenerator.createId();
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Aircraft))
return false;
Aircraft other = (Aircraft)o;
if (uuid == null) return false;
return uuid .equals(other.getUuid ());
}
public int hashCode() {
if (uuid != null) {
return uuid .hashCode();
} else {
return super.hashCode();
}
}
Thank you.
As with every question the full, but seldom usefull, answer is: It depends.
The more helpful variant is:
I use GenerationType.Auto most of the time, and do NOT implement equals and hashcode.
The result is:
you are fine comparing entity objects as long as they live in the same session, since hibernate will ensure that each database row is represented by a single instance per session.
equals and hashcode are stable over time, so you can put your objects in HashSets, change the objects and still get them out again.
If you want to work with objects from different Sessions you have to explicitly compare ids or ids + hashcode or some business key, possibly by implementing a Comparator. The extra effort put in deciding what to use and to implement it will remind you that you are actually doing something going against the grain of Hibernate.
About performance: Depending on the database and the use case UUIDs migh cost performance because they are rather large, or gain performance, because they can get created on the client thus saving a database roundtrip. Most of the times other glitches in the application (especially when using Hibernate) are way bigger then any effect of the ID Generation.
Usually i use:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id
and let the persistence provider to chose the right one.
Hope this help you
I recently asked a question that explores an alternative to the usual pattern: Are there any gotchas with this JPA "cached hashCode" pattern?
I included an example of what I usually do with #Entity classes - generating UUIDs on construction. The probability of a UUID collision is so small that you'd be best worrying about cosmic rays. Some people don't like UUIDs because they feel there is a performance penalty. I've not seen any change in performance versus Integer, but I think the chance of an Integer collision is small enough to make it a concern.
#Id
private UUID id = UUID.randomUUID();
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MY_CLASS) || id == null)
return false;
MY_CLASS other = (MY_CLASS) obj;
return id.equals(other.id);
}
#Override
public int hashCode() {
Preconditions.checkNotNull(id, "id must be set before #Entity.hashCode can be called");
return id.hashCode();
}
Sometimes I want to check if the actual data itself matches, in which case I create a method like this:
public boolean hasSameProperties(Note other) {
Preconditions.checkNotNull(other);
if (this == other)
return true;
return Objects.equal(source, other.source)
&& Objects.equal(title, other.title)
&& Objects.equal(tags, other.tags)
&& Objects.equal(contents, other.contents);
}
#Entity
public class Person {
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
private List<Location> locations;
[...]
}
#Embeddable
public class Location {
[...]
}
Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
And
A lotsa' inserts into the PERSON_LOCATIONS table
Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.
I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.
Any hints are appreciated!
The problem is somehow explained in the page about ElementCollection of the JPA wikibook:
Primary keys in CollectionTable
The JPA 2.0 specification does not
provide a way to define the Id in the
Embeddable. However, to delete or
update a element of the
ElementCollection mapping, some unique
key is normally required. Otherwise,
on every update the JPA provider would
need to delete everything from the
CollectionTable for the Entity, and
then insert the values back. So, the
JPA provider will most likely assume
that the combination of all of the
fields in the Embeddable are unique,
in combination with the foreign key
(JoinColunm(s)). This however could be
inefficient, or just not feasible if
the Embeddable is big, or complex.
And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what element of the collection changed and will delete the old content from the table to insert the new content).
However, if you define an #OrderColumn (to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key (made of the order column and the join column) and will be able to update the collection table without deleting the whole content.
Something like this (if you want to use the default column name):
#Entity
public class Person {
...
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
#OrderColumn
private List<Location> locations;
...
}
References
JPA 2.0 Specification
Section 11.1.12 "ElementCollection Annotation"
Section 11.1.39 "OrderColumn Annotation"
JPA Wikibook
Java Persistence/ElementCollection
In addition to Pascal's answer, you have to also set at least one column as NOT NULL:
#Embeddable
public class Location {
#Column(name = "path", nullable = false)
private String path;
#Column(name = "parent", nullable = false)
private String parent;
public Location() {
}
public Location(String path, String parent) {
this.path = path;
this.parent= parent;
}
public String getPath() {
return path;
}
public String getParent() {
return parent;
}
}
This requirement is documented in AbstractPersistentCollection:
Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
of nullable properties, we currently have to forcefully recreate the entire collection. See the use
of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
the current "WHERE COL = ?" (fails for null for most DBs). Note that
the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
recreation isn't ideal, but not really any other option in ORM 4.
We discovered that entities we were defining as our ElementCollection types did not have an equals or hashcode method defined and had nullable fields. We provided those (via #lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.
Additionally, this error manifested for us because we were within a service method that was marked with the annotation #Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:
HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Here is an example of our entity model that had the error
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
public class Entity2 {
private UUID someUUID;
}
Changing it to this
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
#EqualsAndHashCode
public class Entity2 {
#Column(nullable = false)
private UUID someUUID;
}
Fixed our issue. Good luck.
I had the same issue but wanted to map a list of enums: List<EnumType>.
I got it working like this:
#ElementCollection
#CollectionTable(
name = "enum_table",
joinColumns = #JoinColumn(name = "some_id")
)
#OrderColumn
#Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();
public void setEnumList(List<EnumType> newEnumList) {
this.enumTypeList.clear();
this.enumTypeList.addAll(newEnumList);
}
The issue with me was that the List object was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.
Is there a way in JPA to map a collection of Enums within the Entity class? Or the only solution is to wrap Enum with another domain class and use it to map the collection?
#Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//#???
Collection<InterestsEnum> interests;
}
I am using Hibernate JPA implementation, but of course would prefer implementation agnostic solution.
using Hibernate you can do
#ElementCollection(targetElement = InterestsEnum.class)
#JoinTable(name = "tblInterests", joinColumns = #JoinColumn(name = "personID"))
#Column(name = "interest", nullable = false)
#Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
The link in Andy's answer is a great starting point for mapping collections of "non-Entity" objects in JPA 2, but isn't quite complete when it comes to mapping enums. Here is what I came up with instead.
#Entity
public class Person {
#ElementCollection(targetClass=InterestsEnum.class)
#Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
#CollectionTable(name="person_interest")
#Column(name="interest") // Column name in person_interest
Collection<InterestsEnum> interests;
}
tl;dr A short solution would be the following:
#ElementCollection(targetClass = InterestsEnum.class)
#CollectionTable
#Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
The long answer is that with this annotations JPA will create one table that will hold the list of InterestsEnum pointing to the main class identifier (Person.class in this case).
#ElementCollections specify where JPA can find information about the Enum
#CollectionTable create the table that hold relationship from Person to InterestsEnum
#Enumerated(EnumType.STRING) tell JPA to persist the Enum as String, could be EnumType.ORDINAL
I was able to accomplish this in this simple way:
#ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Eager loading is required in order to avoid lazy loading inizializing error as explained here.
I'm using a slight modification of java.util.RegularEnumSet to have a persistent EnumSet:
#MappedSuperclass
#Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>>
extends AbstractSet<E> {
private long elements;
#Transient
private final Class<E> elementType;
#Transient
private final E[] universe;
public PersistentEnumSet(final Class<E> elementType) {
this.elementType = elementType;
try {
this.universe = (E[]) elementType.getMethod("values").invoke(null);
} catch (final ReflectiveOperationException e) {
throw new IllegalArgumentException("Not an enum type: " + elementType, e);
}
if (this.universe.length > 64) {
throw new IllegalArgumentException("More than 64 enum elements are not allowed");
}
}
// Copy everything else from java.util.RegularEnumSet
// ...
}
This class is now the base for all of my enum sets:
#Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
And that set I can use in my entity:
#Entity
public class MyEntity {
// ...
#Embedded
#AttributeOverride(name="elements", column=#Column(name="interests"))
private InterestsSet interests = new InterestsSet();
}
Advantages:
Working with a type safe and performant enum set in your code (see java.util.EnumSet for a description)
The set is just one numeric column in the database
everything is plain JPA (no provider specific custom types)
easy (and short) declaration of new fields of the same type, compared with the other solutions
Drawbacks:
Code duplication (RegularEnumSet and PersistentEnumSet are nearly the same)
You could wrap the result of EnumSet.noneOf(enumType) in your PersistenEnumSet, declare AccessType.PROPERTY and provide two access methods which use reflection to read and write the elements field
An additional set class is needed for every enum class that should be stored in a persistent set
If your persistence provider supports embeddables without a public constructor, you could add #Embeddable to PersistentEnumSet and drop the
extra class (... interests = new PersistentEnumSet<>(InterestsEnum.class);)
You must use an #AttributeOverride, as given in my example, if you've got more than one PersistentEnumSet in your entity (otherwise both would be stored to the same column "elements")
The access of values() with reflection in the constructor is not optimal (especially when looking at the performance), but the two other options have their drawbacks as well:
An implementation like EnumSet.getUniverse() makes use of a sun.misc class
Providing the values array as parameter has the risk that the given values are not the correct ones
Only enums with up to 64 values are supported (is that really a drawback?)
You could use BigInteger instead
It's not easy to use the elements field in a criteria query or JPQL
You could use binary operators or a bitmask column with the appropriate functions, if your database supports that
Collections in JPA refer to one-to-many or many-to-many relationships and they can only contain other entities. Sorry, but you'd need to wrap those enums in an entity. If you think about it, you'd need some sort of ID field and foreign key to store this information anyway. That is unless you do something crazy like store a comma-separated list in a String (don't do this!).