I'm working on some legacy code and trying to understand what's going on. EJB 3 + Hibernate 3.2 (pray for my poor soul)
There are 2 EJBs (A and B):
EJB-A retrieves an Object1 and modifies some field data.
SessionFactory sf = PersistenceManager.getSessionFactory;
Transaction tx = sessionContext.getUserTransaction;
tx.begin;
Session s = sf.getCurrentSesssion();
ObjectA a = (ObjectA)s.get(ObjectA.class, id);
a.setField(value);
s.update(a);
// other stuff happens and then transaction is committed
tx.commit();
// starts a new transaction?
tx = sessionContext.getUserTransaction();
tx.begin();
// Re initializes object
ObjectA a = (ObjectA)s.get(ObjectA.class, id);
EJB-B b = getService(EJB-b) // sorry pseudo coding this
b.performOperation();
Inside EJB-B, it reads the piece of data set in EJB-A. What I'm seeing is that the data is not updated when EJB-B reads it. I'm a bit confused, I assume that committing the transaction would force a flush and the session to push the data to the database.
Can someone explain to me what could be going on? Is it possible the commit call is performing some async call and is taking longer to actually persist the data than it takes to get to the next EJB and retrieve the data? Is there any way to get around that? Would it be possible to call tx.wasCommitted in a loop or something to wait until proceeding?
Thank you in advance for any insight.
Hibernate doesn't flush the data instantaneously, it flushes when the internal cache reaches some limit or the transaction is closed.
You can use flush here, refer Correct use of flush() in JPA/Hibernate.
Related
I have a Java Spring Boot application reading and writing data to a local Oracle 19c database.
I have the following CommandLineRunner:
#Override
public void run(String... args) {
final EntityManager em = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(customer());
//COMMENT1 em.flush();
/*COMMENT2
Query q = em.createQuery("from " + Customer.class.getName() + " c");
#SuppressWarnings("unchecked")
final Iterator<Object> iterator = (Iterator<Object>) q.getResultList().iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
final Customer c = (Customer) o;
log.info(c.getName());
}
*/
transaction.rollback();
}
When I run this code, using a packet sniffer to monitor TCP traffic between the application and database, I see what I expect: nothing particularly interesting in the conversation, as the em.persist(customer()) will not be flushed.
When I include the code in COMMENT1, then I'm surprised to find that the conversation looks the same - there is nothing interesting after the connection handshake.
When I include the code in COMMENT2, however, then I get a more complete TCP conversation. Now, the captured packets show that the write operation was indeed flushed to the database, and I can also see evidence of the read operation to list all entities following it.
Why is it that the TCP conversation does not reflect the explicit flush() when only COMMENT1 is removed? Why do I need to include COMMENT2 to see an insert into customer... statement captured in the TCP connection?
A call to flush() synchronizes your changes in the persistence context with the database but it may not commit the transaction immediately. Consider it as an optimization to avoid unnecessary DB writes on each flush.
When you un-comment the 2nd block then you see the flush getting executed for sure. This happens because the EM ensures your select query gets all the results in the latest state from DB. It, therefore, commits the flushed changes (alongwith any other changes done via other transactions, if any).
em.persist(customer());
persist does not directly insert the object into the database:
it just registers it as new in the persistence context (transaction).
em.flush();
It flushes the changes to the database but doesn't commit the transaction.
A query gets fired if there is a change expected in database(insert/update/delete)
em.rollback or em.commit will actually rollback or commit the transaction
And all these scenario depends on the flush mode, and I think behaviour is
vendor dependent. Assuming Hibernate, most probably FlushMode is set to auto in
your application and so the desired result as in second scenario.
AUTO Mode : The Session is sometimes flushed before query execution in order to ensure
that queries never return stale state
https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/FlushMode.html
In spring boot I think you can set it as
spring.jpa.properties.org.hibernate.flushMode=AUTO
In the following code sample:
Session session = getSessionFactory().openSession();
MyObject currentState = (MyObject)
session.get(MyObject.class, id, new LockOptions(LockMode.???));
if (!statusToUpdateTo.equals(currentState.getStatus())) {
tx = session.beginTransaction();
currentState.setStatus(statusToUpdateTo);
session.save(currentState);
tx.commit();
}
session.close();
As you might hopefully interpret from the code, the idea is to check our store and find an object with a given id, then update it to a given status. If we find that the object is already in the status we want to update to, we abandon doing anything. Otherwise, we update the status and save it back to the store.
The worry I've got is that what if several of these requests come through at once, and all try to read and update the same object? Looking through the doco it would seem like Hibernate "usually obtains exactly the right lock level automatically." (LockMode.class) but I'm still keen to ensure that only one thing can read the object, make the decision that it needs to update it, and then update it - without any other threads doing the same to the same database entry.
From the LockMode class I think PESSIMISTIC_WRITE is what I'm after, but can't seem to find a documentation resource that confirms this. Is anyone able to confirm this for me, and that the above code will do what I'm after?
So I noticed in my original code that upon session close, a lock was still left on the database, as subsequent calls to delete the rows I'd inserted didn't complete when tx.commit() was called on them.
After adding the following else block, my tests pass which I infer as meaning the lock has been released (as these rows are now being cleaned up).
Session session = getSessionFactory().openSession();
MyObject currentState = (MyObject)
session.get(MyObject.class, id,
new LockOptions(LockMode.PESSIMISTIC_WRITE));
if (!statusToUpdateTo.equals(currentState.getStatus())) {
tx = session.beginTransaction();
currentState.setStatus(statusToUpdateTo);
session.save(currentState);
tx.commit();
} else {
// Seems to clear lock
tx = session.beginTransaction();
tx.rollback();
}
session.close();
To me, this obviously reflects my lack of understanding about Hibernate's locking mechanisms, but it does seem slightly strange that one can get a lock using session.get(...) or session.load(...) and then not release a lock using session itself, but rather only through creating a transaction and committing/rolling back.
Of course, I could just be misunderstanding the observed behaviour :)
Currently we are using play 1.2.5 with Java and MySQL. We have a simple JPA model (a Play entity extending Model class) we save to the database.
SimpleModel() test = new SimpleModel();
test.foo = "bar";
test.save();
At each web request we save multiple instances of the SimpleModel, for example:
JPAPlugin.startTx(false);
for (int i=0;i<5000;i++)
{
SimpleModel() test = new SimpleModel();
test.foo = "bar";
test.save();
}
JPAPlugin.closeTx(false);
We are using the JPAPlugin.startTx and closeTx to manually start and end the transaction.
Everything works fine if there is only one request executing the transaction.
What we noticed is that if a second request tries to execute the loop simultaneously, the second request gets a "Lock wait timeout exceeded; try restarting transaction javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not insert: [SimpleModel]" since the first request locks the table but is not done until the second request times out.
This results in multiple:
ERROR AssertionFailure:45 - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: null id in SimpleModel entry (don't flush the Session after an exception occurs)
Another disinfect is that the CPU usage during the inserts goes crazy.
To fix this, I'm thinking to create a transaction aware queue to insert the entities sequentially but this will result in huge inserting times.
What is the correct way to handle this situation?
JPAPlugin on Play Framwork 1.2.5 is not thread-safe and you will not resolve this using this version of Play.
That problem is fixed on Play 2.x, but if you can't migrate try to use hibernate directly.
You should not need to handle transactions yourself in this scenario.
Instead either put your inserts in a controller method or in an asynchronous job if the task is time consuming.
Jobs and controller both handle transasctions.
However check that this is really what you are trying to achieve. Each http request creating 5000 records does not seem realistic. Perhaps it would make more sense to have a container model with a collection?
Do you really need a transaction for the entire insert? Does it matter if the database is not locked during the data import?
You can simply create a job and execute it for each insert:
for (int i=0;i<5000;i++)
{
new Job() {
doJob(){
SimpleModel() test = new SimpleModel();
test.foo = "bar";
test.save();
}.now();
}
This will create a single transaction for each insert and get rid of your database lock issue.
My question is related to Transactions and Exceptions
Requirements:
I have 10 records to insert into database table. And after inserting every record, I insert data into another table. So if inserting to second table fails, I want to rollback that record.
Ex.
Say handle cash transfer (from one account to account) for 10 persons at a time.
pseudo code:
------------- Start of EJB method
for(int i = 0; i < TransferRecords.length; i++)
{
try
{
//Deduct cash from TransferRecord.accountFrom --- Includes use of Hibernate Session
//Add cash in TransferRecord.accountTo -- Includes use of Hibernate Session
} catch(AppException exception)
{
//Rollback the transaction only for this particular transfer (i)
// But here when I go for next record it says session is closed
}
}
---------End of EJB method
Here AppException is created with #ApplicaitonException(rollback=true) annotion.
The functionality we want is: Even if the transaction fails for TransferRecord (say 2), I want the data to be committed for record 0, record 1, record 3, record 4 (etc... and but not for record 2)
But the issue here is: when TransferRecord 2 fails and when I move to TransferRecord 3, I get "Session Closed" error.
My doubts are:
1. Is this a right approach? or should I run the for loop(for each TransferRecord) outside of the EJB
2. How can I make sure that session is not closed but transaction is rolled back (only for that for particular failed transaction)
Thank you in advance.
I am using EJB3, Hibernate 3.x, Jboss 4.2.x and I am using Container Managed Transaction.
Is this a right approach?
No, with CMT, you method is your transactional unit. So here, all your TransferRecord and handled in a same and unique transaction.
By the way, how do you rollback the transaction? Do you propagate a RuntimeException or do you call setRollbackOnly()? I'm just curious.
Or should I run the for loop (for each TransferRecord) outside of the EJB?
Why outside? Nothing forces you to do that. If you want to process each TransferRecord in its own transaction, you should pass them to another EJB method (the code below is inspired by this answer):
// supposing processRecords is defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
#Stateless
#TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
#EJB
private MyStatelessLocal1 myBean;
public void processRecords(List<TransferRecord> objs) {
// No transactional stuff so no need for a transaction here
for(Object obj : objs) {
this.myBean.process(obj);
}
}
#TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(TransferRecord transferRecord) {
// Transactional stuff performed in its own transaction
// ...
}
}
How can I make sure that session is not closed but transaction is rolled back (only for that for particular failed transaction)
I think I covered that part.
The only option you have here is either to use user transaction instead of container managed transaction of loop outside the bean so that everytime you enter the bean you get fresh entity manager with associated transaction and connection (basically session)
I think that you can create two separated transactions, the first for the TransferRecord(1) (doing a commit once everything is fine) and then starting other TX for all the TransferRecord(i+1).
Another approach is using savepoints, being able to rollback and discard everything past that savepoint (but I like prefer the first approach).
Regards.
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.