Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINERACT-2022: Rewrite account domain, services and jobs using QueryDSL. #3854

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from

Conversation

mariiaKraievska
Copy link
Contributor

@mariiaKraievska mariiaKraievska commented Apr 8, 2024

Description

JIRA ticket - https://issues.apache.org/jira/browse/FINERACT-2022

In this PR, I implemented QueryDSL to generate code for type-safe SQL queries for the Account entity.
I used QueryDSL for complex JPA queries, removed all custom "@query" annotations from repository classes, and refused to use JdbcTemplate.

I'd like to explain the reason for using the @Transactional(propagation = Propagation.REQUIRES_NEW) annotation for methods that use QueryDSL for "select" queries.

Previously, using JdbcTemplate to work with the database, we did not catch errors related to optimistic locking. This was due to the fact that JdbcTemplate works directly with SQL and does not take into account the optimistic locking mechanisms built into JPA. But they were still present and it could be seen in the logs.

After switching to QueryDSL, we started to face OptimisticLockException on read operations. This indicated that we had concurrent changes in the database and these changes were being checked at the time of the read requests, which are now performed via JPA. To avoid such cases and ensure data consistency, I applied the REQUIRES_NEW annotation. This allows each read request to be executed in a new transaction, isolating it from other operations that may be modifying the data at the same time.

Please let me know if you have any questions or suggestions.

Checklist

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Write the commit message as per https://github.com/apache/fineract/#pull-requests

  • Acknowledge that we will not review PRs that are not passing the build ("green") - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.

  • Create/update unit or integration tests for verifying the changes made.

  • Follow coding conventions at https://cwiki.apache.org/confluence/display/FINERACT/Coding+Conventions.

  • Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes

  • Submission is not a "code dump". (Large changes can be made "in repository" via a branch. Ask on the developer mailing list for guidance, if required.)

FYI our guidelines for code reviews are at https://cwiki.apache.org/confluence/display/FINERACT/Code+Review+Guide.

…s-account-services

# Conflicts:
#	build.gradle
#	fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
#	fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
#	fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
#	fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
#	integration-tests/dependencies.gradle
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
#	integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
#	oauth2-tests/dependencies.gradle
#	twofactor-tests/dependencies.gradle
@vidakovic vidakovic self-requested a review April 9, 2024 07:26
@vidakovic
Copy link
Contributor

@mariiaKraievska ... I'll check it out as soon as possible... will ping you... FYI

@@ -113,8 +120,12 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());

if (transferCompleted) {
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
final JPAQueryFactory queryFactory = new JPAQueryFactory(this.entityManager);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this whole logic into an AccountTransferStandingInstruction repository. We can change our repository interfaces to an impl where queryDSL is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

newHistory.setErrorLog(String.valueOf(errorLog));

entityManager.persist(newHistory);
entityManager.flush();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think we need explicit flush.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You right, done

updateQuery.append("'").append(errorLog).append("')");
jdbcTemplate.update(updateQuery.toString());

final AccountTransferStandingInstructionsHistory newHistory = new AccountTransferStandingInstructionsHistory();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this whole logic into a repository

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


@Transactional(propagation = Propagation.REQUIRES_NEW)
Copy link
Contributor

@adamsaghy adamsaghy Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont really understand the new transaction for reading... I believe you are referring to Optimistic read locking but I am afraid you are failing to sort the explained problem by this.

If I understand the problem you are explaining in the description of the PR is the following:
Transaction isolation= READ COMMITTED
TX1 started

  • Read Account (by name) -> Account (id=1, name=Adam, version=1, ref=1234)
    TX2 started
  • Read Account (by id) -> Account (id=1, name=Adam, version=1, ref=1234)
  • Modify Account name -> Account (id=1, name=Josh, version=2, ref=3456)
    TX2 ended
    TX1:
  • Read Accounts -> [Account(id=1, name=Josh, version=2)] -> JPA realize with the same ID a modified entry it has in its persistence context, so it is not in sync with the DB anymore and trigger Optimistic lock!

Imagine if you are reading the entity in new transactions when the read completed, the read entity became detached and removed from the persistence context and the JPA optimistic locking would only be triggered if this detached entity to be written back to the DB, but not when it was read again...

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comments and concerns regarding the use of @transactional with Propagation.REQUIRES_NEW for read-only operations. Initially, this approach was adopted to manage specific issues with OptimisticLockException observed when using QueryDSL instead of JdbcTemplate. However, improvements made in the handling of OptimisticLockException by the recent updates #3761 have resolved these issues effectively. As a result, the additional transactional annotation is no longer necessary, and our tests confirm that the system performs well without it. I appreciate your input and agree that adhering to these updated practices ensures cleaner and more efficient code.

}
} catch (final EmptyResultDataAccessException e) {
log.debug("Linking account is not configured");
final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to have for this a Repository

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@adamsaghy adamsaghy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am really happy to see we are moving away from building queries "manually" toward QueryDSL!!

In the other hand please take a look on my review comments and let me know about your thoughts!

@vidakovic
Copy link
Contributor

Just a quick note... QueryDSL (the project) seems to have hit a bit of a slow down due to members occupied with work and studies... the good news: it seems that the people from OpenFeign jumped in and already made quite some significant improvements. Also: there is also a better way to generate the metadata sources with the Infobip extensions.

I have contributed some improvements to both projects and hope they get accepted. The one for the Infobip extensions is a fairly small one, just trying to convince them to switch from com.querydsl:* to io.github.openfeign.querydsl:*; package names etc. are all the same.

The other PR on OpenFeign's QueryDSL fork is not relevant for what we need, just supporting a newer geo library (JTS) and the latest Postgis driver extensions.

The PR that would be interesting for us is the Infobip one (replacing com.querydsl:* to io.github.openfeign.querydsl:*).

FYI

infobip/infobip-spring-data-querydsl#101
OpenFeign/querydsl#416

…-sql-queries-account-services

# Conflicts:
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java
#	fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java
@adamsaghy
Copy link
Contributor

adamsaghy commented May 7, 2024

@mariiaKraievska Please kindly check @vidakovic recommendation and resolve the conflicting files and squash the commits! Thank you! ;)

@adamsaghy
Copy link
Contributor

@mariiaKraievska Kindly asking you to squash your commits and resolve the conflicts. Feel free to reach out if you are facing any issues with that.

@mariiaKraievska
Copy link
Contributor Author

@adamsaghy I'm sorry for the delay. (I thought I could quickly deal with the problem and then write back, but no).
I'm a bit stuck with these two tests right now:
ClientLoanIntegrationTest - testLoanCharges_INSTALLATION_FEE.
SchedulerJobsTestResults - testInterestTransferForSavings.

They started to fail for me after this commit c267bfd, they do not pass without an annotation @transactional(propagation = Propagation.REQUIRES_NEW)
The first one does not pass without this annotation on the fetchPostInterestTransactionIds method in the AccountTransfersReadPlatformServiceImpl class.
And the second without it on the retrieveLoanLinkedAssociation method in the AccountAssociationsCustomRepositoryImpl class.

I will work further and try to solve this ASAP.

@borikas
Copy link
Contributor

borikas commented Jun 4, 2024

@adamsaghy I'm sorry for the delay. (I thought I could quickly deal with the problem and then write back, but no). I'm a bit stuck with these two tests right now: ClientLoanIntegrationTest - testLoanCharges_INSTALLATION_FEE. SchedulerJobsTestResults - testInterestTransferForSavings.

They started to fail for me after this commit c267bfd, they do not pass without an annotation @transactional(propagation = Propagation.REQUIRES_NEW) The first one does not pass without this annotation on the fetchPostInterestTransactionIds method in the AccountTransfersReadPlatformServiceImpl class. And the second without it on the retrieveLoanLinkedAssociation method in the AccountAssociationsCustomRepositoryImpl class.

I will work further and try to solve this ASAP.

@adamsaghy It looks like we have a problem with caching sql queries when migrating from jdbcTemplate to queryDSL, because queryDSL uses EntityManager Cache, at the same time as jdbcTemplate uses Spring Cache.

@adamsaghy
Copy link
Contributor

@adamsaghy I'm sorry for the delay. (I thought I could quickly deal with the problem and then write back, but no). I'm a bit stuck with these two tests right now: ClientLoanIntegrationTest - testLoanCharges_INSTALLATION_FEE. SchedulerJobsTestResults - testInterestTransferForSavings.
They started to fail for me after this commit c267bfd, they do not pass without an annotation @transactional(propagation = Propagation.REQUIRES_NEW) The first one does not pass without this annotation on the fetchPostInterestTransactionIds method in the AccountTransfersReadPlatformServiceImpl class. And the second without it on the retrieveLoanLinkedAssociation method in the AccountAssociationsCustomRepositoryImpl class.
I will work further and try to solve this ASAP.

@adamsaghy It looks like we have a problem with caching sql queries when migrating from jdbcTemplate to queryDSL, because queryDSL uses EntityManager Cache, at the same time as jdbcTemplate uses Spring Cache.

I might not yet fully understand the issue. Are we talking about the prepared statement cache? Or the entity cache, rather when something was already fetched, it does not go to the database to fetch again?

@borikas
Copy link
Contributor

borikas commented Jun 4, 2024

@adamsaghy I'm sorry for the delay. (I thought I could quickly deal with the problem and then write back, but no). I'm a bit stuck with these two tests right now: ClientLoanIntegrationTest - testLoanCharges_INSTALLATION_FEE. SchedulerJobsTestResults - testInterestTransferForSavings.
They started to fail for me after this commit c267bfd, they do not pass without an annotation @transactional(propagation = Propagation.REQUIRES_NEW) The first one does not pass without this annotation on the fetchPostInterestTransactionIds method in the AccountTransfersReadPlatformServiceImpl class. And the second without it on the retrieveLoanLinkedAssociation method in the AccountAssociationsCustomRepositoryImpl class.
I will work further and try to solve this ASAP.

@adamsaghy It looks like we have a problem with caching sql queries when migrating from jdbcTemplate to queryDSL, because queryDSL uses EntityManager Cache, at the same time as jdbcTemplate uses Spring Cache.

I might not yet fully understand the issue. Are we talking about the prepared statement cache? Or the entity cache, rather when something was already fetched, it does not go to the database to fetch again?

This problem is similar to an incorrect sequence of sql queries within the same transaction
Снимок экрана 2024-06-04 в 15 21 38
Снимок экрана 2024-06-04 в 15 21 58
Снимок экрана 2024-06-04 в 15 24 22

@mariiaKraievska
Copy link
Contributor Author

@adamsaghy Related to the problem of having in the same transaction queries that use jdbcTemplate and new, rewritten queries that use QueryDSL'. I propose to go the way of rewriting within transactions or as now within modules (as in this PR account domain), but duplicating methods related to other modules, written with jdbcTemplate, rewriting them with QueryDSL`. Or the third option, which I am inclined to and have already started working on, is to take another module, more independent from other modules, and present PR on its example. What do you think?

@adamsaghy
Copy link
Contributor

adamsaghy commented Jun 17, 2024

@adamsaghy Related to the problem of having in the same transaction queries that use jdbcTemplate and new, rewritten queries that use QueryDSL'. I propose to go the way of rewriting within transactions or as now within modules (as in this PR account domain), but duplicating methods related to other modules, written with jdbcTemplate, rewriting them with QueryDSL`. Or the third option, which I am inclined to and have already started working on, is to take another module, more independent from other modules, and present PR on its example. What do you think?

I like the idea to do it within module boundaries in this case. I wont mind small steps towards the final goal ;)

@vidakovic Thoughts?

@vidakovic
Copy link
Contributor

@mariiaKraievska ... maybe some inspiration: https://github.com/vidakovic/fineract/tree/feature/APACHE_EU_2024/custom/apachecon/note ... if you want we can talk this through

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants