LazyInitializationException, for an int - java

A simple class with an integer field:
#Entity
#Cacheable
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Table(name = "myObject")
public class MyObject
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(columnDefinition = "int default 0")
#Index(name = "refCount")
private int refCount;
public int getRefCount(){ return refCount; }
}
Objects are fetched from the database using a simple Utility method:
Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();
criteria.setFetchSize(1);
T object = (T) criteria.uniqueResult();
// I tried to add this line, but it made no difference
Hibernate.initialize(object);
tx.commit();
return object;
The problem is the following:
Shortly after fetching this object, I am calling the getRefCount method. At that point I encounter the following exception:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at mypackage.MyObject_$$_javassist_1.getRefCount(MyObject_$$_javassist_1.java)
My hibernate configuration file (i.e. hibernate.cfg.xml) contains the following property:
<property name="hibernate.current_session_context_class">thread</property>
What I don't understand:
If this would happen to a collection, then I would just add the fetch = FetchType.LAZY annotation. But this simple int field is not a join. Why would an int ever be wrapped inside a Proxy in the first place ?
I tried to add the Hibernate.initialize(object); line, but it made no difference at all.
I also experimented with the hibernate.current_session_context_class="managed" setting. After which I had to start and stop all sessions manually. I opened it at every fetch and closed it in a finally block. But that also made no difference.
This is one of my first Hibernate projects. I'm starting to wonder if I should open a transaction before calling getters on hibernate objects.
I'm not using Spring, just Hibernate.
EDIT: actually there is a 2nd object
Actually there is a parent object (which I initially thought was not important). This Parent object contains a link to the MyObject
#Entity
#Cacheable
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Table(name = "parentObject")
public class ParentObject
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// link
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#ElementCollection(targetClass = MyObject.class)
#JoinColumn(name = "myObjectId")
private MyObject myObject;
public MyObject getMyObject(){ return myObject; }
}
What happens is:
a Parent object gets fetched
parent.getMyObject() is called to get a MyObject instance
This MyObject instance is in fact a proxy without any fields on it.
As soon as I call a method on this MyObject instance, I get the LazyInitializationException
When I fetch my objects I make sure a session exists and a transaction is created. But after the fetching I immediately close the transaction.
I am not creating a transaction when I'm calling the getMyObject() or when calling the getters. I guess that's the problem. I'll test if that makes a difference.
EDIT 2:
It turns out that I indeed need to call the getters inside a transaction. But that in itself is not enough.
A second problem is that the Parent object was fetched in a transaction that was already committed. As a result, the proxy object is no longer bound to an event. I guess that's what they call a "detached object". (lol, I'm just learning as we go here.)
I had to "reattach" this object by calling the Session#update(proxy) method. Now finally I can call the getter without exceptions.
// uses a transaction internally
Parent parent = MyHibernateUtil.fetch(Parent.class, ...);
MyObject object = parent.getMyObject();
...
// create a new transaction
Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();
// reattach the object
SessionFactory.getCurrentSession().update(myObject);
int count = myObject.getRefCount();
tx.commit();
But what I learned from this issue is that I probably use transactions the wrong way. I guess I should make longer transactions that contain both the fetches and the calls to the getters. Right ?

I suppose whole object(MyObject in your case) is proxied. Could you call getId instead of getRefCount() ?

Try calling getId function before closing the transaction. Don't know what will happen but just a suggestion.

Related

JPA - OneToMany struggle

I have an entity Property which has several PropertyAttributes - managed as list, i.e.
#OneToMany(mappedBy = "property", cascade = { CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinFetch(value = JoinFetchType.INNER)
private List<PropertyAttribute> propertyAttribute;
in the PropertyAttribute I have as well a reference to the Property, i.e.
#Id
#JsonIgnore
#JoinColumn(name = "property_id")
#ManyToOne
private Property property;
when I save a new Property,e.g. id=10400 than the additional PropertyAttribute is saved as well with the same id - as expected. My save() method looks like:
public void save(){
//begin transaction
getEntityManager.persist(newProperty);
// end transaction...
//begin transaction...
getEntityManager.merge(newPropertyAttriubtes);
// end transaction
}
But when I perform the second round of save() (now with id=10401 then I get strange results, namely:
getEntityManager().find(Property.class,10400)
has now PropertyAttribute with Property - id = 10401, i.e. from the last saved record (=wrong content!)
Any ideas why it fails with the second save? Might it be a DB-issue? Or EclipseLink?
I finally figured out how to do it properly :-/ Just in case others struggle the same...
public void save(){
//begin transaction
getEntityManager.persist(newProperty);
// end transaction...
newPropertyAttriubtes.forEach(attr -> {
//begin transaction...
getEntityManager.merge(attr);
// end transaction
newProperty.attach(attr)
}
//begin transaction
// to have the PropertyAttributes attached in the EntityManager => merge
getEntityManager.merge(newProperty);
// end transaction...
}
}
Thx for Chris to point me to the correct direction to not change the Entity after JPA manage it!

Trying to save different java objects which represents the same entity. Hibernate

(This is a simplification of the real problem)
Let's start with the following little class:
#Entity
class Test {
Test(int id, String name) {
this.id = id;
this.name = name;
}
#Id
private int id;
#Column
private String name;
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Test) {
return id == ((Test) obj).id;
}
return false;
}
}
If we execute the following, no exception occurs:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();
Test obj1 = new Test(1, "uno");
tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1); // <-- No exception
tA.commit();
I guess the second call is ignored, or maybe the object is saved to the DB again. The thing is there is no problem in saving the same entity twice. Now let's try the following:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();
Test obj1 = new Test(1, "uno");
Test obj1_ = new Test(1, "uno");
tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1_); // <-- javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session
tA.commit();
What? How could it possibly be relevant that the object is in a different memory location? Somehow it is and the code throws an exception.
How can I make the second example work just like the first?
I am just rewriting what #jb-nizet wrote in the comments, which feels like the answer to me:
Hibernate doesn't use ==. It simply does what you're telling it to do.
persist's contract is: associate this object with the session. If it's
already associated to the session, it's a noop. If it isn't, it is
associated to the session to be inserted in the database later. If
what yo want to do is make sure the state of this object is copied to
a persistent entity, and give me back that persistent entity, then
you're looking for merge().
So the solution was to just use
AtheManager.merge(obj1);
instead of
AtheManager.persist(obj1);
In first case, you save the same object twice, which is allowed.
But in second case, you save two different object to database, but both has the same primary key. It is database constraint violation.
In the first example you pass a reference to an object to save it and in the second call you pass exactly the same reference; they both point to the same object in memory.
However, in the second example you allocated two objects with two new calls which creates the objects at two different memory addresses; they are two different objects. The first reference points to some other memory address then the second object's reference. If you tried this in the second example it would return false: obj1 == obj1_

ManyToMany NOT NULL check constraint when using Named query

I have an Entity (Layer) that maps a list of other Entities (Member). This List may have no entries / be null. Yet, when I query for the Entity I get a NOT NULL check constraint error from the database.
It seems to be connected to the NamedQueries as I can read the Entity from DB if I query by id.
#Entity
#NamedQueries({
#NamedQuery(name="getChildLayers",-
query = "SELECT la
FROM Layer la
WHERE la.parent = :parent AND la.deletedDate IS NULL")})
public class Layer extends CommonModel {
/*... other field */
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Layer.class, optional = true)
private Layer parent;
#ManyToMany(fetch = FetchType.LAZY, targetEntity = MyUser.class)
private List<MyUser> members;
public List<MyUser> getMembers() {
return members;
}
public void setMembers(List<MyUser> members) {
this.members = members;
}
/*... other getters and setters */
}
I get this error: integrity constraint violation: NOT NULL check constraint; SYS_CT_10298 table: LAYER_MYUSER column: MEMBERS_ID
I am able to create the entry, though.
When I run my tests then all tests fail that read the Entity (but creation works). If I add the following line in the creation method:
layer.setMembers(new ArrayList<MyUser>());
then the methods that test the alternation of the members work (meaning, I can create a Layer and alter its members by adding and removing elements from the list).
It seems to me that reading the Entity from Database fails whenever there are no Member to the Layer.
I did try adding #JoinColumn(nullable=true) to the field, but it changed nothing.
I import javax.persistence classes.
Example as to how I access the variable (in LayerService)
// this method works as expected
public Layer getById(Long id) {
Session s = sessionFactory.getCurrentSession();
return (Layer)s.get(Layer.class, id);
}
// this does not.
public List<Layer> getChildren(Layer layer) {
Query childrenQuery = sessionFactory.getCurrentSession().getNamedQuery("getChildLayers");
childrenQuery.setParameter("parent", layer);
return (List<Layer>) childrenQuery.list();
}
Code changed after Jason Cs answer:
Layer
...
private final List<OCWUser> members = new ArrayList<>();
...
public void setMembers(List<OCWUser> members) {
this.members.clear();
this.members.addAll(members);
}
Problem still exists.
It can be so simple. I forgot to add #JoinTable
#JoinTable(name = "LAYER_USER", joinColumns = #JoinColumn(nullable = true))
One important thing to be aware of is you shouldn't replace this.members with another list in setMembers unless you know you are doing it before you call persist(). Instead you need to clear this.members then add all the specified elements to it. The reason is that Hibernate can and will use its own proxied / instrumented collections classes when [de]serializing an entity, and you blow that away when overwriting the collection class. You should declare members as final and always initialize it to a non-null empty List.
See for example (3.6 but still relevant): http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/collections.html#collections-persistent, In particular:
Notice how in Example 7.2, “Collection mapping using #OneToMany and
#JoinColumn” the instance variable parts was initialized with an
instance of HashSet. This is the best way to initialize collection
valued properties of newly instantiated (non-persistent) instances.
When you make the instance persistent, by calling persist(), Hibernate
will actually replace the HashSet with an instance of Hibernate's own
implementation of Set.
As long as you are messing with collection fields in this way, any number of strange things can happen.
Also, in general, you want to be careful about stating your invariants and such when accessing collections in this way, as it's easily possible to, e.g., create two Layers that reference the same collection internally, so that actions on one affect the other, or external actions on the passed-in collection affect the layer, e.g. the following code probably doesn't behave like you want it to:
List<MyUser> u = new ArrayList<MyUser>();
Layer a = new Layer();
Layer b = new Layer();
u.add(...);
a.setMembers(u);
b.setMembers(u);
u.clear();
Further, when you persist() one of the layers there, and Hibernate overwrites the field with its own collection class, the behavior then changes as the objects are no longer referencing the same collection:
// not only did u.clear() [possibly undesirably] affect a and b above, but:
session.persist(a);
u.add(...); // ... now it only affects b.

JPA: check whether an entity object has been persisted or not

Is there a general method that can
if(entity is persisted before){
entity = entity.merge();
}else{
entity.persist();
}
So the method contain above logic is safe everywhere?
If you need to know is object already in persistence context you should use contains method of EntityManager.
Only EntityManager can tell you is entity persisted or not, entity does not have such information.
Here you can check javadoc for contains method.
if (!em.contains(entity)) {
em.persist(entity);
} else {
em.merge(entity);
}
To check if entity object has been persisted or not by the current PersistenceContext you can use the EntityManager method contains(Object entity)
Maybe it's too late, but here are my findings!
If you have an entity with a generate value, you can use it to check if the entity is already in DB, assuming you are not modifying this value manually.
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
// getter...
}
public class Main {
public static void main() {
MyEntity myEntity1 = new MyEntity();
MyEntity myEntity2 = em.find(MyEntity.class, 4);
em.detach(myEntity2); // or em.close()
// other stuff and modifications
// begin transaction
persistEntity(myEntity1); // will use persist()
persistEntity(myEntity2); // will use merge()
// commit transaction
}
// This will manage correctly entities in different state
public void persistEntity(MyEtity entity) {
if (myEntity.getId() != null) em.merge(entity);
else em.persist(entity);
}
}
Using em.contains(entity) will fail in this scenario:
public static void main(){
MyEntity myEntity = em.find(MyEntity.class, 5);
em.detach(myEntity); // or em.close()
// We are going to execute persist() because the entity is detached
if (!em.contains(myEntity))
// This call will produce an exception org.hibernate.PersistentObjectException
em.persist(myEntity);
else
em.merge(myEntity);
}
There are a performance reasons to try to achieve what OP is trying to do. You surely can use em.merge() instead of em.persist(), but not without a cost.
A call to em.merge() is trying to retrieve an existing entity from DB with a SELECT query and update it. So if the entity was never persisted, this will waste some CPU cycles. On the other side em.persist() will only produce one INSERT query.

openjpa, update, error 'PK has non-default value'

I wonder if anyone has come across this error and can explain what's happening:
<openjpa-2.1.1-SNAPSHOT-r422266:1087028 nonfatal user error>
org.apache.openjpa.persistence.InvalidStateException:
Primary key field com.qbe.config.bean.QBEPropertyHistory.id of com.qbe.config.bean.QBEPropertyHistory#1c710ab has non-default value.
The instance life cycle is in PNewProvisionalState state and hence an
existing non-default value for the identity field is not permitted.
You either need to remove the #GeneratedValue annotation or modify the
code to remove the initializer processing.
I have two objects, Property and PropertyHistory. Property has OneToMany List of PropertyHistory:
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.MERGE, orphanRemoval=false)
#JoinColumn(name="PROPERTY_NAME")
#OrderBy("updatedTime DESC")
private List<QBEPropertyHistory> history = new ArrayList<QBEPropertyHistory>();
And Property object is loaded and saved like this:
public T find(Object id) {
T t = null;
synchronized(this) {
EntityManager em = getEm();
t = em.find(type, id);
//em.close(); //If this is uncommented, fetch=LAZY doesn't work. And fetch=EAGER is too slow.
}
return t;
}
public T update(T t) {
synchronized(this) {
EntityManager em = getEm();
em.getTransaction().begin();
t = em.merge(t);
em.getTransaction().commit();
em.close();
return t;
}
}
In the service layer I load a property using find(id) method, instantiate a new PropertyHistory, add it into property prop.getHistory().add(propHist) then call update(prop) and get the above error.
The error disappears if I close EntityManager in find() but that breaks lazy loading and prop.getHistory() always returns null. If I set fetch=EAGER it becomes unacceptably slow as there are 10s of 1000s of records and I need to select thousands of property objects at a time and history is not needed 99.99% of the time.
I can't remove the #GeneratedValue as the error text suggests because it is generated (DB2, autoincrement). Now I wonder how would i "modify the code to remove the initializer processing" ?
Thanks!
The problem is that you are trying to share an Entity across persistence contexts(EntityManager). You could change your methods to take an EntityManager instance and use the same EM for the find and update operations.

Categories