Home » Eclipse Projects » EclipseLink » Find managed entities in PersistenceContext(How to avoid duplicated managed entities)
Find managed entities in PersistenceContext [message #1855608] |
Sun, 23 October 2022 12:06  |
Ulrich Cech Messages: 8 Registered: February 2018 |
Junior Member |
|
|
Hi to all,
I face some problem in a container managed environment (Payara 5 2022.2 with Eclipselink):
1. I do a named query. If the result is empty, some new Entity is created, otherwise the returned entity from the query should be used (so far, so easy)
2. But if I do a persist() with the new entity and make the query again, the result is always empty/not found, because there was no INSERT statement.. This is somewhat problematic, because then duplicated entities are created, and some unique index fails, and the whole transaction is rolled back because of the UniqueConstraintViolation.
I can „unwrap" the EntityManager to UnitOfWorkImpl.class and get all objects in the peristence-context via getCloneMapping().keyset()
but I thought, that the persistence-context will be flushed before a new query resulting is executed an the the query will then return the new inserted entity.
Other try was to always flush() the entityManager after each „persist" or merge, but I m not sure, if this is the right behavior/usage of the flush() method (performance impact?!)
Btw: this „unwrapping" results in „no longer be JPA only dependent", rather dependent on Eclipselink (that's no real problem but a more JPA-general solution will be appreciated).
Perhaps, some Eclipselink-Experts could led me in the right direction.
Many thanks in advance
Ulrich
|
|
| |
Re: Find managed entities in PersistenceContext [message #1857468 is a reply to message #1855854] |
Thu, 09 February 2023 07:52   |
Ulrich Cech Messages: 8 Registered: February 2018 |
Junior Member |
|
|
Ok, I try to explain the situation a bit better:
I have some (meta-data) persistent-object where I track some additional states for some other persistent object. But this meta-data object is not needed in any case, so I cannot instantiate and persist is when I persist the "main" persistent-entity, because it is user-dependent.
So, in case I need this meta-data object, I query the database for it with (userId and some other matching parameters). If the result is empty, I know, I need to instantiate the new meta-data persistent-object with "entityManager.persist(metaData);"
So far so good. But within the transaction, another business-case also needs the meta-data object. And this business-case can be called separately, so these two don't know each other, so again, I query the database for this meta-data object, and now it don't find it again (because the first creation was not committed and flushed to the database, so the query must be empty).
Again, I create a new meta-data object and call again "entityManager.persist(metaData);".
And now the problem occurs: If i commit this transaction, there a two meta-data persistent objects, which want to be persisted, but this fails, because of "duplicate keys" (for sure, because the both meta-data objects have the same indexed-keys).
So, I can use the following with Eclipselink to try to find the meta-data object in the current persistence context and return it to the second business-case so that there is only ONE meta-data object, but this seems to me a bit hacky and I wanted to know, if there is some other mechanism for this situation.
Many thanks in advance
Ulrich
|
|
|
Re: Find managed entities in PersistenceContext [message #1857540 is a reply to message #1857468] |
Tue, 14 February 2023 15:17   |
Chris Delahunt Messages: 43 Registered: December 2021 |
Member |
|
|
I think I have the problem now, but your description still isn't clear.
You have a shared object you are trying to use in two separate processes, using two separate transactions - but transactions (usually) are isolated. They cannot see inserts made until the full transaction commits, so there is no way for one to know you are trying to insert the object in the other, or to wait for that 'first' process to complete before it does its thing.
To be clear, since your processes are very separate there really is no way for the processes to know about the other - even reading dirty transaction changes won't guarantee two inserts can't happen as there is always a window where they both read and they both attempt to persist that shared object.
Some options I see are:
1) Let them all go and the first one to commit wins. The second will get an exception that it should be able to handle by querying the shared object and trying its work again. This is common in optimistic locking patterns where you expect infrequent collisions, and you can force a flush after the persist to reduce the cost of the 'second' process going too far with other processing before the collision is detected.
2) Immediately query the shared object, and if it doesn't exist create it immediately in its own transaction. This object handling too might still get conflicts, but since this is outside your process work, you can just ignore it and re-query the shared object to return.
3) lock on the shared object somehow so that only one process can run with it at a time - using java synchronization locks, pessimistic locks in the database, or what ever singleton lock pattern fits your app.
|
|
|
Re: Find managed entities in PersistenceContext [message #1857586 is a reply to message #1857540] |
Thu, 16 February 2023 00:32   |
Ulrich Cech Messages: 8 Registered: February 2018 |
Junior Member |
|
|
Thanks again for your response... I will try to clarify more with some very simplified code-example:
The main point is, that it is exactly ONE transaction...
In the following code example, there are two "Command"-EJBs, which are used by an "OverallProcess"-EJB. The OverallProcess-Bean starts the transaction, when calling the startingPoint() method. Both Command-EJBs query the database for some object and if it does not exist, they create the object and set their specific values. The persist() method in CommandA creates the first INSERT-SQL-statement, the persist()-method in CommandB create the second INSERT-SQL-statement.
After finishing method startingPoint() of the "OverallProcess"-EJB, where the transaction starts and ends, the unique constraint violation exception is thrown because now two users with the same ID should be inserted (which of course fails because of unique index of userId).
And currently, to solve the situation, I would need to look at the managed objects within the EntityManager for a user-object with a specific ID and if I find it, I do not create a new one, but rather use the one from the EntityManager and set the valueB(...) there.
Or, could it be a solution to call entityManager.flush(); after each execute()-method of the Command-EJBs? Would the second query in CommandB-EJB see the INSERT from the first CommandA-EJB?
@Stateless
public class CommandA {
public void execute(UUID userId) {
final List resultList = entityManager.createQuery("select * from tableA where userId= ?1").setParameter(1, userId).getResultList();
if (resultList.isEmpty()) {
User user = new User();
user.setUserId(userId)
user.setValueA(...)
entityManager.persist(user); // <--- CREATES FIRST INSERT
} else {
resultList.get(0).setValueA(...);
entityManager.merge(user);
}
}
}
@Stateless
public class CommandB {
public void execute(UUID userId) {
final List resultList = entityManager.createNativeQuery("select * from tableA where userId = ?1").setParameter(1, userId).getResultList();
if (resultList.isEmpty()) {
User user = new User();
user.setUserId(userId)
user.setValueB(...)
entityManager.persist(user); // <-- CREATES second insert statement
} else {
resultList.get(0).setValueB(...);
entityManager.merge(user);
}
}
}
@Stateless
public class OverallProcess {
@EJB
CommandA commandA;
@EJB
CommandB commandB;
public void startingPoint() {
commandA.execute(...);
commandB.execute(...);
}
// --> after finishing this method (and the transaction is committed, it throws the exception because of unique constraint)
}
|
|
| |
Re: Find managed entities in PersistenceContext [message #1857811 is a reply to message #1857696] |
Mon, 27 February 2023 09:57   |
Ulrich Cech Messages: 8 Registered: February 2018 |
Junior Member |
|
|
Hi Chris,
<If they are in the same transaction, everything should be visible to each other>
And that is the problem, it is definitely not, at least in this/my situation.
Command-EJB1 queries the database for an entity, which does not exist, so it is created and "entityManager.persist(entity1a)".
Command-EJB2 queries the database again for the entity (because these "processes" are normally separated, but in this case, I need both processes), and important here, it is still the same transaction. So Command-EJB2 does not find the entity (because the persist() in EJB1 does not flush to DB), and creates a new entity and calls again "entityManager.persist(entity1b)"... entity1a and entity1b are initially created identically with the same business keys.
And now, I have the problem, when the transaction commits, because now 2 SQL-INSERTS are generated (for entity1a und entity 1b)
So, I only see these two options:
1. flushing the EntityManager after each "Command-EJB" manually
2. entityManager.unwrap(UnitOfWorkImpl.class) and then getting the getCloneMapping().keySet() and check manually for an already existing entity with these business keys
|
|
| | |
Goto Forum:
Current Time: Mon Dec 04 14:11:48 GMT 2023
Powered by FUDForum. Page generated in 0.02682 seconds
|