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 EntityOne
s. 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
Set
fields' initial value withnew LinkedHashMap()' instead of
new HashMap()` and it started working. Is it not strange? – Jochebed