Related
When an object is written to database and the primary identifier (id) is known, it can be retrieved by the code below:
MyObject myObject = session.get(Class<MyObject>, id);
It seems, there is another way similar to get() method:
IdentifierLoadAccess<MyObject> ila = session.byId(Class<MyObject>);
MyObject myObject = ila.load(id);
I'm looking for a scenario which clarifies differences between them and describes the reason for having two similar methods for the same job in API.
same question can be asked about session.load() and session.byId().getReference().
Edit 1:
According to API documentation:
session.get() and session.byId().load() return persistent instance with given identifier, or null if there is no such persistent instance.
session.load() and session.byId().getReference() might return a proxied instance that is initialized in demand.
IdentifierLoadAccess allows you to specify:
LockOptions
CacheMode
even specifying both of them at once:
Post post = session
.byId( Post.class )
.with( new LockOptions( LockMode.OPTIMISTIC_FORCE_INCREMENT) )
.with( CacheMode.GET )
.load( id );
The same for getting a Proxy reference via getReference(id).
So, they are more flexible than the standard get or load which only take the entity identifier.
The similarity between
MyObject myObject = session.get(Class<MyObject>, id);
and
IdentifierLoadAccess<MyObject> ila = session.byId(Class<MyObject>);
MyObject myObject = ila.load(id);
is that both uses the copncept of hibernate cache mechanism but difference comes in fetching the data from database i.e
When we use session.get(Class,id) data from database comes in cache and you can make changes on that data and will be reflected back in database, as hibernate internally maintains a time stamp cache. This time stamp cache records the time at which a particular Hibernate managed table got modified and before returning the data from entity cache it validate whether the result cache are older with respect to table modification time.
But in case of session.byId().getReference() hibernate uses the concept of natural id in which data from database comes in cache but only onces.If you do any changes on that data using session.save(entity object) approach hibernate will throw an exception and if you do manually modification of table(insert,update,delete) it will not be reflected back when you fetch the data again as it always get the data from cache without checking whether that table for that entity has been modified again or not.
In case of session.get() and session.load() if there is any change in database like (insert,delete,update) of record it will get reflected either in the form of record or null pointer exception if record gets deleted.But in case of session.byId().load() and session.byId().getReference() it will first get the record from database when you try to fetch first time then it will save those record in session and will be shown to user from session only if any (insertion,deletion,updation) occurs then it will not be reflected back
It's mostly used in polymorphic association/queries. assume you have an entity named User with the BillingDetails association. If BillingDetails was mapped with
lazy="true" (which is the default), Hibernate would proxy the association target. In this case, you wouldn’t be able to perform a type-cast to the concrete class CreditCard (which is a subclass of BillingDetails) at runtime, and even the instanceof operator would behave strangely:
User user = (User) session.get(User.class, userid);
BillingDetails bd = user.getDefaultBillingDetails();
System.out.println( bd instanceof CreditCard ); // Prints "false"
CreditCard cc = (CreditCard) bd; // ClassCastException!
To perform a proxy-safe typecast, use load()
User user = (User) session.get(User.class, userId);
BillingDetails bd = user.getDefaultBillingDetails();
// Narrow the proxy to the subclass, doesn't hit the database
CreditCard cc =
(CreditCard) session.load( CreditCard.class, bd.getId() );
expiryDate = cc.getExpiryDate();
Note that you can avoid these issues by avoiding lazy fetching, as in the follow-ing code, using an eager fetch query
User user = (User)session.createCriteria(User.class)
.add(Restrictions.eq("id", uid) )
.setFetchMode("defaultBillingDetails", FetchMode.JOIN)
.uniqueResult();
// The users defaultBillingDetails have been fetched eagerly
CreditCard cc = (CreditCard) user.getDefaultBillingDetails();
expiryDate = cc.getExpiryDate();
Truly object-oriented code shouldn’t use instanceof or numerous typecasts. If you find yourself running into problems with proxies, you should question your design, asking whether there is a more polymorphic approach.
The key difference between get() and load() method is that load() will throw an exception if an object with id passed to them is not found, but get() will return null. Another important difference is that load can return proxy without hitting the database unless required (when you access any attribute other than id) but get() always go to the database, so sometimes using load() can be faster than the get() method. It makes sense to use the load() method if you know the object exists but get() method if you are not sure about object's existence.
What is difference between get () and load() method? with respect to data fetching approach
public static void main(String[] args) {
SessionFactory factory= new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();
Transaction tx = null;
tx = session.beginTransaction();
System.out.println("1 st time calling load method");
Account acc =
(Account)session.load(Account.class, 180);
System.out.println("bal"+acc.getBalance());
System.out.println("2nd time calling load method");
Account acc1=(Account)session.load(Account.class, 180);
System.out.println("bal"+acc1.getBalance());
System.out.println("1 st time calling get method");
Account acc2= (Account) session.get(Account.class, accId);
System.out.println("bal"+acc2.getBalance());
System.out.println("2 st time calling get method");
Account acc2= (Account) session.get(Account.class, accId);
System.out.println("bal"+acc2.getBalance());
tx.commit();
session.close();
}
I got following output
1 st time calling load method
Hibernate:
/* load com.abcd.Account */ select
account0_.ACCOUNTID as ACCOUNTID1_0_,
account0_.ACCOUNTTYPE as ACCOUNTT2_1_0_,
account0_.CREATIONDATE as CREATION3_1_0_,
account0_.BALANCE as BALANCE1_0_
from
a.MYACCOUNT account0_
where
account0_.ACCOUNTID=?
bal3000.0
2nd time calling load method
bal3000.0
1 st time calling get method
bal3000.0
2 st time calling get method
bal3000.0
From ouput it is clear that get method did not hit database.It behaves like load() method. Could any one tell me is this behavior correct.
As T Mishra states here:
By default, hibernate creates run-time proxies. It loads the objects as a proxy unless a fetch mode is specified or set to false.
That's because once the object is loaded in cache, the next subsequent calls perform repeatable read.
Although the state of this object changes from persistent to detached
The entity can be retrieved in 2 ways.
load() - returns the proxy object with an identifier.
get() - returns the complete object from database.
for more details click this link
Actually, both functions are use to retrieve an object with different mechanism,
session.load()
It will always return a “proxy” (Hibernate term) without hitting the database. In Hibernate, proxy is an object with the given identifier value, its properties are not initialized yet, it just look like a temporary fake object.
If no row found , it will throws an ObjectNotFoundException.
session.get()
It always hit the database and return the real object, an object that represent the database row, not proxy.
If no row found , it return null.
When you call session.load() method, it will always return a “proxy” object, whats the meaning of proxy object ?
Proxy means, hibernate will prepare some fake object with given identifier value in the memory without hitting the database, for example if we call session.load(Student.class,new Integer(107)); > hibernate will create one fake Student object [row] in the memory with id 107, but remaining properties of Student class will not even be initialized.
GET
When you call session.get() method, it will hit the database immediately and returns the original object.
If the row is not available in the database, it returns null.
hibernatesession.get() will fetch the real object from the database and hibernatesession.load() will return proxy without hitting the database. For more details click here. It explains get and load method and their difference with example codes.
Use load:
If you are sure about the object availability that you are retrieving
from DB. Else you will end up catching ObjectNotFoundException.
When you have heavy object to be loaded (Since it loads lazily whenever you use it)
Use get:
If you are not sure about the object availability in the DB.
You get luxury to check for null, when there is not object available in DB.
When you have light object to be loaded (since it loads eagerly).
** Load:** Whenever the load() method is called, the hibernate creates a proxy object of a POJO class, and it will set the id to the proxy object, then it returns the proxy object to the program. Based on the operations performed on the proxy object, the hibernate will decide whether to go cache or database to load the data. This process is called lazy loading.
** Get:** When we call the get() method, then hibernate first goes to first level cache and if that object doesn’t exist in the first level cache then it goes to the database and loads the object from the database. If Id doesn’t exist in the database, then get() method returns null. When get() method is called no proxy object is created, hence it is called as early loading.
Ref : http://docs.jboss.org/hibernate/orm/4.3/javadocs/
Complete Example you can find # my blog: http://www.onlinetutorialspoint.com/hibernate/hibernate-session-differences-between-load-and-get.html
Here is a simple explanation of what you need:
http://www.mkyong.com/hibernate/different-between-session-get-and-session-load/
Or by looking at the API:
http://docs.jboss.org/hibernate/orm/4.3/javadocs/
with get : Return the persistent instance of the given named entity. The persistent one, so the one stored in the database.
with load: Read the persistent state associated with the given identifier into the given transient instance.
In the first link you find very useful examples to test the differences..
Load: when we call session.load() method it doesn't hit directly database. It creates and return proxy object if object didn't belongs in db it throws "ObjectNotFountException". And supports lazy loading.
Get: It hits directly object in db and gives original value if object not found then it returns null. And it supports eager loading.
From the above example - both the functions are not working same.
load() - hibernate will only load proxy of the object with the specified ID. All the properties will not be set in advanced. Once you call the getter methods on the object, the query will be issues.
So when you can a getAccount method , select query will be issued and the result object will be saved in cache as it is retrieved by ID. So any subsequent calls via get will not result any select statement.
get() - will always retrieve the object from database with full properties populated. For properties defined in collections, depends on lazy initialization configuration.
Please note - Any subsequent calls on the same session will return the object from the cache only and hence no queries will be returned. That is what happening with the call to get method in the first time and the second time as the object is ready in cache by calling the acc.getBalance() on the object retrieved by load.
Don't forget performance aspect between get and load method
The get() method fetches data as soon as it’s executed while the load() method returns a proxy object and fetches only data when object properties is required.
So that the load() method gets better performance because it support lazy loading.
Whe should use the load() method only when we know data exists because it throws exception when data is not found.
You can see the example that demo this difference on the tutorial Difference between get and load method in Hibernate
When you call session.load() method, it will always return a proxy object, whats the meaning of proxy object ?
Proxy means, hibernate will prepare some fake object with given identifier value in the memory without hitting the database, for example if we call.
session.load(Student.class,new Integer(107));
hibernate will create one fake Student object [row] in the memory with id 107, but remaining properties of Student class will not even be initialized, observe this graphical representation…
It will hit the database only when we try to retrieve the other properties of Student object I mean stdName, stdCountry.
If we call s2.getStdName() then hibernate will hit the database and search the row with student id 107 and retrieve the values, if object [row] not found in the database it will throws ObjectNotFoundException.
session.get()
When you call session.get() method, it will hit the database immediately and returns the original object.
If the row is not available in the database, it returns null.
So which is the best method to use, hibernate load() or get()?
It's completely your choice .
I have a hibernate object that I'm persisting using session.saveOrUpdate without committing to the database which gives me the object id. I'm passing the object id to my interface which will later be used to rebuild the object on submit. When I submit the form, I'm no longer able to do a session query to retrieve object because the object technically doesn't exist in the database.
Does anybody know how to retrieve that object? If I just create a new object, the database id's will increment out of control. Any thoughts?
Question Edits and clarification.
When the form is submitted and contains errors, I need to catch the errors and reload the page with the errors. The problem I've been having is I'm losing the non persisted data in my collections. My solution was to temporarily persist the current object with all the child objects to the users session and then copy the persisted object back into the main object once the page reloaded which would return all the users previous data. However I've run into the failed to lazily initialize a collection of role: which is why I've resorted to a transaction such as session.update. This solved this issue, but gave id's to the newly created objects prior to the validation error before actually saving to the database.
Code Sample
#Property
private PurchaseRequest pr;
#Persist
private PurchaseRequest prPersist;
Class<?> onActivate(Long prId) {
if(request.isXHR()) {
return null;
}
PurchaseRequest purchaseRequest = null;
if(prPersist != null) {
session.update(prPersist);
purchaseRequest = prPersist;
prPersist = null;
} else {
purchaseRequest = (PurchaseRequest) session.createCriteria(PurchaseRequest.class)
.add(Restrictions.idEq(prId))
.uniqueResult();
}
this.pr = purchaseRequest;
}
Validation Method
void onValidateFromPR() throws Exception {
if (form.getHasErrors()) {
prPersist = this.pr;
return Page.Index;
}
}
There's no reason to saveOrUpdate an object and assign it an ID if you don't want it to be saved in the database yet. Only save it when the form is submitted and the data contained in the object are valid.
The thing is that the object is saved in the current DB transaction. That's what it is not yet visible to another transactions (or sessions).
That object does not exists for another transactions until the current one is commited. You can play along with Isolation level, but I would not recommend it.
Either you persisted it at the end of the transaction, and use a detach reattach pattern. Or you do partial saves, where you save an object that is not fully done. You can flag it someway.
Another approach is to use an in-memory storage of such objects.
It is legit that the ID becomes generated since every time you call saveOrUpdate or whatever method that makes an object persistent you are telling Hibernate that you are willing to save it eventually.
The Hibernate JavaDoc states that Session.update(Object o) would raise an exception if there's already a persistent instance of o, right?
If there is a persistent instance with the same identifier, an exception is thrown.
However, the following code doesn't throw anything when I run it. And I think it should!
Email email = new Email("andre", "girafa", "hi");
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
session.save(email);
session.update(email);
session.update(email);
tx.commit();
// didn't throw... Let's try again
tx = session.beginTransaction();
session.update(email);
session.update(email);
tx.commit();
session.close();
// still nothing! :(
As you can say, twice I try to do multiple update()s, but still Hibernate's taking easy on me.
Anybody has a hunch why?
EDIT: it seems that it would only throw if another equivalent object, for instance, email2 with the same ID as email. I guess the documentation was kinda sloppy there.
Update in Hibernate is not UPDATE in SQL language. Hibernate handles SQL UPDATEs
automatically through state cache in Session object.
But it's only for entities loaded in current session. This method, session.update(object) is meant for attaching object from another tier to current session to track and, possible, update at the end.
In your case it's just an NOOP. It'll sourly throw if:
Email email = new Email("andre", "girafa", "hi");
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
int id = session.save(email);
Email anotherEmail = new Email("", "", "");
anotherEmail.id = id;
session.update(anotherEmail); // will throw
You could read more about update method semantics on Hibernate reference.
No error because it's the same instance you're updating.
The error is thrown if a DIFFERENT persistent instance is present in the session and you try to update().
Can you try with a session.flush()? To see if that raises the exception (sometimes commit may not flush data depending on flush mode).
Although I would say the exception is only thrown if the object was updated outside the scope of the current session, let's say by a concurrent client.
I suggest you always stick to EntityManager.merge instead of Hibernate update method, update is quite confusing.
I found this article explain very clear which I always refer to.
As with persist and save, the update method is an “original” Hibernate
method that was present long before the merge method was added. Its
semantics differs in several key points:
it acts upon passed object (its return type is void); the update method transitions the passed object from detached to persistent
state;
this method throws an exception if you pass it a transient entity.
In the following example we save the object, then evict (detach) it
from the context, then change its name and call update. Notice that we
don’t put the result of the update operation in a separate variable,
because the update takes place on the person object itself. Basically
we’re reattaching the existing entity instance to the persistence
context — something the JPA specification does not allow us to do.
Person person = new Person();
person.setName("John");
session.save(person);
session.evict(person);
person.setName("Mary");
session.update(person);
Trying to call update on a transient instance will result in an
exception. The following will not work:
Person person = new Person();
person.setName("John");
session.update(person); // PersistenceException!
In order to understand the part above, you need to understand the difference betweentransient and detached object.
I have a situation in which I need to re-attach detached objects to a hibernate session, although an object of the same identity MAY already exist in the session, which will cause errors.
Right now, I can do one of two things.
getHibernateTemplate().update( obj )
This works if and only if an object doesn't already exist in the hibernate session. Exceptions are thrown stating an object with the given identifier already exists in the session when I need it later.
getHibernateTemplate().merge( obj )
This works if and only if an object exists in the hibernate session. Exceptions are thrown when I need the object to be in a session later if I use this.
Given these two scenarios, how can I generically attach sessions to objects? I don't want to use exceptions to control the flow of this problem's solution, as there must be a more elegant solution...
So it seems that there is no way to reattach a stale detached entity in JPA.
merge() will push the stale state to the DB,
and overwrite any intervening updates.
refresh() cannot be called on a detached entity.
lock() cannot be called on a detached entity,
and even if it could, and it did reattach the entity,
calling 'lock' with argument 'LockMode.NONE'
implying that you are locking, but not locking,
is the most counterintuitive piece of API design I've ever seen.
So you are stuck.
There's an detach() method, but no attach() or reattach().
An obvious step in the object lifecycle is not available to you.
Judging by the number of similar questions about JPA,
it seems that even if JPA does claim to have a coherent model,
it most certainly does not match the mental model of most programmers,
who have been cursed to waste many hours trying understand
how to get JPA to do the simplest things, and end up with cache
management code all over their applications.
It seems the only way to do it is discard your stale detached entity
and do a find query with the same id, that will hit the L2 or the DB.
Mik
All of these answers miss an important distinction. update() is used to (re)attach your object graph to a Session. The objects you pass it are the ones that are made managed.
merge() is actually not a (re)attachment API. Notice merge() has a return value? That's because it returns you the managed graph, which may not be the graph you passed it. merge() is a JPA API and its behavior is governed by the JPA spec. If the object you pass in to merge() is already managed (already associated with the Session) then that's the graph Hibernate works with; the object passed in is the same object returned from merge(). If, however, the object you pass into merge() is detached, Hibernate creates a new object graph that is managed and it copies the state from your detached graph onto the new managed graph. Again, this is all dictated and governed by the JPA spec.
In terms of a generic strategy for "make sure this entity is managed, or make it managed", it kind of depends on if you want to account for not-yet-inserted data as well. Assuming you do, use something like
if ( session.contains( myEntity ) ) {
// nothing to do... myEntity is already associated with the session
}
else {
session.saveOrUpdate( myEntity );
}
Notice I used saveOrUpdate() rather than update(). If you do not want not-yet-inserted data handled here, use update() instead...
Entity states
JPA defines the following entity states:
New (Transient)
A newly created object that hasn’t ever been associated with a Hibernate Session (a.k.a Persistence Context) and is not mapped to any database table row is considered to be in the New (Transient) state.
To become persisted we need to either explicitly call the EntityManager#persist method or make use of the transitive persistence mechanism.
Persistent (Managed)
A persistent entity has been associated with a database table row and it’s being managed by the currently running Persistence Context. Any change made to such an entity is going to be detected and propagated to the database (during the Session flush-time).
With Hibernate, we no longer have to execute INSERT/UPDATE/DELETE statements. Hibernate employs a transactional write-behind working style and changes are synchronized at the very last responsible moment, during the current Session flush-time.
Detached
Once the currently running Persistence Context is closed all the previously managed entities become detached. Successive changes will no longer be tracked and no automatic database synchronization is going to happen.
Entity state transitions
You can change the entity state using various methods defined by the EntityManager interface.
To understand the JPA entity state transitions better, consider the following diagram:
When using JPA, to reassociate a detached entity to an active EntityManager, you can use the merge operation.
When using the native Hibernate API, apart from merge, you can reattach a detached entity to an active Hibernate Sessionusing the update methods, as demonstrated by the following diagram:
Merging a detached entity
The merge is going to copy the detached entity state (source) to a managed entity instance (destination).
Consider we have persisted the following Book entity, and now the entity is detached as the EntityManager that was used to persist the entity got closed:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
While the entity is in the detached state, we modify it as follows:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Now, we want to propagate the changes to the database, so we can call the merge method:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
And Hibernate is going to execute the following SQL statements:
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
If the merging entity has no equivalent in the current EntityManager, a fresh entity snapshot will be fetched from the database.
Once there is a managed entity, JPA copies the state of the detached entity onto the one that is currently managed, and during the Persistence Context flush, an UPDATE will be generated if the dirty checking mechanism finds that the managed entity has changed.
So, when using merge, the detached object instance will continue to remain detached even after the merge operation.
Reattaching a detached entity
Hibernate, but not JPA supports reattaching through the update method.
A Hibernate Session can only associate one entity object for a given database row. This is because the Persistence Context acts as an in-memory cache (first level cache) and only one value (entity) is associated with a given key (entity type and database identifier).
An entity can be reattached only if there is no other JVM object (matching the same database row) already associated with the current Hibernate Session.
Considering we have persisted the Book entity and that we modified it when the Book entity was in the detached state:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
We can reattach the detached entity like this:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
And Hibernate will execute the following SQL statement:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
The update method requires you to unwrap the EntityManager to a Hibernate Session.
Unlike merge, the provided detached entity is going to be reassociated with the current Persistence Context and an UPDATE is scheduled during flush whether the entity has modified or not.
To prevent this, you can use the #SelectBeforeUpdate Hibernate annotation which will trigger a SELECT statement that fetched loaded state which is then used by the dirty checking mechanism.
#Entity(name = "Book")
#Table(name = "book")
#SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Beware of the NonUniqueObjectException
One problem that can occur with update is if the Persistence Context already contains an entity reference with the same id and of the same type as in the following example:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
Now, when executing the test case above, Hibernate is going to throw a NonUniqueObjectException because the second EntityManager already contains a Book entity with the same identifier as the one we pass to update, and the Persistence Context cannot hold two representations of the same entity.
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
Conclusion
The merge method is to be preferred if you are using optimistic locking as it allows you to prevent lost updates.
The update is good for batch updates as it can prevent the additional SELECT statement generated by the merge operation, therefore reducing the batch update execution time.
Undiplomatic answer: You're probably looking for an extended persistence context. This is one of the main reasons behind the Seam Framework... If you're struggling to use Hibernate in Spring in particular, check out this piece of Seam's docs.
Diplomatic answer: This is described in the Hibernate docs. If you need more clarification, have a look at Section 9.3.2 of Java Persistence with Hibernate called "Working with Detached Objects." I'd strongly recommend you get this book if you're doing anything more than CRUD with Hibernate.
If you are sure that your entity has not been modified (or if you agree any modification will be lost), then you may reattach it to the session with lock.
session.lock(entity, LockMode.NONE);
It will lock nothing, but it will get the entity from the session cache or (if not found there) read it from the DB.
It's very useful to prevent LazyInitException when you are navigating relations from an "old" (from the HttpSession for example) entities. You first "re-attach" the entity.
Using get may also work, except when you get inheritance mapped (which will already throw an exception on the getId()).
entity = session.get(entity.getClass(), entity.getId());
I went back to the JavaDoc for org.hibernate.Session and found the following:
Transient instances may be made persistent by calling save(), persist() or
saveOrUpdate(). Persistent instances may be made transient by calling delete(). Any instance returned by a get() or load() method is persistent. Detached instances may be made persistent by calling update(), saveOrUpdate(), lock() or replicate(). The state of a transient or detached instance may also be made persistent as a new persistent instance by calling merge().
Thus update(), saveOrUpdate(), lock(), replicate() and merge() are the candidate options.
update(): Will throw an exception if there is a persistent instance with the same identifier.
saveOrUpdate(): Either save or update
lock(): Deprecated
replicate(): Persist the state of the given detached instance, reusing the current identifier value.
merge(): Returns a persistent object with the same identifier. The given instance does not become associated with the session.
Hence, lock() should not be used straightway and based on the functional requirement one or more of them can be chosen.
I did it that way in C# with NHibernate, but it should work the same way in Java:
public virtual void Attach()
{
if (!HibernateSessionManager.Instance.GetSession().Contains(this))
{
ISession session = HibernateSessionManager.Instance.GetSession();
using (ITransaction t = session.BeginTransaction())
{
session.Lock(this, NHibernate.LockMode.None);
t.Commit();
}
}
}
First Lock was called on every object because Contains was always false. The problem is that NHibernate compares objects by database id and type. Contains uses the equals method, which compares by reference if it's not overwritten. With that equals method it works without any Exceptions:
public override bool Equals(object obj)
{
if (this == obj) {
return true;
}
if (GetType() != obj.GetType()) {
return false;
}
if (Id != ((BaseObject)obj).Id)
{
return false;
}
return true;
}
Session.contains(Object obj) checks the reference and will not detect a different instance that represents the same row and is already attached to it.
Here my generic solution for Entities with an identifier property.
public static void update(final Session session, final Object entity)
{
// if the given instance is in session, nothing to do
if (session.contains(entity))
return;
// check if there is already a different attached instance representing the same row
final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass());
final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session);
final Object sessionEntity = session.load(entity.getClass(), identifier);
// override changes, last call to update wins
if (sessionEntity != null)
session.evict(sessionEntity);
session.update(entity);
}
This is one of the few aspects of .Net EntityFramework I like, the different attach options regarding changed entities and their properties.
I came up with a solution to "refresh" an object from the persistence store that will account for other objects which may already be attached to the session:
public void refreshDetached(T entity, Long id)
{
// Check for any OTHER instances already attached to the session since
// refresh will not work if there are any.
T attached = (T) session.load(getPersistentClass(), id);
if (attached != entity)
{
session.evict(attached);
session.lock(entity, LockMode.NONE);
}
session.refresh(entity);
}
Sorry, cannot seem to add comments (yet?).
Using Hibernate 3.5.0-Final
Whereas the Session#lock method this deprecated, the javadoc does suggest using Session#buildLockRequest(LockOptions)#lock(entity)and if you make sure your associations have cascade=lock, the lazy-loading isn't an issue either.
So, my attach method looks a bit like
MyEntity attach(MyEntity entity) {
if(getSession().contains(entity)) return entity;
getSession().buildLockRequest(LockOptions.NONE).lock(entity);
return entity;
Initial tests suggest it works a treat.
Perhaps it behaves slightly different on Eclipselink. To re-attach detached objects without getting stale data, I usually do:
Object obj = em.find(obj.getClass(), id);
and as an optional a second step (to get caches invalidated):
em.refresh(obj)
try getHibernateTemplate().replicate(entity,ReplicationMode.LATEST_VERSION)
In the original post, there are two methods, update(obj) and merge(obj) that are mentioned to work, but in opposite circumstances. If this is really true, then why not test to see if the object is already in the session first, and then call update(obj) if it is, otherwise call merge(obj).
The test for existence in the session is session.contains(obj). Therefore, I would think the following pseudo-code would work:
if (session.contains(obj))
{
session.update(obj);
}
else
{
session.merge(obj);
}
to reattach this object, you must use merge();
this methode accept in parameter your entity detached and return an entity will be attached and reloaded from Database.
Example :
Lot objAttach = em.merge(oldObjDetached);
objAttach.setEtat(...);
em.persist(objAttach);
calling first merge() (to update persistent instance), then lock(LockMode.NONE) (to attach the current instance, not the one returned by merge()) seems to work for some use cases.
Property hibernate.allow_refresh_detached_entity did the trick for me. But it is a general rule, so it is not very suitable if you want to do it only in some cases. I hope it helps.
Tested on Hibernate 5.4.9
SessionFactoryOptionsBuilder
try getHibernateTemplate().saveOrUpdate()