Seems my EntityManager lost data occasionally

Hi,
I am using cuba6.10, here is my case:
I have a screen and with it’s datasource, and a @Component with some other DB change logic(the other DB changes has no DB relationship to the datasource in screen).

For some user action, I have datasource changes and also call the @Component to perform other DB change。

In general , all works fine. But occasionally, I found that the change which in @Component does not happen(did not commit to database), but I did not find in which process flow that no db change will happens in @Component

And recently,in two days, there are around 260 that actions performed, but about 250 has no DB change done in @Component, and other db changes of the screen datasource works fine. I also did not see any other error/warnings in logfile.

Do you have such experience or could you advice how can I debug/analyze this issue, please?

Below is the main code:

//screen controller
//other datasource operation here 
// then, new an entity, and pass it to a service
        CertSignLog certSignLog = (CertSignLog) metadata.create("cip$CertSignLog");
        certSignLog.setContent(content);

        certSignLogService.saveEntity(certSignLog);
//other datasource operation here 
//in the service , call the component, and in componet, commit db changes:
    public void saveEntity(CertSignLog entity) {
        try (Transaction tx = persistence.getTransaction()) {
            EntityManager em = persistence.getEntityManager();
            em.persist(entity);
            tx.commit();
        } catch (Exception e) {
            throw e;
        }
    }

Hi Anjingjing,

Firstly, a few comments not directly related to possible causes:

  1. Don’t use EntityManager until needed. Your code could be simpler and less error prone with DataManager:

     CertSignLog certSignLog = (CertSignLog) metadata.create("cip$CertSignLog");
     certSignLog.setContent(content);
     // right in the screen controller, or in a service but without any transaction management
     dataManager.commit(certSignLog);
    
  2. When using programmatic transaction management with EntityManager, use createTransaction() in try-with-resources block or use lambda:

     public void saveEntity(CertSignLog entity) {
         // use createTransaction() to always create new transaction
         try (Transaction tx = persistence.createTransaction()) {
             EntityManager em = persistence.getEntityManager();
             em.persist(entity);
             tx.commit();
         }
     }
    
     // or
    
     public void saveEntity(CertSignLog entity) {
         persistence.runInTransaction(em -> {
             em.persist(entity);
         });
     }
    

As for a possible cause of your problem, try to add logging to your “save” method or just use DataManager which outputs debug messages on saving entities.

Thanks Konstantin.
I also curious about why using EntityManager there, I will modify it to DataManager.

Hi Konstantin,
One more thing about this topic, similar “data lost” behavior also happened to AccessToken。
I am using cuba6.10, and have StoreTokensInDb set to true。

When the “data lost” happens, I noticed that in same time, sys$AccessToken did not stored the token。
The store token to db code is also using EntityManager+transaction, but without tx.end().
I guess some of my code leads some bad result after long running but I did not figure out yet, just keep you posted about this topic.

Every time when cuba before insert AccessToken , it try to delete it first.
From the logfile, the delete sql executed, but the insert sql right after it not executed, no error no warning.

BTW, maybe I should always use persistence.createTransaction() rather than persistence.getTransaction() for my own standalone logic, right?

    protected void storeAccessTokenToDatabase(String tokenValue,
                                              byte[] accessTokenBytes,
                                              String authenticationKey,
                                              byte[] authenticationBytes,
                                              Date tokenExpiry,
                                              String userLogin,
                                              @Nullable Locale locale,
                                              @Nullable String refreshTokenValue) {
        try (Transaction tx = persistence.getTransaction()) {
            EntityManager em = persistence.getEntityManager();
            AccessToken accessToken = metadata.create(AccessToken.class);
            accessToken.setCreateTs(timeSource.currentTimestamp());
            accessToken.setTokenValue(tokenValue);
            accessToken.setTokenBytes(accessTokenBytes);
            accessToken.setAuthenticationKey(authenticationKey);
            accessToken.setAuthenticationBytes(authenticationBytes);
            accessToken.setExpiry(tokenExpiry);
            accessToken.setUserLogin(userLogin);
            accessToken.setLocale(locale != null ? locale.toString() : null);
            accessToken.setRefreshTokenValue(refreshTokenValue);
            em.persist(accessToken);
            tx.commit();
        }
    }

Hi,
If you use persistence.getTransaction() - then the fact whether this data will be committed to database or lost - will depend on the outer code from which you call storeAccessTokenToDatabase() method. Because this method takes outer transaction from calling code.

So general rule #1 - don’t use persistence.getTransaction() unless you really need it and understand what it does.

maybe I should always use persistence.createTransaction() rather than persistence.getTransaction() for my own standalone logic, right?

Again, general rule #2, by Konstantin:

Manual transaction control is usually necessary only if one programs a complex long transaction where several business-logically or constraint-related entities MUST be committed to the database in the same transaction.
And even in this case DataManager can be used, like:

dataManager.commit(user, role, userRole1, userRole2);

In modern versions of CUBA the preferred way to work with data is using a DataManager, TransactionalDataManager, EntityChangedEvent.

There are cases when EntityManager is still needed, like hard-deleting many entitites with a JPQL in database without loading entities to java heap. But the common rule is that DataManager should be used everywhere if it the task can be accomplished by using a DataManager.

Quite clear! Thanks~

One more question, for DataManager. If in one function, I have:

dataManager.commit(user);
dataManager.commit(userRole1);

That will result in two transactions, right?

Yes, if you call it this way - it will result in two transactions.

Understood, thanks~

After modify all code as suggested, problems not happened this month. Seems it solved.

Problem should be caused by abuse of persistence.getTransaction()

1 Like