Set, Refresh, Commit

Hello all,

Sorry for my silly question but maybe someone can help me to clarify this issue.

Every time when I open a screen (Create or Update), CUBA loads detached data from the database to a screen’s data container. Detached means that there is no live connection to the database.
If I make any change to DataBase (for instance a query filter), regardless if the query is made in the screen or in the middle-tier (service), I need to call Refresh (dataloaderDl.load() or getScreenData.loadAll() if there are many loaders).

If is so, why when I make a (Set) change at screen level (Transaction Browse) is not necessary to call Refresh (see below) (or Commit())? Set() is enough in order to save the change in the Database .

@Subscribe("setBtn")
    public void onSetBtnClick(Button.ClickEvent event) {
      Set<Transaction> transactions = transactionsTable.getSelected();
        for (Transaction a : transactions) {
                if (logic) {
                a.setStatus(TrStatusEnum.INVOICED);
                }
          }
    }

On the other hand is not very clear when should I use commit(). I saw that is used very often at middle-tier level. But in the middle-tier the entity is not (state) managed ? The changes should not be taken without commit()?

Please advise!
-n

Any news here?
BR,
-n

Hi,

Could you create a separate example for this behavior please? Just a small application that performs the same set of actions on an isolated entity. Because it’s quite hard to understand the problem without a proper context. I might try to guess, but it would be easier to explain on your example.

The problem is that CUBA (in contrast with Spring) comes with this datasource/container concept.
Now is pretty clear that as long as an Entity is part of datacontainer is not necessary to commit separately. It detects the change and knows to commit it when screen is closed.

Thank you,
-n

This behavior depends on a screen type, data context management, etc., that’s why I asked for a sample.

BTW, considering your example: it looks like you’re trying to add business logic to a screen. I’d suggest moving your transaction update logic to a service and invoke the service from a screen. E.g. your screen controller code might look like this:

    @Subscribe("setBtn")
    public void onSetBtnClick(Button.ClickEvent event) {
      Set<Transaction> transactions = transactionsTable.getSelected();
      transactionsService.updateTransactions(transactions);
      transactionsDl.load();  
    }

And there is a transactions service class that performs the logic

        for (Transaction a : transactions) {
                if (logic) {
                a.setStatus(TrStatusEnum.INVOICED);
                }
          }

It will help you to reuse the logic and separates data processing from the data display.

Can you give me please one (two) example related to screen type and data context management (when is/is not mandatory to commit())? For instance in my example, I suppose the collection of transactions are already loaded in the data container (browser) - (as detached entities) and is not necessary to commit them. The set() is enough.

This is very good advice but tell me please why if I use the logic in Screen Controller the refresh is not necessary and when I use the same logic like a Service the refresh ( transactionsDl.load(); ) is mandatory?

Best Regards,
-n

One thing that’s clear from all of your posts is that you don’t “get” the CUBA Framework at a very foundational level. CUBA is what’s known as an “Opinionated Framework.” In general, you need to do things the “CUBA way” or… you end up making several dozen posts on a support forum. :wink:

I would suggest you go through the tutorials, and see how CUBA expects things to be done. A great many things can be done just with nearly 100% auto-generated code. The rest, there is almost always a “CUBA way” to do it, that doesn’t involve all this complexity you are fighting with.

Thank you Jon. This discussion helps me to appropriate “Cuba way”. Please stay in the subject otherwise “thanks but no thanks”.

If we’re talking generally, as an example, you can load data from a 3rd party service using XML and then send them to another service via REST. In this case, you may need to implement your own data management processes by overriding the standard load and commit processes.

You can invoke an editor screen in the read-only mode, so no data changes are allowed, commit is not mandatory on a screen close.

There are a lot of cases, that’s why a simple example for a particular case costs a hundred words :slight_smile:

In my example, the service works in the core module and does not return changed entities to the screen. Therefore, you need to reload changed data.

Remember, that core and web are effectively two different applications even if they run in the same application server. Therefore, if you send data to the service from the screen, it is serialized in web and restored in core. So web does not ‘know’ what is going on in the core services. It means that you need either return changed instances and put them to the data container or reload them.

Your explanation is very accurate and now I understand.
One single point: why in my case is not necessary commit and set is enough to save the changes in database? It’s about a browser screen not a editor screen in read-only mode…

Please send me a small concise example. For me it would be much easier to explain the case using your code.

But I already did (setStatus is enough to save the changes in the database):

public class TransactionBrowse extends StandardLookup<Transaction> {
@Subscribe("setBtn")
    public void onSetBtnClick(Button.ClickEvent event) {
      Set<Transaction> transactions = transactionsTable.getSelected();
        for (Transaction a : transactions) {
                if (logic) {
                a.setStatus(TrStatusEnum.INVOICED);
                }
          }
    }
}

I mean that I need a minimal CUBA project that shows the case. I don’t need all your code, just create another application that shows this exact case.

BTW, you can use zipProject task to create an archive of the project sources.

I think is pretty obvious. When I click a button I need to change some fields (attributes) according with another attributes presence/absence. This code works perfectly and I wasted my time trying to commit (as well)…
Additional commit actually perform a random saving behavior with errors.
image
Actually I have several if(s) and several set()s…commit()s.

 if (logic1) {
                a.setStatus(TrStatusEnum.INVOICED);
                //dataManager.commit(a);
                }
 if (logic2) {
                a.setStatus(TrStatusEnum.NONE);
                 //dataManager.commit(a);
                }

Hi,

I tried to reproduce your example, see the application attached (89.2 KB) Thanks to your proper questions, we’ve found an issue in the framework and we’ll be fixing it soon.

As for the application. I’ve created a similar code and we definitely need to commit data in the screen after updating it programmatically. I just don’t understand how you get your data committed automatically, that’s why I asked you or the code.

In my case, the code

    @Subscribe("transactionsTable.process")
    public void onTransactionsTableProcess(Action.ActionPerformedEvent event) {
        Set<Transaction> transactions = transactionsTable.getSelected();
        transactions.forEach(t -> {
            if (t.getStatus() == TrStatusEnum.PROCESSING) {
                t.setStatus(TrStatusEnum.INVOICED);
            }
        });
        dataContext.commit();
    }

requires final commit. Without it, data won’t be updated in the database, only in the screen. You can try it by yourself, commenting out the dataContext.commit(); line, updating data and then refreshing it.

Mind the bug while experimenting, do not forget refreshing data before processing transactions.

Hello Andrey,
Thank you for your example.
Finally I found out where the difference comes from!
In my Transaction Browser screen I want also to be able to modify a Date:
<column id="myDate" editable="true" />
and I did it in this (wrong!?) way:

    @Subscribe
    public void onInit(InitEvent event) {
     transactionsDc.addItemPropertyChangeListener(e -> {
            Transaction tr = e.getItem();
            dataManager.commit(tr);
 });

When I press the “Process” button addItemPropertyChangeListener is triggered (I’m not sure why) and commit the (whole) transaction (the status changes included).
If there are many logics in the same screen It’s quite difficult to manage the commit()s. Maybe you suggest a guideline.
Related to the bug, good to know…however maybe explain a little bit relation between (Cuba) data container and (Spring) data context.

Best Regards,
-n

Hi,

That’s why I asked you for an example :slight_smile:

You did it right, using data manager to commit entities is a perfectly legal approach. But you trigger the property change listener on any property change, that’s why it runs during transaction status update. In the listener, you can use a condition like this: if ("date".equals(e.getProperty())) {/*commit data*/} to run the code only on a particular property change.

There is no spring data context in CUBA screen tough, only CUBA data context. You can read more about screen data components here. I thought that our documentation explain relations pretty clear.

And again, think about logic separation to avoid issues with transactions. It is recommended screens to be responsible for display and manual data editing only. It might be better to extract all programmatic data processing (a.k.a. business logic) to services in the core module.

Hi,
Three cheers for you, things are clearer now.
One more question regarding business separation please. Usually for this I use 1. Services (from Middleware) that can be injected in the screens controllers (through their interface).
In this respect I didn’t understand very well what Beans (@Component) are for? Seems that can help to decouple further the business logic and 2. inject their interface (or @TransactionalEventListener) into the Service(s). However Beans seems can be used also in 3. the Screens (package) and inject directly in the screens controller.

What is the best (approach) from these 3 scenarios?

Best Regards,
-n

Hi,

Think of Beans as of Spring managed utility classes that run only in one module (core or web) in contrast to Services that provide an API interface that can be used in both modules.

So, if you declare a Bean in the core module, it won’t be available in web and vice versa.

Hi,

Ok.Good.

Back to decouple logic via Service I think something is missing.

Service(StatusService.NAME)
public class StatusServiceBean implements StatusService {
    public void updateTransactions(Set<Transaction> transactions){
      transactions.forEach(a -> {
       if (t.getStatus() == TrStatusEnum.PROCESSING) {
                t.setStatus(TrStatusEnum.INVOICED);
            }
        });

and in Controller

@Subscribe("transactionsTable.process")
    public void onTransactionsTableProcess(Action.ActionPerformedEvent event) {
        Set<Transaction> transactions = transactionsTable.getSelected();
        statusService.updateTransactions(transactions);
        transactionsDl.load();
    }

do nothing…I think the code from ServiceBean should be inside of a Transaction…