equals and hashCode with Spring Data JPA and Hibernate - java

After reading several articles, threads and making some research, now I am completely confused regarding to implementing a proper equals and hashCode method in my Spring Boot app.
For example, I have the following class:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String title;
#Column(length = 100)
private String description;
#Column(nullable = false)
private Integer prepTime;
#Column(nullable = false)
private Integer cookTime;
#Column(nullable = false)
private Integer servings;
#Lob
#org.hibernate.annotations.Type(type = "org.hibernate.type.TextType")
#Column(nullable = false)
private String instructions;
#Column(nullable = false)
#Enumerated(value = EnumType.STRING)
private Difficulty difficulty;
#Column(nullable = false)
#Enumerated(value = EnumType.STRING)
private HealthLabel healthLabel;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
private Category category;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}
I have trouble for these issues below and trying to implement equals and hashCode methods properly:
1) As far as I know, when there is a unique field that can be used to differentiate one person from the other e.g. email or username, then it is enough ONLY use these fields in equals and hashCode method. Is that true?
2) If there is not any unique field except from id, then should I add ALL the fields (id and the others) to equals and hashCode method implementation?
3) When using Hibernate and Data JPA, should I follow a different approach than other situation as there are 4 states transient, managed, removed, and detached in JPA lifecycle? I think id field should not be used in this situation as it is not present in transient mode? Right?

When implementing equals() and hashCode() methods:
If there is a unique field that can be used to differentiate one object from another, use only that field in the implementation.
If there is no unique field, use all the fields including the id in the implementation.
When using Hibernate and Data JPA, do not use the ID field in the implementation as it is not present in the transient state, instead use fields that are present in all states such as unique fields or all fields.

The problem with equals and hashCode is that their contract is broken for any mutable entity and with JPA, there aren't really any other.
Ignoring JPA for a moment, by definition the id of an entity defines its identity.
So it should be used for equals and hashCode.
But this requires the id to be unmodifiable in an entity, but JPA requires a no args constructor and a way to set all properties, including the id.
Probably the best way around this is to
use the id.
make sure that equals and hashCode is never ever used before the id is set, and the id is never changed afterwards.
Not changing the id after it is once set is normally not a problem, since the id shouldn't change from one value to another.
The problem is creation of new instances.
Again instances returned by JPA aren't a problem, because JPA will fully initialise them before returning them to you.
Creating fresh instances in your application is the problem.
Here you have the following options:
create the instance and immediately assign a id. UUIDs are perfect for this.
They can be generated easily and efficiently on the application server.
This could be done in a static factory method on the entity class.
The drawback is that UUIDs are a pain to work with for humans, since they are long and basically random.
They are also large and eat more memory in the database than a traditional sequence number.
But the use cases with so many rows that this actually is a problem are rare.
generate the id in the database as most people do, and make sure that your new entity gets saved immediately after creation.
This could be nicely done in a custom method in a repository.
But it does require that you set all required properties in one place, which often can be a problem.
Using some other attribute which is supposed to be immutable, like the account name or an email works only for very few entities in the first place and even for those the fact that it is immutable now doesn't mean it stays that way.
Instead of trying to avoid the pitfalls created by JPA you could alternatively rely on it.
JPA guarantees that for a given class and id only one instance is in a persistence context.
Therefore, as long as you only work within a single session/transaction with an entity and don't try to compare detachd entities, there is no need to implement equals and hashCode at all.

as you are already using lombok, you can use #Data annotation as well:
#Data
All together now: A shortcut for #ToString, #EqualsAndHashCode, #Getter on all fields, #Setter on all non-final fields, and #RequiredArgsConstructor!

Related

How to use CriteriaQuery to query collection that gets persisted as a string using #Convert annotation?

In a SpringBoot app using Hibernate, I have an entity similar to the following:
#Entity
#Table(...)
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#Convert(converter = IntegerListConverter.class) //this maps a set of integers to a string, and vice versa when writing to the DB
#Column(name = "types", nullable = false)
private Set<Integer> types;
//other members, getters and setters omitted for brevity
}
I'm doing a search based on several parameters and therefore the query has to be built programmatically to filter based on the variable input. I want to write a query equivalent to the following SQL:
SELECT * FROM my_entity
WHERE id IN (:ids)
AND types LIKE '%:type%';
or equivalent to:
SELECT * FROM my_entity
WHERE id IN (:ids)
AND CONCAT(',', types) LIKE '%,:type%';
When using the first approach the persistence layer complains about types not being a Set.
The second approach works if I write it using a #Query annotation but I can't really use that since, like I mentioned before, the query is built conditionally depending on the user filter.
One additional note, I'm performing the query on a large dataset (i.e. ~100K matching entries) on MySQL, and running separate queries will involve transferring a lot of data which makes it slow (i.e. ~20 seconds).
Note: Please don't ramble about how bad it is to store one-to-many relationships on the same table. There are trade-offs that were considered and the system is designed like that for a reason.
Help appreciated.
Thanks in advance.
Annotate it with #Basic then this should work. IMO you should consider using the JSON type in MySQL instead to avoid the custom "parsing" that you have to do to be able to find individual values. I think you can even index this properly when you switch to JSON.

Hibernate/JPA - Insert a field only if exists in table

I am looking for an answer if it is possible or not in hibernate. What I am trying to achieve is that if a field exists in a particular table then only it should insert it. Else just ignore the field in the #Entity class.
I want this as a new field is going to introduce in one of the table we are using and there are many dependent components which right now insert the data into that table. I don't want a big bang release. Want something like it doesn't impact the older version as well as when the upgrade happens and a new column introduced then also it should work.
For example -
#Entity
#Table(name = "EMPLOYEE_RECORDS")
public class Employee
{
#Id
#Column(name = "employee_id")
private Integer employeeId;
#Column(name = "employee_name")
private String employeeName;
#Column(name="address")
private String address;
}
What if I only want to insert address field into DB only when column(address) exists in the table EMPLOYEE_RECORDS. Please forgive me if this is something obvious, as I am not very proficient in Hibernate.
Also let me explain what have I thought of (But not sure if it will also work) -
1. Create 2 different #Entity classes. Try to insert and if the insertion failed then at runtime switch the #Entity and use without address.
2. Check if field exist in the table by simple query if it fails use #Entity without address else use without address.
I'm very confused about the scenario - It seems like there were deeper issues regarding decoupling of components in your system.
Nevertheless, you can add the column in the database, but you don't need to declare the field in the hibernate entity. On the other hand there is no way you can have an optional field in an hibernate entity. Either a field is mapped or it is not mapped.

Hibernate: Lazy initialization vs broken hashcode/equals conundrum

I'm quite new to JPA and Hibernate (I'm studying hard though!) and I am struggling with a problem that I can't seem to find a trivial solution for, so here it is.
I have an entity that looks kinda like the following:
#Entity
#Table(name = "mytable1")
public class EntityOne {
// surrogate key, database generated
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// business key
#Column(name = "identifier", nullable = false, unique = true)
private String identifier;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "twoId", nullable = false)
private EntityTwo two;
#OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER,
cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<EntityThree> resources = new HashSet<>();
// getters/setters omitted
#Override
public int hashCode() {
// the business key should always be defined (through constructor/query)
// if this is null the class violates the general hashcode contract
// that the integer value returned must always be the same
Assert.notNull(identifier);
// a dirty alternative would be:
// if(identifier==null) return 0;
return identifier.hashCode();
}
#Override
public boolean equals(Object o) {
return o instanceof ResourceGroup
&& ((ResourceGroup) o).identifier.equals(identifier);
}
}
My project is set up with Spring JPA, so I have my CrudRepository<EntityOne,Long> injected in a Service class that has a few #Transactional methods and I scan my domain/service packages for JPA and transactions respectively.
One of the service methods calls the repository's findAll() method and returns a list of EntityOnes. Everything works fine unless I try to access the getter for two, which obviously throws:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
I thought it might be useful to have this object initialized, so I switched the fetching type from lazy to eager. However, if I do that I get the following:
java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.util.Assert.notNull(Assert.java:123)
at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74)
at java.util.HashMap.hash(HashMap.java:351)
at java.util.HashMap.put(HashMap.java:471)
at java.util.HashSet.add(HashSet.java:217)
at java.util.AbstractCollection.addAll(AbstractCollection.java:334)
at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209)
at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149)
//...
I briefly looked at Hibernate's source code and it looks like it's trying to put my EntityOne objects in a set before their business key is initialized. Is my interpretation correct? Is there a way around this? Am I doing something incredibly dumb?
I appreciate your help
EDIT: I just want to clarify that what I'm trying to understand here is what the best practices are specifically with respect to JPA and Hibernate. If this was a plain POJO I could make the identifier field final (I would actually make the whole class immutable) and be safe. I can't do this because I'm using JPA. So the questions: do you violate the hashCode contract and in which way? How does Hibernate deal with this violation? What's the JPA recommended way of doing this in general? Should I get rid of hash based collections altogether and use lists instead?
Giovanni
No, you're not doing anything dumb. Implementing equals and hashCode on a JPA entity is a matter of much heated debate, and all of the approaches I know about have significant drawbacks. There's no obvious, trivial solution that you're just missing.
You have, however, hit on a case which is not discussed very much for some reason. The hibernate wiki recommends using a business key as you are doing, and "Java Persistence with Hibernate" (Bauer / King, 2007, widely regarded as the standard Hibernate reference work) on page 398 recommends the same thing. But in some situations, as you observe, Hibernate can add an entity into a Set before its fields are initialized, so the business-key-based hashCode doesn't work, just as you point out. See Hibernate issue HHH-3799 for discussion of this case. There is an expected-to-fail test case in the Hibernate source code demonstrating the issue, added in 2010, so at least one Hibernate developer considers it to be a bug and wants to fix it, but there hasn't been any activity since 2010. Please consider voting for that issue.
One solution you might consider is to expand the scope of your session so that all your access to entities happens within the same session. Then you can make your Set<EntityThree> be lazy-fetched instead of eager-fetched, and you'll avoid the eager-fetching problem in HHH-3799. Most applications I've worked on make only sparing use of objects in the detached state. It sounds like you're loading your entity and then using it for a while after the session ends; that's a pattern I'd recommend against. If you're writing a web application, see the "open session in view" pattern and Spring's OpenSessionInViewFilter for ideas on how to do this.
Incidentally, I like how you throw an exception when the business key is not initialized; that way you can catch coding errors quickly. Our application has a nasty bug due to HHH-3799 which we might have caught in development if we had used your not-null assertion.
Your interpretation is correct. As a first first step code your hashCode() and equals() with your id field - the one you are telling Hibernate that is your id.
As a second step implement a correct hashCode() and equals() to save you from future trouble. There are plenty of resources if you google it. Here is one on this site
I believe I actually found a way to make this work a little better, i.e., forcing Hibernate (or whatever JPA provider) to have the key available before sticking objects in the collection. In this scenario, the object will be properly initialized and we can be sure that the business key won't be null.
For example, here's how the class EntityTwo would have to look:
#Entity
#Table(name = "mytable2")
public class EntityTwo {
// other code omitted ...
#OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER,
cascade = {CascadeType.ALL}, orphanRemoval = true)
#MapKey(name = "identifier")
private Map<String, EntityOne> entityOnes = new HashMap<>();
}
I haven't tested this specific code but I have other working examples and it should work fine according to the JPA docs. In this case, the JPA provider is cornered: it must know the value of identifier before it can put the object in the collection. Besides, the object's hashCode and equals are not even called because the mapping is explicitly handled by the JPA provider.
This is a case in which explicitly forcing the tool to understand the way things are modeled and related to each other leads to great benefit.

Can I create a OneToOne relationship with just the id?

In JPA is it possible to create an #OnToOne relationship with just an idea and not embedding the class? For example:
#Entity public class Relationship {
#Id
#OneToOne
private Long parentId; // instead of "private User parent;"
#Id
#OneToOne
private Long childId; // instead of "private User child;"
int type;
...
I don't mind having a getter method with a lazy fetch but I don't need one and I'd prefer not to have to pull in complete parent objects to work with child objects. Also, I want to deserialize this to JSON using just the id and not the embedded object. I can do this by adding a getter that delegates to #getParent().getId() and putting a JsonIgnore on the #getParent() method (and the same for child) but I'd prefer to get the entity to look the way I want it to from the get go without adding this stuff. Possible and if so how?
Thanks!
No. How can an Object be related to a Number? This is an O-O language after all.
Labelling something as #OneToOne is semantically equivalent to a FK in the datastore. If you just omit #OneToOne and use a number then you have a numeric column in the datastore without a FK. So JPA allows both, but one gives the benefits of a FK, whereas with the other you just pass "numbers" around with no context of what they relate to

Java JPA (EclipseLink) How to receive the next GeneratedValue before persisting actual entity?

I'm using the EclipseLink implementation of JPA and I've got a problem.
Here is my entity class:
#Entity
#Table(name = "attendances")
public class Attendance implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Short id;
....
In my MySQL table (that is the underlying data store) I have auto_increment flag also set.
How can I get the auto-generated value (id field) before persisting the entity? So if there are 3 entities in the table (id IN (1, 2, 3)), I want to get 4 as the next auto-generated value.
The only workaround I have is:
Get the id of the entity with the largest id field.
Is that all I can do?
You can try this, if using JPA 2.0,
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(attendance);
DISCLAIMER: Never tried it myself.
Otherwise, you can change your id generation strategy to something, that can get you the id before persisting the entity. For example, try to use table generator to generate your ids. Here, I found an example, Use Table Generator To Generate ID. Then you would be able to read the value from that table, directly. You may not be able to handle this automatically using #Id, precisely you will end up calculating the next id yourself. Hence, you can employ the same idea but do all the work yourself and set the id while creating the operation, and don't let JPA to generate one.
Or you might like to use the sequence, if supported. In case of sequence also you need to get the next value yourself and set it manually. Be prepared to loose skipped ids in case of no insert, or handle that somehow.
Or you are fine with what you are doing. Just take care of atomicity. Or you may not have the case of multiple transactions running simultaneously.

Categories