FINERACT-2181: Batch API - Fix read-only connection handling
This commit is contained in:
committed by
Arnold Gálovics
parent
0752106742
commit
bc4b3555f6
+16
-6
@@ -58,6 +58,8 @@ import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
|||||||
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
|
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
|
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
||||||
|
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.ConcurrencyFailureException;
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
import org.springframework.dao.NonTransientDataAccessException;
|
import org.springframework.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
@@ -74,9 +76,9 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||||||
* CommandStrategy from CommandStrategyProvider.
|
* CommandStrategy from CommandStrategyProvider.
|
||||||
*
|
*
|
||||||
* @author Rishabh Shukla
|
* @author Rishabh Shukla
|
||||||
* @see org.apache.fineract.batch.domain.BatchRequest
|
* @see BatchRequest
|
||||||
* @see org.apache.fineract.batch.domain.BatchResponse
|
* @see BatchResponse
|
||||||
* @see org.apache.fineract.batch.command.CommandStrategyProvider
|
* @see CommandStrategyProvider
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -85,7 +87,6 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
|
|
||||||
private final CommandStrategyProvider strategyProvider;
|
private final CommandStrategyProvider strategyProvider;
|
||||||
private final ResolutionHelper resolutionHelper;
|
private final ResolutionHelper resolutionHelper;
|
||||||
private final PlatformTransactionManager transactionManager;
|
|
||||||
private final ErrorHandler errorHandler;
|
private final ErrorHandler errorHandler;
|
||||||
|
|
||||||
private final List<BatchFilter> batchFilters;
|
private final List<BatchFilter> batchFilters;
|
||||||
@@ -94,6 +95,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
|
|
||||||
private final RetryConfigurationAssembler retryConfigurationAssembler;
|
private final RetryConfigurationAssembler retryConfigurationAssembler;
|
||||||
|
|
||||||
|
private PlatformTransactionManager transactionManager;
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,6 +154,9 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
try {
|
try {
|
||||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||||
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
|
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
|
||||||
|
if (transactionManager instanceof ExtendedJpaTransactionManager extendedJpaTransactionManager) {
|
||||||
|
transactionTemplate.setReadOnly(extendedJpaTransactionManager.isReadOnlyConnection());
|
||||||
|
}
|
||||||
transactionConfigurator.accept(transactionTemplate);
|
transactionConfigurator.accept(transactionTemplate);
|
||||||
return transactionTemplate.execute(status -> {
|
return transactionTemplate.execute(status -> {
|
||||||
BatchRequestContextHolder.setEnclosingTransaction(status);
|
BatchRequestContextHolder.setEnclosingTransaction(status);
|
||||||
@@ -175,8 +180,8 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the response list by getting a proper {@link org.apache.fineract.batch.command.CommandStrategy}.
|
* Returns the response list by getting a proper {@link CommandStrategy}. execute() method of acquired
|
||||||
* execute() method of acquired commandStrategy is then provided with the separate Request.
|
* commandStrategy is then provided with the separate Request.
|
||||||
*
|
*
|
||||||
* @param requestList
|
* @param requestList
|
||||||
* @param uriInfo
|
* @param uriInfo
|
||||||
@@ -395,4 +400,9 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
public void setEntityManager(EntityManager entityManager) {
|
public void setEntityManager(EntityManager entityManager) {
|
||||||
this.entityManager = entityManager;
|
this.entityManager = entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-4
@@ -19,11 +19,14 @@
|
|||||||
package org.apache.fineract.batch.service;
|
package org.apache.fineract.batch.service;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -51,6 +54,8 @@ import org.junit.jupiter.api.AfterEach;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@@ -86,8 +91,8 @@ class BatchApiServiceImplTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private RetryConfigurationAssembler retryConfigurationAssembler;
|
private RetryConfigurationAssembler retryConfigurationAssembler;
|
||||||
|
|
||||||
private final ResolutionHelper resolutionHelper = Mockito.spy(new ResolutionHelper(new FromJsonHelper()));
|
private final ResolutionHelper resolutionHelper = spy(new ResolutionHelper(new FromJsonHelper()));
|
||||||
private final List<BatchRequestPreprocessor> batchPreprocessors = Mockito.spy(List.of());
|
private final List<BatchRequestPreprocessor> batchPreprocessors = spy(List.of());
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private BatchApiServiceImpl batchApiService;
|
private BatchApiServiceImpl batchApiService;
|
||||||
@@ -96,8 +101,9 @@ class BatchApiServiceImplTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, transactionManager, errorHandler, List.of(),
|
batchApiService = new BatchApiServiceImpl(strategyProvider, resolutionHelper, errorHandler, List.of(), batchPreprocessors,
|
||||||
batchPreprocessors, retryConfigurationAssembler);
|
retryConfigurationAssembler);
|
||||||
|
batchApiService.setTransactionManager(transactionManager);
|
||||||
batchApiService.setEntityManager(entityManager);
|
batchApiService.setEntityManager(entityManager);
|
||||||
request = new BatchRequest();
|
request = new BatchRequest();
|
||||||
request.setRequestId(1L);
|
request.setRequestId(1L);
|
||||||
@@ -227,6 +233,43 @@ class BatchApiServiceImplTest {
|
|||||||
Mockito.verifyNoInteractions(entityManager);
|
Mockito.verifyNoInteractions(entityManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(booleans = { true, false })
|
||||||
|
void testCallInTransactionReadOnlyFlag(boolean isReadOnly) {
|
||||||
|
// Given
|
||||||
|
ExtendedJpaTransactionManager extendedJpaTransactionManager = mock(ExtendedJpaTransactionManager.class);
|
||||||
|
|
||||||
|
// Create a transaction status with the correct read-only flag
|
||||||
|
DefaultTransactionStatus transactionStatus = new DefaultTransactionStatus("txn_name", null, true, true, false, isReadOnly, false,
|
||||||
|
null);
|
||||||
|
|
||||||
|
// Mock getTransaction to return our status when the read-only flag matches
|
||||||
|
when(extendedJpaTransactionManager.isReadOnlyConnection()).thenReturn(isReadOnly);
|
||||||
|
when(extendedJpaTransactionManager
|
||||||
|
.getTransaction(argThat(definition -> definition != null && definition.isReadOnly() == isReadOnly)))
|
||||||
|
.thenReturn(transactionStatus);
|
||||||
|
|
||||||
|
// Mock other required dependencies
|
||||||
|
when(strategyProvider.getCommandStrategy(any())).thenReturn(commandStrategy);
|
||||||
|
when(commandStrategy.execute(any(), any())).thenReturn(response);
|
||||||
|
|
||||||
|
batchApiService.setTransactionManager(extendedJpaTransactionManager);
|
||||||
|
|
||||||
|
// Set up a request that will trigger the read-only behavior we want to test
|
||||||
|
BatchRequest testRequest = new BatchRequest();
|
||||||
|
testRequest.setRequestId(1L);
|
||||||
|
testRequest.setMethod(isReadOnly ? "GET" : "POST"); // Use GET for read-only, POST for read-write
|
||||||
|
testRequest.setRelativeUrl("/test/endpoint");
|
||||||
|
|
||||||
|
// When
|
||||||
|
List<BatchResponse> responses = batchApiService.handleBatchRequestsWithEnclosingTransaction(List.of(testRequest), uriInfo);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertFalse(responses.isEmpty());
|
||||||
|
verify(extendedJpaTransactionManager)
|
||||||
|
.getTransaction(argThat(definition -> definition != null && definition.isReadOnly() == isReadOnly));
|
||||||
|
}
|
||||||
|
|
||||||
private static final class RetryException extends RuntimeException {}
|
private static final class RetryException extends RuntimeException {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user