Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
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 Go to next message
Ulrich Cech is currently offline Ulrich CechFriend
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 #1855854 is a reply to message #1855608] Mon, 07 November 2022 21:09 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris DelahuntFriend
Messages: 54
Registered: December 2021
Member
You'll have to explain your situation a bit more and the settings you are using, as if you are querying in the same context you issued a persist call to, you should see the Entity you persisted. The default behavior ( https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/p_persistence_context_flushmode.htm ) is to have the context decide if it needs to flush when executing a query - EclipseLink has in-memory querying options though that do not require flushing, so it entirely depends.

Does issuing flush work around the issue?

If you are unwrapping the EntityManager, I would not be doing so as a solution, but to debug what is happening. If you find your entity in it, then it is purely a flush issue and we can look at the query and context options to work it out. You shouldn't need to use unwrap, as you can just issue a find operation with the ID from the persist.

If it doesn't, first guess is you are using some repository mechanism that has your persist call going to a separate and very different context than the one issuing your subsequent query. They need to be to the same EntityManager context.

Re: Find managed entities in PersistenceContext [message #1857468 is a reply to message #1855854] Thu, 09 February 2023 07:52 Go to previous messageGo to next message
Ulrich Cech is currently offline Ulrich CechFriend
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 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris DelahuntFriend
Messages: 54
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 Go to previous messageGo to next message
Ulrich Cech is currently offline Ulrich CechFriend
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 #1857696 is a reply to message #1857586] Tue, 21 February 2023 17:07 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris DelahuntFriend
Messages: 54
Registered: December 2021
Member
I do not follow. I thought I did when you said these 'processes' and the shared object were in the same transaction, but then you mentioned "Command"-EJBs and I don't think I do anymore. If they are in the same transaction, everything should be visible to each other. I suspect you maybe using multiple EntityManager contexts within this single transactions - essentially multiple unitsofWork that are isolated from each other. Yes, flushing one would push its current context to the database, which would then be readable to other UnitsOfWork/EntityManagers using the same transaction/connection, but.. Seems the more efficient solution is to just use a single EntityManager with the single cache. The first will read the entity and persist it if it isn't there; any subsequent lookups just hit the cache, not the database. You'd have to look at how the EntityManager is obtained within those beans and why it is using different contexts for the calls.

PS the code shows one method doing a select * as a JPQL query - I assume this is cut and paste error, as the string is native SQL. If you used JPQL, the query could use the entity manager cache and not have to hit the database at all if the entity was read or persisted into the context already.
Re: Find managed entities in PersistenceContext [message #1857811 is a reply to message #1857696] Mon, 27 February 2023 09:57 Go to previous messageGo to next message
Ulrich Cech is currently offline Ulrich CechFriend
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
Re: Find managed entities in PersistenceContext [message #1857915 is a reply to message #1857811] Mon, 06 March 2023 15:52 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris DelahuntFriend
Messages: 54
Registered: December 2021
Member
I don't know how EJB1 and EJB2 aren't using the same persistence context/EntityManager instance - it should tie the EntityManager resource to the context just like it does the transaction. If it isn't, it is a configuration problem with your app and the container, or are these two EJBs considered separate applications?
If they cannot be merged into one single persistence unit, your options then are to treat them as completely separate applicationsusing the same transaction and force flushing to make changes visible between them. You can certainly also use the EntityManager of one in the other and look things up directly, but if you can do that, I'd just pass the shared object between the processes, or designate one to do the lookup/persist if it isn't there, not both.

Best Regards,
Chris
Re: Find managed entities in PersistenceContext [message #1858025 is a reply to message #1857915] Sat, 11 March 2023 16:32 Go to previous message
Ulrich Cech is currently offline Ulrich CechFriend
Messages: 8
Registered: February 2018
Junior Member
Hi Chris,

<I don't know how EJB1 and EJB2 aren't using the same persistence context/EntityManager instance>
The both EJBs participate in the same persistence context, so they do definitely share the same EntityManager, that's why the option with "reading the managed entities in the context" work at all. The thing I found interesting is, that "entityManager.merge()" method-calls are somewhat consolidated and result into ONE SQL-Update-statement, where a call to entityManager.persist() results here in TWO SQL-Insert statements.

Best regards
Ulrich
Previous Topic:Migration from Eclipselink 3.0.2 to 4.0.0
Next Topic:Transactions not working in junit with eclipselink
Goto Forum:
  


Current Time: Mon Apr 29 12:13:45 GMT 2024

Powered by FUDForum. Page generated in 0.03792 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top