I am puzzled by a JPA behavior which I did not expect in that way (using Eclipselink).
I run on Wildfly 10 (JDK-8) a stateless session EJB (3.2). My method call is - per default - encapsulated in a transaction.
Now my business method, when reading and updating a entity bean, did not recognize updates - especially the version number of the entity. So my call results in a
org.eclipse.persistence.exceptions.OptimisticLockException
My code looks simplified as this:
public ItemCollection process(MyData workitem) {
....
// load document from jpa
persistedDocument = manager.find(Document.class, id);
logger.info("#version=" + persistedDocument.getVersion());
// prints e.g. 3
// change some data
....
manager.flush();
logger.info("#version=" + persistedDocument.getVersion());
// prints e.g. 4
....
// load document from jpa once again
persistedDocument = manager.find(Document.class, id);
logger.info("#version=" + persistedDocument.getVersion());
// prints e.g. 3 (!!)
// change some data
....
manager.flush();
// Throws OptimisticLockException !!
// ...Document#1fbf7c8e] cannot be updated because it has changed or been deleted since it was last read
...
}
If I put the code (which changes the data and flush the entity bean) in a method annotated with
#TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
everything works as expected.
But why is the second call of the find() method in my code not reading the new version number? I would expect version 4 after the flush() and find() call.
After all it looks like calling
manager.clear();
solves the problem. I thought that detaching the object should do the same, but in my case only calling clear() did fix the problem.
More findings:
After all it seems not be a good idea to call the methods detach() and flush() form a service layer. I did this, because I wanted to get the new version id of my entity before I left my business method to return this id to the client. I changed my strategy in this case and I removed all the 'bad stuff' with detaching and flushing my entity beans. The code becomes more clear and after all the code complexity was reduced dramatically.
And of course the entityManager now behaves correctly. If I query again the same entity bean several times in one transaction, the entityManager returns the correct updated version.
So the answer to my own question is: Leave the methods flush() and clear(), as long as there is no really good reason for it to use them.
Related
Sample Scenario
I have a limit that controls the total value of a column. If I make a save that exceeds this limit, I want it to throw an exception. For example;
Suppose I have already added the following data: LIMIT = 20
id
code
value
1
A
15
2
A
5
3
B
12
4
B
3
If I insert (A,2) it exceeds the limit and I want to get exception
If I insert (B,4) the transaction should be successful since it didn't exceed the limit
code and value are interrelated
What can I do
I can check this scenario with required queries. For example, I write a method for it and I can check it in the save method. That's it.
However, I'm looking for a more useful solution than this
For example, is there any annotation when designing Entity ?
Can I do this without calling the method that provides this control every time ?
What examples can I give ?
#UniqueConstraint checking if it adds the same values
Using transaction
The most common and long-accepted way is to simply abstract in a suitable form (in a class, a library, a service, ...) the business rules that govern the behavior you describe, within a transaction:
#Transactional(propagation = Propagation.REQUIRED)
public RetType operation(ReqType args) {
...
perform operations;
...
if(fail post conditions)
throw ...;
...
}
In this case, if when calling a method there is already an open transaction, that transaction will be used (and there will be no interlocks), if there is no transaction created, it will create a new one so that both the operations and the postconditions check are performed within the same transaction.
Note that with this strategy both operation and invariant check transactions can combine multiple transactional states managed by the TransactionManager (e.g. Redis, MySQL, MQS, ... simultaneously and in a coordinated manner).
Using only the database
It has not been used for a long time (in favor of the first way) but using TRIGGERS was the canonical option used some decades ago to check postconditions, but this solution is usually coupled to the specific database engine (e.g. in PostgreSQL or MySQL).
It could be useful in the case where the client making the modifications is unable or unwilling (not safe) to check postconditions (e.g. bash processes) within a transaction. But nowadays it is infrequent.
The use of TRIGGERS may also be preferable in certain scenarios where efficiency is required, as there are certain optimization options within the database scripts.
Neither Hibernate nor Spring Data JPA have anything built-in for this scenario. You have to program the transaction logic in your repository yourself:
#PersistenceContext
EntityManager em;
public addValue(String code, int value) {
var checkQuery = em.createQuery("SELECT SUM(value) FROM Entity WHERE code = :code", Integer.class);
checkQuery.setParameter("code", code);
if (checkQuery.getSingleResult() + value > 20) {
throw new LimitExceededException("attempted to exceed limit for " + code);
}
var newEntity = new Entity();
newEntity.setCode(code);
newEntity.setValue(value);
em.persist(newEntity);
}
Then (it's important!) you have to define SERIALIZABLE isolation level on the #Transactional annotations for the methods that work with this table.
Read more about serializable isolation level here, they have an oddly similar example.
Note that you have to consider retrying the failed transaction. No idea how to do this with Spring though.
You should use a singleton (javax/ejb/Singleton)
#Singleton
public class Register {
#Lock(LockType.WRITE)
public register(String code, int value) {
if(i_can_insert_modify(code, value)) {
//use entityManager or some dao
} else {
//do something
}
}
}
In my controller I invoke a method from a service that will invoke save() on my database, and return me the object that was saved. That class that I am saving has a auto_generated id, so when i save it to the database, i expect to be returned with the id set (and that is working fine). In the same controller i store a result of that save() in a variable, and I found out that it's id is not set. That is because save() will actually save i to the database once that transaction is completed (when i exit the controller method. My problem is that i want to use that result before i exit my controller in a different service. How can I force the service (and consenquetly the repositroy) to save it immidiatelly and return me the result.
The reason for using id of classA in classB , is because classB is a "conncetion" table between two tables in database, and I should update it only when certain conditions are met, but this is of the point. I have already tried saveAndFlush() method in repository, that this service is calling, but it doesn't help. My service is only doing calling a save(), or saveAndFlush() method and nothing else (so it can't be a problem in the service).
I have already tried #Transactional annotation with REQUIRES_NEW, but it isn't working.
#PostMapping("")
public ClassA createClassA(#RequestBody ClassA classA){
ClassA a = classAService.saveClassA (classA);
System.out.println("Id = " + a.getIdClassA());
classBService.saveClassB(new classB(a.getId()); //it will cause an exception if a.getId() returns 0
return classA;
}
System.out.println will print out Id=0, but should print out Id=(some number that database makes, and cannot be zero because database has AUTO INCREMENT)
I have already tested all other services, repositories, connections etc. I am just interested how to force a response to come immidatelly so it can be stored in a variable and used later in the method.
Well, thank you for the comments, #JBNizet and #Lebecca you were both right :). Indeed saveAndFlush() would reslove my problem if I had told my class that id will be generated in the database. Thats why the soultion is to put something like #GeneratedValue(strategy = GenerationType.IDENTITY), and to add saveAndFlush(). It worked after these two steps.
I'm facing a weird issue in Grails. When I make a findBy call after changing and saving values of a domain, I am still getting the old values even after the values get persisted to the database. I can see the values are changed in my table.
My code is something like this:
Car car = Car.findByCarId(carId)
car.modelName = "some_model_name"
car.save() // Not flushing here
Tire tire = Tire.findByIdAndCarId(tireId,carId)
tire.manufacturer = "some_manufacturer"
tire.save()
Light light = Light.findByIdAndCarId(lightId,carId)
light.manufacturer = "some_manufacturer"
light.save()
Mirror mirror = Mirror.findByIdAndCarId(mirrorId,carId)
mirror.manufacturer = "some_manufacturer"
mirror.save()
My domain also has a few one-to-many associations. Let's say something like this:
class Car {
String modelName
static hasMany = [tires : Tire, mirrors : Mirror, lights : Light]
}
After these changes, when I make a DB call for the Car domain, I still get the older values:
Car car = Car.findById(carId)
println car.modelName // This gives older value
I know this is because the values are yet to be persisted to the database. I want to know if I use car.save(flush: true) in the above code, will it cause collection was not processed by flush() error? However, I am also getting the older values even after the values are persisted to the database. (e.g. when I make the above query after a long time) I can see the values are changed in my tables, but when I do the above query, it gives me the old values. Does Hibernate cache this query automatically? I use the above query quite a lot of times.
When I use withNewSession, it retrieves the new values:
Car car
Car.withNewSession {
car = Car.findById(carId,[readOnly : true])
}
println car.modelName // Gives new value
I want to know how this is giving the new values everytime, since I'm not flushing the current session. Instead, I'm only using a new Hibernate session. Does readOnly command flush the current session? The above code works fine for me. Should I use withNewSession instead of flushing while saving?
Thanks.
All your business logic has to be inside a transactional service. All services in grails are transactional by default. Take care about notations as #Transactional or #NotTransactional
If you want to manage data in a controller (not recommended) you should surround your code into a Transaction. An allow hibernate to manage the transaction, not breaking it with flush.
All changes are commited after a transaction finishes.
Remember that you could also use the refresh method which re-reads the state of the given instance from the underlying database.
domainInstance.refresh()
We are using Spring and IBatis and I have discovered something interesting in the way a service method with #Transactional handles multiple DAO calls that return the same record. Here is an example of a method that does not work.
#Transactional
public void processIndividualTrans(IndvTrans trans) {
Individual individual = individualDAO.selectByPrimaryKey(trans.getPartyId());
individual.setFirstName(trans.getFirstName());
individual.setMiddleName(trans.getMiddleName());
individual.setLastName(trans.getLastName());
Individual oldIndvRecord = individualDAO.selectByPrimaryKey(trans.getPartyId());
individualHistoryDAO.insert(oldIndvRecord);
individualDAO.updateByPrimaryKey(individual);
}
The problem with the above method is that the 2nd execution of the line
individualDAO.selectByPrimaryKey(trans.getPartyId())
returns the exact object returned from the first call.
This means that oldIndvRecord and individual are the same object, and the line
individualHistoryDAO.insert(oldIndvRecord);
adds a row to the history table that contains the changes (which we do not want).
In order for it to work it must look like this.
#Transactional
public void processIndividualTrans(IndvTrans trans) {
Individual individual = individualDAO.selectByPrimaryKey(trans.getPartyId());
individualHistoryDAO.insert(individual);
individual.setFirstName(trans.getFirstName());
individual.setMiddleName(trans.getMiddleName());
individual.setLastName(trans.getLastName());
individualDAO.updateByPrimaryKey(individual);
}
We wanted to write a service called updateIndividual that we could use for all updates of this table that would store a row in the IndividualHistory table before performing the update.
#Transactional
public void updateIndividual(Individual individual) {
Individual oldIndvRecord = individualDAO.selectByPrimaryKey(trans.getPartyId());
individualHistoryDAO.insert(oldIndvRecord);
individualDAO.updateByPrimaryKey(individual);
}
But it does not store the row as it was before the object changed. We can even explicitly instantiate different objects before the DAO calls and the second one becomes the same object as the first.
I have looked through the Spring documentation and cannot determine why this is happening.
Can anyone explain this?
Is there a setting that can allow the 2nd DAO call to return the database contents and not the previously returned object?
You are using Hibernate as ORM and this behavior is perfectly described in the Hibernate documentation. In the Transaction chapter:
Through Session, which is also a transaction-scoped cache, Hibernate provides repeatable reads for lookup by identifier and entity queries and not reporting queries that return scalar values.
Same goes for IBatis
MyBatis uses two caches: a local cache and a second level cache. Each
time a new session is created MyBatis creates a local cache and
attaches it to the session. Any query executed within the session will
be stored in the local cache so further executions of the same query
with the same input parameters will not hit the database. The local
cache is cleared upon update, commit, rollback and close.
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.