I am new to hibernate. I would like to know if we have any alternatives for session.evict().
I have commented that line and my logic works fine.
session.evict() method is used to remove a particular object from cache associated with the session. So removing it will force the hibernate to fetch the object from database instead of looking into cache. Example
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
try
{
SomeEntity myEntity= (SomeEntity) session.load(SomeEntity.class, new Integer(1)); //look in cache (will not be found) if not found then fetch from database
System.out.println(myEntity.getName());
myEntity = (SomeEntity) session.load(SomeEntity.class, new Integer(1)); //look in cache(will be found) if not then fetch from database
System.out.println(myEntity.getName());
session.evict(myEntity); // will remove from cache
myEntity = (SomeEntity) session.load(SomeEntity.class, new Integer(1)); // object will again be fetched from database if not found in second level cache
System.out.println(myEntity.getName());
}
finally
{
session.getTransaction().commit();
HibernateUtil.shutdown();
}
Edit : Session.evict() will remove the entity from first level cache and after removing the object from the session, any change to object will not be persisted. The associated objects will also be detached if the association is mapped with cascade="evict".
Hope it helps!!
Related
I work on a Java project and I have to write a new module in order to copy some data from one database to another (same tables).
I have an entity Contrat containing several fields and the following field :
#OneToMany(mappedBy = "contrat", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#Cascade( { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
#BatchSize(size = 50)
private Set<MonElement> elements = new HashSet<MonElement>();
I must read some "Contrat" objects from a database and write them in another database.
I hesitate between 2 solutions :
use jdbc to query the first database and get the objects and then write those objects into the second database (paying attention to the order and the different keys). It will be long.
as the project currently uses Hibernate and contains all hibernate mapping classes, I was thinking about opening a first session to the first database, reading the hibernate Contrat object, setting the ids to null in the children elements and writing the object to the destination database with a second session. It should be quicker.
I wrote a test class for the second use case and the process fails with the following exception :
org.hibernate.HibernateException: Don't change the reference to a
collection with cascade="all-delete-orphan"
I think the reference must change when I set the ids to null, but I am not sure : I don't understand how changing a field of a Collection member can change the Collection reference
Note that if I remove DELETE_ORPHAN from the configuration, everything works, all the objects and their dependencies are written in the database.
So I would like to use the hibernate solution which is faster but I have to keep the DELETE_ORPHAN feature because the application currently uses this feature to ensure that every MonElement removed from the elements Set will be deleted in the database.
I don't need this feature but cannot remove it.
Also, I need to set the MonElement ids to null in order to generate new ones because their id in the first database may exist in the target database.
Here is the code I wrote which works well when I remove the DELETE_ORPHAN option.
SessionFactory sessionFactory = new AnnotationConfiguration().configure("/hibernate.cfg.src.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
// search the Contrat object
Criteria crit = session.createCriteria(Contrat.class);
CriteriaUtil.addEqualCriteria(crit, "column", "65465454");
Contrat contrat = (Contrat)crit.list().get(0);
session.close();
SessionFactory sessionFactoryDest = new AnnotationConfiguration().configure("/hibernate.cfg.dest.xml").buildSessionFactory();
Session sessionDest = sessionFactoryDest.openSession();
Transaction transaction = sessionDest.beginTransaction();
// setting id to null, also for the elements in the elements Set
contrat.setId(null);
for (MonElement element:contrat.getElements()) {
element.setId(null);
}
// writing the object in the database
sessionDest.save(contrat);
transaction.commit();
sessionDest.flush();
sessionDest.close();
This is way faster than managing myself the queries and the primary / foreign keys and dependencies between objects.
Does anyone have an idea to get rid of this exception ?
Or maybe I should change the state of the Set.
In fact I'm not trying to delete any element of this Set, I just want them to be considered as new objects.
If I don't find a solution, I will do something dirty : duplicate all hibernate entity objects in my new project and remove the DELETE_ORPHAN parameter in the newly created Contrat.
So the application will continue using its mapping and my new project will use my specific mapping. But I want to avoid that.
Thanks
A correct solution has been written by crizzis as a comment to my question.
I quote him :
I'd try wrapping the contrat.elements in a new collection (contrat.setElements(new HashSet<>(contrat.getElements())) before trying to persist the contract with the new session
It works well.
I understand that when does NonUniqueObjectException occurs and why does it occurs.
I saw many example on the internet for NonUniqueObjectException , each has same thing.
object is first detached from session1 so session1 is closed and cleared also and then object with same identifier is update or SaveorUpdate in session2.
Code snippet:
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close(); // end of first session, item is detached
item.getId();// The database identity is "1234" item.setDescription("my new description");
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
session2.update(item);// Throws NonUniqueObjectException
tx2.commit();
session2.close();
My question is even if seesion1 is closed , why does hibernate keeps detached object in session1 though not managing it. when does all the object be removed from session1.
Hibernate does not keep a reference to a detached object, i.e. detached objects are not part of any session, and their garbage collection is not impeded in any way.
The cause of a NonUniqueObjectException is that two different objects for the same database row have become associated with the same session. That's bad because Hibernate automatically detects changes to objects in the session, and writes these changes back to the database. If several objects for the same row are in the same session, it is ambiguous which object's state should be written. Because this would result in hard to find bugs, Hibernate refuses such a situation.
Usually, Hibernate ensures that all queries for a row in a given session return the same object, so this situation can not arise. However, if you use an object obtained from a different session with a new session, it becomes associated with the new session, which can fail if the new session already contains an object for that row.
This is why the newer EntityManager API no longer features an update method, that associates a pre-existing object with the session, but a merge method, that copies the contents of the object into the object associated with the session.
To understand, have a look here :
Item item2 = (Item) session2.get(Item.class, new Long(1234));
This will turns in persistent state item2 with id 1234 in the session2.
session2.update(item);
item is in detached state, and update() will turn item in persistent state on session2 >>> Hibernate throws NonUniqueObjectException, because there are two objects Item with same id in the same session !
Do it like this :
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
item2.setDescription("my new description");
session2.update(item2);
tx2.commit();
Does remove(Object entity) method of EntityManager work only on those objects got from find() method?
I have following code snippet:
public void deletePerson() {
EntityManager em = getEntityManager();
Person p = new Person("x", "y", 200);
em.remove(p);
}
But it is not removing the particular entry from database.
When I tried something like below:
public void deletePerson() {
EntityManager em = getEntityManager();
Person p = em.find(Person.class, 200);
em.remove(p);
}
It's working fine.
Quoting from ObjectDB's manual on deleting JPA entity objects:
In order to delete an object from the database it has to first be
retrieved (no matter which way) and then in an active transaction, it
can be deleted using the remove method.
An IllegalArgumentException is thrown by remove if the argument is not
a an instance of an entity class or if it is a detached entity.
When creating object with new operator, it becomes a detached entity, you need to persist it if you want to remove it.
When retrieving entity, you are retrieving persistent entity.
Something to that direction. EntityManager.remove works only for managed entities. How you obtained these managed entities does not matter, it can be for example:
via JPQL query
via Criteria API query
find method in EntityManager
by following relationship from some other entity.
created new entity and persisted it
But simply creating new object and trying to remove it does not work, because this new object is not managed entity. Also entity should not be yet detached.
Life of entity is quite much as follows, all in same transaction (entities outside their transaction are not managed):
Entity ent = new Entity(1); //entity is in new state, EntityManager never know
//anything about it
em.persist(ent); //entity is managed as long as not disconnected
//from EntityManager
em.clear(); // all previously managed entities, including ent, are now detached
Entity same = em.find(1); //managed same
em.remove(same); // entity is removed
Yes in case of merge or remove operation you have to use a find() operation and then use the remove method on the retrieved entity.
With JPA, you can remove an entity without retrieving it by simply executing a delete statement:
javax.persistence.Query q= entityManager.createQuery("delete from A where id = :id");
q.setParameter("id", "value of id to delete");
int deletedRows = q.executeUpdate();
I'm trying to reattach to the session an object from the HTTP session that was originally retrieved from the DB, I do this by calling session.lock(object, LockMode.None) and even though lock does not cascade this works all right for me because it does not push updates to the DB like merge does (the lock is required to open a detail view in a pop-up and the actual saving would occur later on the main window). Now to my surprise I have found that if my entity has a one to many relation any changes on that collection would cause a HibernateException "Reassociated object has dirty collection".
How am I supposed to reattach objects to the session without updating the DB or discarding the changes on the object then?
Here is the situation as code
EntityA t = createAnEntityA();
Session sess = factory.openSession();
sess.beginTransaction();
sess.save(t);
sess.getTransaction().commit();
sess.close();
// t is now saved on the DB but in dettached state
// change a simple property
sess = factory.openSession();
sess.beginTransaction();
t.setPropertyB("B");
sess.lock(t, LockMode.NONE);
// t is attached again, you won't get LazyInitializationException
// by calling its properties, although you have to be careful
// because the reattachment does not cascade to children
sess.getTransaction().commit();
sess.close();
// no updates went to the DB because setPropertyB was called
// when t was still dettached
// now change a collection
EntityC c = createAnEntityC();
t.getCollectionPropertyC().add(c);
sess = factory.openSession();
sess.beginTransaction();
sess.lock(t, LockMode.NONE);
// Exception is thrown :-(
sess.getTransaction().commit();
sess.close();
I'm afraid currently it's not possible without hitting DB.
Apparently, the exception is happening because of lock command.
There's a Jira reporting this behavior.
https://hibernate.atlassian.net/browse/HHH-511
It has 2 patches to fix the problem. You could try those patches.
But if you problem is only Lazy loading of collection, you could consider using Open Session In View pattern. (maybe not the best pattern, but it can work for your case)
I saw to types of update operation:
First:
getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) {
session.flush();
session.setCacheMode(CacheMode.IGNORE);
SomeObject ss = (SomeObject) session.get(SomeObject.class, id);
long next = ss.getAndIncrement();
session.update(ss);
session.flush();
return null;
}
});
and secondly
SomeObject ss = loadSomeObject();
long next = ss.getAndIncrement();
getHibernateTemplate.merge(ss);
These two method do the same. I want to know which one is better and safe and why. Thank you.
In the first operation the object ss is attached to the session. where as in the second operation its detached. So if you have an attached objects you can use update. If you have a detached objects then use merge which first attaches the object to the session then will do an update.
EDIT: For your information on attached(persistent) and detached objects :
Hibernate defines and supports the following object states:
Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. Transient instances will be destroyed by the garbage collector if the application does not hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition).
Persistent - a persistent instance has a representation in the database and an identifier value. It might just have been saved or loaded, however, it is by definition in the scope of a Session. Hibernate will detect any changes made to an object in persistent state and synchronize the state with the database when the unit of work completes. Developers do not execute manual UPDATE statements, or DELETE statements when an object should be made transient.
Detached - a detached instance is an object that has been persistent, but its Session has been closed. The reference to the object is still valid, of course, and the detached instance might even be modified in this state. A detached instance can be reattached to a new Session at a later point in time, making it (and all the modifications) persistent again. This feature enables a programming model for long running units of work that require user think-time. We call them application transactions, i.e., a unit of work from the point of view of the user.
Whats an API without any code examples?
SessionFactory sf = ctx.getBean("hibernateSessionFactory",SessionFactory.class);
Session session = sf.openSession();
Transaction t = session.beginTransaction();
try {
Session s2 = sf.openSession();
Organization org = (Organization)s2.get(Organization.class,100624l);//1
org.setOrgName("org");
s2.close();//2
Organization org1 = (Organization)session.get(Organization.class,100624l);//3
org.setOrgName("testOrg");
org1.setOrgName("org");//a
session.merge(org);//4
System.out.println(org == org1);//b
t.commit();
} catch (HibernateException e) {
t.rollback();
throw e;
}finally{
session.close();
}
1st instance is loaded and made persistent.
The instance is detached.
Another instance is loaded
At the point of executing this operation, there are 2 instances of the same object
in the session(org and org1) – org is detached and org1 is persistent
If we do an update() or saveOrUpdate() there, we get the below exception:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.spring.model.Organization#100624]
a. We do a merge here which:
I. Merges the state of the detached object + persistent object.
II. In case of conflict, the object which is merged wins, like in this case, the value saved will be : testOrg
III. Had we merged on org1, we would have got org.
b. This will always return false, meaning post merge, org was still in DETACHED state
I hope the diff. is clear now.
Summary :
saveOrUpdate() or update() will throw an exception if there are 2 instances of the same object in the session(one detached and one persistent)
merge() will not throw the exception, but will save the object while merging the changes.
Merge Does Following
Merge has intelligence. It has lot of pre-checks before it go actual merge(if required)
if Object is transient, It simply fires INSERT query makes object persistent(attached to session)
if Object is detached, fires select query to check whether data modified or not
if modified, fires UPDATE query otherwise just ignore merge task.
where as session.update
throws exception if object is transient.
if Object is detached, it simply fires UPDATE query irrespective of data changes to object.
session.merge is expensive than update
The basic difference is:
Merge() is not concerned about sessions whether persistent or detached...it will just update without considering the sessions.
In case of update() it will throw an exception like org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session.
It's explained here with a good example:
http://www.java4developer.com/difference-between-update-and-merge-in-hibernate/
Both update() and merge() methods in hibernate are used to convert the object which is in detached state into persistence state. But there is little difference. Let us see which method will be used in what situation.
Let Us Take An Example
SessionFactory factory = cfg.buildSessionFactory();
Session session1 = factory.openSession();
Employee s1 = null;
Object o = session1.get(Employee.class, new Integer(101));
s1 = (Student)o;
session1.close();
s1.setSSN(97);
Session session2 = factory.openSession();
Employee s2 = null;
Object o1 = session2.get(Employee.class, new Integer(101));
s2 = (Student)o1;
Transaction tx=session2.beginTransaction();
session2.merge(s1);
SessionFactory factory = cfg.buildSessionFactory();
Session session1 = factory.openSession();
Employee s1 = null;
Object o = session1.get(Employee.class, new Integer(101));
s1 = (Employee)o;
session1.close();
s1.setMarks(97);
Session session2 = factory.openSession();
Employee s2 = null;
Object o1 = session2.get(Employee.class, new Integer(101));
s2 = (Employee)o1;
Transaction tx=session2.beginTransaction();
session2.merge(s1);
Hope you are clear…, actually update and merge methods will come into picture when ever we loaded the same object again and again into the database, like above.