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!
Related
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.
I've the following Entity:
class User {
..
...
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "id.uKey")
#MapKey(name="id.achieveId")
private Map<Integer, Achievement> achievements;
..
..
}
at some point I call:
Hibernate.Initialize();
and this map is filled with entries from DB.
when the app continues I save new entries into the DB table.
and then I try to access the map but it doesn't contain the new entries.
Is there a way to make it aware that it needs to re-select the DB table?
Thanks
EDIT:
I add new entries like this:
public void save() {
..
tx = dbs.beginTransaction();
Achievement ua = new Achievement(key, id);
dbs.save(ua);
tx.commit();
}
After initializing your object, it resides in the hibernate session and this session is not designed to be updated by changes from the underlying database. Just reload the object from the database, but remove it from the session before doing so.
But maybe, what you really want is to modify the map contained in your object. That would be the OOP way. After that you could persist the entire object with Hibernate.
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.
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.
I have run into a nasty bug with jpa and hibernate. I have a billing class with the following annotation:
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="ch1_id", referencedColumnName="id")
private List<BillingItem>billingItems = new ArrayList<BillingItem>();
Now I need to filter deleted items from the collection but cannot use anything but jpa. No hibernate specific annotations allowed. So I wrote a post load function:
#PostLoad
public void postLoad() {
ArrayList<BillingItem>tempItems = new ArrayList<BillingItem>();
Iterator<BillingItem> i = this.billingItems.iterator();
BillingItem item;
while(i.hasNext()) {
item = i.next();
if( item.getStatus().equals("D")) {
tempItems.add(item);
}
}
this.billingItems.removeAll(tempItems);
}
However when there are deleted items to filter I'm seeing
Hibernate: update billing_on_item set ch1_id=null where ch1_id=? and id=?
which produces an exception because ch1_id is a foreign key and cannot be null. However hibernate is binding the parameters to correct values. Why is this update occurring in the first place? How do I correct the error?
Thanks in advance,
Randy
By removing the items from the collection, you're telling Hibernate that the association between the two entities doesn't exist anymore, so obviously, Hibernate removes what materializes this association in the database: it sets the foreign key to null.
What you probably want is just a getter in your entity that returns only the non-deleted items:
public List<BillingItem> getNonDeletedItems() {
List<BillingItem> result = new ArrayList<BillingItem>();
for (BillingItem item : this.billingItems) {
if (!"D".equals(item.getStatus()) {
result.add(item);
}
}
return result;
}
The #OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) line says that it will cascade ALL updates. Look into CascadeType.