Hibernate: Lazy initialization vs broken hashcode/equals conundrum - java

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.

Related

Why can't we make a relationship attribute in spring data JPA final as well?

An Owner entity has a #ManyToOne - #OneToMany relationship with the teacher entity. When I annotate each like this
#Entity
public class Product {
...
#ManyToOne(cascade = MERGE)
private final Owner owner;
On the other side, in the Owner Class,
#Entity
public class Owner {
...
#OneToMany(mappedBy = "owner", cascade = MERGE)
private final List<Product> products;
What happens now is that "owner" in the line mappedBy = "owner" turns red. Hovering over it, I get the error that the owner attribute cannot be found.
The error: Cannot find inverse attribute
The solution was simply to remove the final keyword in the attribute owner in the Product class.
It becomes private Owner owner and the error disappears. I don't understand why adding the keyword final causes this problem.
Why does this happen?
Is there a workaround? Can I still make the owner attribute final?
The main idea from the getgo was to make the product class immutable. While this is a challenge, I've managed to find a workaround for most things, but I didn't know how to fix this problem.
JPA does not support immutability. A JPA entity does require a default constructor and getters and setters for properties.
And the fields must not be final.
Technically it would be possible to implement an ORM that ignores final attributes but why should it?
The keyword final says: This gets assigned a value at construction time and never changes after that. This is just not true for JPA entities which get constructed via no-args-constructor and then populated in second step.
If you are looking for an ORM that has better support for immutable classes (constructor with arguments, "wither" methods) you might want to check out Spring Data JDBC.
Full disclosure: I'm Spring Data developer working on both Spring Data JPA and Spring Data JDBC.
I think you have understood immutability concept wrong. Immutability is a concept being forced by the Java language. For example String class is immutable because of the security, caching etc. But in your case Product is an entity class and if you save it in a persistent layer, it is already unique on it's own. So even if you make the Product class immutable, how are you going to keep that consistency during two application loads?. If you are trying to make a Product having owned by only one owner, then do a db check rather than trying to make it immutable in memory.

Spring-Data JPA: modelling a graph getting 'column violates not-null' when removing edges

I have a set of entities that that build some sort of graph. This is modelled by a class Entity with two fields modelling the relationships between entities.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "fromId")
private Set<EntityRelation> outEdges;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "toId")
private Set<EntityRelation> inEdges;
All outEdges are supposed to belong to the entity when it is saved, the inEdges are "inferred" from these. Problem is then after removing an out-edge I always get an error ERROR: null value in column "fromid" violates not-null constraint where fromid is a field of EntityRelation.
For performance reasons, I don't want to have direct relations form Entity to Entity.
To fix this, I used a new Spring-Data JPA method (in the corresponding Repository class) to explicitly remove everything an entity points to (like
#Modifying
#Query(value = "delete from entityrelation where fromid = ?1", nativeQuery = true)
int deleteEntityRelations(String entityId);
But this somehow misses the whole point, since I want JPA to take responsibility of that.
what is wrong here? I really got stuck, since all posts I could find suggest that it should simply work with orphan-delete.
In the SQL-Trace you can see that an org.hibernate.SQL - update EntityRelation set fromId=null where fromId=? and id=? is issued automatically (which then triggers the error).
thanks and regards
fricke
This is a known issue of hibernate. In certain scenarios (and you found one of them) it violates constraints on foreign key relations. There are various options (but I'm afraid you might not like any of them)
remove the constraint. I know, I know ..
make the constraint deferred. Not sure if this feature is available in other databases but Oracle.
limit the expectations to JPA. Seriously, it looks like you expecting more from it then it will give you. I highly recommend reading this article before proceeding with any project using any kind of ORM.
Please note that even though setting hbm2ddl.auto to UPDATE, it doesn't remove the not-null type constrains when the nullable in entity is set to FALSE. I would suggest that check the history of the class for any changes to entity relationship or column mapping for nullable constraint.
If anybody is looking for the solution, updatable=false in the JoinCoulum annotation fixed this problem for me
#JoinColumn(name = "fromId", updatable = false)

How does Hibernate work with normalized databases?

Preliminary Info
I'm currently trying to integrate Hibernate with my team at work. We primarily do Java web development, creating webapps that provide data to clients. Our old approach involves calling stored procedures with JDBC (on top of Oracle boxes) and storing their results in beans. However, I've heard a lot about the benefits of integrating Hibernate into a development environment like ours so I'm attempting to move away from our old habits. Note: I'm using the Hibernate JPA annotation approach due to simplicity for team adoption's sake.
Specific Problem
The specific issue I'm having currently is using Hibernate with normalized tables. We have a lot of schemas structured like so:
StateCodes (integer state_code, varchar state_name)
Businesses (integer business_id, varchar business_name, integer state_code)
I want to be able to have a single #Entity that has all of the "Businesses" fields, except instead of "state_code" it has "state_name". To my understanding, Hibernate treats #Entity classes as tables. The #OneToMany, #OneToOne, #ManyToOne annotations create relationships between entities, but this is a very simplistic, dictionary-like lookup and I feel like it doesn't apply here (or might be overkill).
One approach I've seen is
#Formula("(select state_name from StateCodes where Businesses.state_code = state_code)")
private String stateCode;
But, given Hibernate's perk of "avoiding writing raw SQL", this seems like bad practice. Not to mention, I'm extremely confused about how Hibernate will then treat this field. Does it get saved on a save operation? It's just defined as a query, not a column, after all.
So what is the best way to accomplish this?
I do not see any reason not use the standard JPA mappings in this case. Short of creating a database view and mapping an entity to that (or using the non-JPA compliant #Formula) then you will have to map as below.
Unless you are providing a means for the State to be changed then you do not need to expose the State entity to the outside world: JPA providers do not need getters/setters to be present.. Neither do you need to Map a State to Businesses:
#Entity
#Table(name = "Businesses")
public class Business{
//define id and other fields
#ManyToOne
#JoinColumn(name = "state_code")
private State state;
public String getStateName(){
return state.getName();
}
}
#Entity
#Table(name="StateCodes")
public class State{
//define id and other fields.
#Column(name = "state_name")
private String stateName;
public String getStateName(){
return stateName;
}
}

Make JPA entity field immutable in REST environment

There are two simple JPA entities for Jersey REST web-service. Basically business item has a creator which refers to a user. When serializing it is not necessarily to show the creator of the business item to the client, so JsonIgnore annotation is there. Assume that I allow every user to update BusinessItem name via HTTP PUT request, but not to update creator value.
#Entity
public class BusinessItem {
#GeneratedValue(strategy = AUTO)
#Id
Long id;
String name;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "creator_id", nullable = false, updatable = false)
User creator;
}
#Entity
public class User {
#GeneratedValue(strategy = AUTO)
#Id
Long id;
String name;
#OneToMany(fetch = LAZY, mappedBy = "creator")
List<PhotoSpot> createdPhotoSpots;
}
The problem is that once PUT request with JSON body is received from a client and deserialized then creator field would be null. Then in order to update entity in a database I would need to retrieve a creator value from the DB by the business item id, substitute in the deserialized entity and only then apply update to the database. This all sounds like not elegant and boiler plate.
What would be an elegant solution for this type of problem?
The direct association between a service and an entity can be a pragmatic design in very simple applications. However in many case this is not a suitable option. Managing properly entity relationships can be a good reason to have a more structured design.
In the usual design of standard MVC applications, it is a good practice not to use the entity directly as a JavaBean accessed via the View / page (the entity is an Enterprise JavaBean in an EJB container, not to be confused) . The implementation of a TransferObject pattern is an usual solution (beware, the sequence documented in that link is for a data retrieval operation, not an update). The controller commonly assumes the construction of the TransferObject. It then becomes a recommended candidate for a DAO operation.
A REST service is not very different from an MVC pattern: in fact this is an Entity-Control-Boundary pattern (in the case of Jersey <= 2 applications, the distinction can be quite subtle, even if this should evolve in Java EE 8). The service itself is a Boundary replacing the View.
In other words, according to Core J2EE Patterns, the elegant solution would be to feed a TransferObject with your PUT parameters: this object is then transmitted to the DAO. Then the DAO layer just has to retrieve a properly managed entity from the database to get the entities relationships and report the transferred values to update into the managed entity while preserving the relationships (this can be done with a DAO).
Java EE design can be quite challenging. The platform provides out of the box components very similar to pattern shapes. But in several cases these are not replacements. For example an EntityManager looks like a DAO. But using an explicit DAO usually provides a clear and understandable code while using directly the EntityManager can be quite verbose as transactions are not encapsulated.
Conclusion
In your situation, what seems important to me is the use of a DAO to encapsulate your database operations (this is only if you really need to map the relationships, otherwise a #Transient annotation would be a solution). You will anyway have to retrieve a managed entity before performing the merge(...) operation. The use of the TransferObject is not mandatory, this is just an advice to have a proper design. In many case this can lead to verbose code. You have several solutions to limit this: a simple Map can limit the code, a utility such as BeanUtils can also be precious.
But avoiding a basic transfer operation in setting up a DAO can be challenging. In your case this would induce the use of a MultivaluedMap in the DAO layer, which does not seem a very good idea on a dependency point of view as it links the Model/Entity and the View/Boundary components. This is probably why Core J2EE patterns introduces the TransferObject.

JPA SortedSet is not being resorted after persist

I have an entity with the following field:
#ManyToMany(cascade = { CascadeType.ALL }, targetEntity = Comment.class)
#JoinTable(name = "program_to_comment")
#OrderBy("position")
private Set<Comment> comments = new HashSet<Comment>();
but I have the problem that whenever I persist it using:
Program p = entityManager.persist(entity);
the field comes with the objects sorted as it was sorted in the entity object.
Suppose the entity object is configured as following: Program(comments:[Comment(position:15), Comment(position:10)], ...), persisting the entity (entityManager.persist), it will store both comments and the program entity itself to the database. But the resulted entity from the persist method invocation is an object as follows: Program(comments:[Comment(position:15), Comment(position:10)], ...), in the same order gave to the persist method.
From my point of view at this point the resulted entity should present the values following the specified #OrderBy rule, or am I missing something?
Additional information:
JPA2
Hibernate 4.2.0.Final
OrderBy simply add an order by clause to the query used to load the comments of a program. Nothing more. The rest is under your responsibility. So if you want the comments sorted by position when adding comments and persisting them, you have to take care of this by yourself.
I have personally never found this annotation to be really useful. I have also found it not to work in every case, particularly when using a query to fetch programs with their comments, with an order by clause already present in the query. I generally prefer not to use theis annotation, and provide a getSortedComments() method which returns a sorted set or list of comments, using a comparator.

Categories