Can't switch AbstractRoutingDataSource without using #Transactional - java

I have a Spring-Boot project. As mentioned above, I want to use AbstractRoutingDataSource to switch DataSouce. I don't want to use #Transactional since no transactions are needed.
But when I transfer my method, it seems like which the #Transactional is being used, but when I test my method in unit test, it runs ok, I think it's strange because in unit-test, #Transactional shouldn't work.
public class DynamicDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return DynamicContextHolder.peek();
}
}
The controller method
#RequestMapping(value = "/pipeline/analyze/{systemId}/{dbName}/{dbSchemaName}/{dbTableName}", method = RequestMethod.GET)
public String pipelineAnalyze(#PathVariable("systemId") Long systemId, #PathVariable("dbName") String dbName, #PathVariable("dbSchemaName") String dbSchemaName, #PathVariable("dbTableName") String dbTableName) throws IOException {
ETLServerAccessEntityInterface etlServerAccessEntity = etlServer.getETLServerAccessEntity();
/* LOGGER.info("===etlServerAccessEntity.getAccessToken()===");
LOGGER.info(etlServerAccessEntity.getAccessToken());
LOGGER.info("isAlive=" + etlServer.isAlive());*/
TableInfoMasterEntity tableInfoMaster = new TableInfoMasterEntity();
TableInfoMasterEntityPk tableInfoMasterPk = new TableInfoMasterEntityPk();
tableInfoMasterPk.setSystemId(systemId);
tableInfoMasterPk.setDbName(dbName);
tableInfoMasterPk.setDbSchemaName(dbSchemaName);
tableInfoMasterPk.setDbTableName(dbTableName);
tableInfoMaster.setId(tableInfoMasterPk);
Example<TableInfoMasterEntity> exampleTableInfoMaster = Example.of(tableInfoMaster);
List<TableInfoMasterEntity> filteredTableInfoMaster = tableInfoMasterService.findAll(exampleTableInfoMaster);
System.out.println("filteredTableInfoMaster.size()=" + filteredTableInfoMaster.size());
TableInfoMasterEntity tableInfoMasterEntity = filteredTableInfoMaster.get(0);
pipelineRequirementAnalyzeService.analyze(tableInfoMasterEntity);
return String.valueOf(pipelineRequirementAnalyzeService.toString());
}
the unit test
#Test
public void findByTemplateNameContaining() throws IOException {
ETLServerAccessEntityInterface etlServerAccessEntity = etlServer.getETLServerAccessEntity();
TableInfoMasterEntity tableInfoMaster = new TableInfoMasterEntity();
TableInfoMasterEntityPk tableInfoMasterPk = new TableInfoMasterEntityPk();
tableInfoMasterPk.setSystemId(1);
tableInfoMasterPk.setDbName("DGRAMES");
tableInfoMasterPk.setDbSchemaName("MESSERIES");
tableInfoMasterPk.setDbTableName("TBLEMSACCESSORYSTATE_MAINTAIN");
tableInfoMaster.setId(tableInfoMasterPk);
Example<TableInfoMasterEntity> exampleTableInfoMaster = Example.of(tableInfoMaster);
List<TableInfoMasterEntity> filteredTableInfoMaster = tableInfoMasterService.findAll(exampleTableInfoMaster);
TableInfoMasterEntity tableInfoMasterEntity = filteredTableInfoMaster.get(0);
pipelineRequirementAnalyzeService.analyze(tableInfoMasterEntity);
System.out.println();
}
These two method are similar, they all transfers the pipelineRequirementAnalyzeService.analyze(tableInfoMasterEntity);
but the results are different. here is the failure result and the success result.

Related

Mock enhanced DynamoDbTable CRUD operations

How to mock software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable.getItem?
So far I have tried the below, which is throwing NullPointerException from inside the SDK.
Any idea how to mock the table CRUD operations?
#Mock private DynamoDbEnhancedClient enhdynamodb;
#Mock private DynamoDbClient dynamodb;
#Mock private DynamoDbTable<EventRecord> dyamodbTable;
#Mock private SecurityContext securityContext;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(securityContext.getUserPrincipal()).thenReturn(principal);
enhdynamodb = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamodb).build();
dyamodbTable = enhdynamodb.table(TABLE_NAME, TableSchema.fromBean(EventRecord.class));
service = new EventsService(tokenSerializer, enhdynamodb, configProvider, clock);
service.setSecurityContext(securityContext);
}
#Test
public void getEvent_null_notFound() {
String userId = UUID.randomUUID().toString();
String eventId = UUID.randomUUID().toString();
GetItemResponse response = GetItemResponse.builder().build();
EventRecord event = null;
when(principal.getName()).thenReturn(userId);
when(dyamodbTable.getItem(any(GetItemEnhancedRequest.class))).thenReturn(event);
assertThatThrownBy(() -> service.getEvent(eventId)).isInstanceOf(NotFoundApiException.class);
}
public Event getEvent(String eventId) {
log.info("Getting event {}", eventId);
EventRecord eventRecord = loadEvent(eventId);
return modelMapper.map(eventRecord, Event.class);
}
private EventRecord loadEvent(final String eventId) {
String userId = securityContext.getUserPrincipal().getName();
EventRecord event =
getTable()
.getItem(
GetItemEnhancedRequest.builder()
.consistentRead(Boolean.TRUE)
.key(k -> k.partitionValue(userId).sortValue(eventId).build())
.build());
if (event == null) {
throw new NotFoundApiException(
new NotFoundException()
.errorCode("EventNotFound")
.message(String.format("Event %s can not be found.", eventId)));
}
return event;
}
private DynamoDbTable<EventRecord> getTable() {
return dynamodb.table(tableName, TableSchema.fromBean(EventRecord.class));
}
I tried it like this and it does not throw exceptions.
#Test
public void getEvent_null_notFound() {
String userId = UUID.randomUUID().toString();
String eventId = UUID.randomUUID().toString();
DynamoDbTable dynamoDbTable = mock(DynamoDbTable.class);
EventRecord event = null;
when(dynamoDbTable.getItem(any(GetItemEnhancedRequest.class))).thenReturn(event);
assertEquals(event, dynamoDbTable.getItem(event));
}
Note that I mocking DynamoDbTable instead of DynamoDbEnhancedClient.
Mocking calls to the client and doing unit test on your own code is of course a good idea but I highly recommend using the local dynamodb library if you want to do an actual DyanmoDb calls with a local DB.
Here is full documentation. If you use this library in your unit tests you dont need to mock the calls.

JPARepository is not saving to DB

Long story, but I had to redesign an application this weekend. From a spring boot app to a spring batch app. The process was always a batch process, but I tried to make this batch engine and it got way too complex and i had to stop what I was doing. I'm sure we've all been there. Anyway everything is working fine!! Except for one piece of code that I tried to keep the original piece of code for. I'm trying to use a JPARepository save method and it's not working!! I am able to call the save method, I feel like the Repo is instantiated because I'm not getting a null pointer exception. In fact, I'm not getting any exceptions thrown. I am just not seeing anything in the DB. And I know this code has worked because I had it running in the previous design. Anyway here are my classes...
Data object:
#Data
#Entity
#Table(name="PAYEE_QUAL_LS")
public class PayeeList {
#EmbeddedId
private PayeeListPK payeeListPK = new PayeeListPK();
#Column(name = "PAYEE_QUAL_CD")
private String payeeQualCode;
#Column(name = "ETL_TS")
private Timestamp etlTimestamp;
}
Primary key data class...
#Data
#Embeddable
public class PayeeListPK implements Serializable {
#Column(name = "PAYEE_NM")
private String payeeName;
#Column(name = "BAT_PROC_DT")
private Date batchProcDate;
}
Repo class...
#Repository
public interface PayeeListRepo extends JpaRepository<PayeeList,String> {}
My Service class...
public class OracleService {
private static final Logger logger = LoggerFactory.getLogger(OracleService.class);
#Autowired
PayeeListRepo payeeListRepo;
public void loadToPayeeListTable(PayeeList payeeList) {
payeeListRepo.save(payeeList);
}
I have an implementation of Tasklet which I am calling from my batch Step...
public class PayeeListTableLoad implements Tasklet {
private static final Logger logger = LoggerFactory.getLogger(PayeeListTableLoad.class);
private java.sql.Date procDt;
private String inputFile;
private Timestamp time;
private int safeRecordCount = 0;
private int blockRecordCount = 0;
private int safeRejectRecordCount = 0;
private int blockRejectRecordCount = 0;
private ArrayList<String> rejectRecordList = new ArrayList<>();
#Autowired
OracleService oracleService;
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
java.util.Date parsed = format.parse(System.getenv("procDt"));
procDt = new java.sql.Date(parsed.getTime());
inputFile = Constants.filePath;
time = new Timestamp(System.currentTimeMillis());
logger.info("Running data quality checks on input file and loading to Oracle");
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
String line = reader.readLine();
while (line != null) {
if (dataQuality(line)) {
PayeeList payeeList = buildPayeeListObject(line);
oracleService.loadToPayeeListTable(payeeList);
logger.info("Record loaded: " + line);
} else {
rejectRecordList.add(line);
try {
if (line.split("\\|")[1].equals("B")) {
blockRejectRecordCount++;
} else if (line.split("\\|")[1].equals("S")) {
safeRejectRecordCount++;
}
logger.info("Record rejected: " + line);
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
line = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
logger.info("Safe record count is: " + safeRecordCount);
logger.info("Block record count is: " + blockRecordCount);
logger.info("Rejected records are: " + rejectRecordList);
SendEmail sendEmail = new SendEmail();
sendEmail.sendEmail(Constants.aegisCheckInclearingRecipient,Constants.aegisCheckInclearingSender,Constants.payeeListFileSuccessEmailSubject,Constants.payeeListFileSuccessEmailBodyBuilder(safeRecordCount,blockRecordCount,safeRejectRecordCount,blockRejectRecordCount,rejectRecordList));
logger.info("Successfully loaded to Oracle and sent out Email to stakeholders");
return null;
}
In my batch configuration....
#Bean
public OracleService oracleService() { return new OracleService(); }
#Bean
public PayeeListTableLoad payeeListTableLoad() {
return new PayeeListTableLoad();
}
#Bean
public Step payeeListLoadStep() {
return stepBuilderFactory.get("payeeListLoadStep")
.tasklet(payeeListTableLoad())
.build();
}
#Bean
public Job loadPositivePayFile(NotificationListener listener, Step positivePayLoadStep) {
return jobBuilderFactory.get("loadPositivePayFile")
.incrementer(new RunIdIncrementer())
.listener(listener)
.start(positivePayDataQualityStep())
.next(initialCleanUpStep())
.next(positivePayLoadStep)
.next(metadataTableLoadStep())
.next(cleanUpGOSStep())
.build();
}
Ultimately our step is running an implementation of Tasklet, we are Autowiring out OracleService class, and then that is being called and is then calling the Repo method. I am getting to the Oracle Service class method and I am calling the save method of my Autowired Repository but again nothing is happening!!
EDIT!!!
I have figured out another way to do it and that is with EntityManager and using the persist and flush methods. Below is now my loadToPayeeListTable method in my Oracle Service class...
public void loadToPayeeListTable(PayeeList payeeList) throws ParseException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
entityManager.persist(payeeList);
entityManager.flush();
transaction.commit();
entityManager.close();
}
Could you have a try to passe the repository with a Spring Test? I have never met this problem, but I am not sure about the DB type. Is it Mysql, Oracle? Because I never used it with #EmbeddedId.
IF you passed the unit test, you ought to check your service logic with debugging. Opposite, you ought to passe the test first.
Change your jpa repository to
#Repository
public interface PayeeListRepo extends JpaRepository<PayeeList, PayeeListPK>

How to write a query using only certain parts of an object with Spring JPA

I feel like this should be pretty straightforward, but I'm not sure about the actual code for it. Basically, I have my rest controller taking in 6 arguments, passing that through the Service and then using those arguments to build the object inside of the ServiceImplementation. From there I return a call to my repo using the object I just made. This call should attempt to query the database specific parameters of the object.
This query is the part where I'm not sure how to write using Spring JPA standards. I'd like to just use the variables I set my object with, but I'm not sure if I'll have to write out a query or if spring JPA can make it a bit more simple?
Code:
Controller:
#RestController
public class exampleController {
#Autowired
private ExampleService exampleService;
#GetMapping("/rest/example/search")
public exampleObj searchExample (#RequestParam(value = "exLetter") String exLetter,
#RequestParam(value = "exLang") String exLang, #RequestParam(value = "exType")int exType,
#RequestParam(value = "exMethod") String exMethod, #RequestParam(value = "exCd") String exCd,
#RequestParam(value = "exOrg") String exOrg) {
return exampleService.getExampleLetter(exLetter, exLang, exType, exMethod, exCd, exOrg);
}
}
ExampleSerivce:
public interface ExampleService {
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg);
}
ExampleServiceImplementation:
#Service
public class ExampleServiceImpl implements ExampleService {
#Autowired
private ExampleRepository exampleRepo;
#Override
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg) {
ExampleLetter examp = new ExampleLetter();
examp.setExCd(exCd);
examp.getKey().setExampleNumber(exLetter);
examp.getKey().setLanguageType(exLang);
examp.getKey().setMethod(exMethod);
examp.getKey().setMarketOrg(exOrg);
examp.getKey().setType(exType);
return exampleRepo.findExampleLetter(examp);
}
}
Repo:
#Repository
public interface ExampleRepository extends CrudRepository<ExampleLetter, ExampleLetterKey> {
}
If I understand it correctly, you are trying to make a dinamic query, based on filtering values that may or may not be there. If that's the case, you can use the Specification class to create the query dinamically:
First, in your Repository class, extend JpaSpecificationExecutor<ExampleLetter>:
#Repository
public interface ExampleRepository extends CrudRepository<ExampleLetter, ExampleLetterKey>, JpaSpecificationExecutor<ExampleLetter> {
}
Now, you will need a method (I'd sugest you put it in an specific class, for organization sake) to generate the query itself:
public class GenerateQueryForExampleLetter {
ExampleLetter exampleLetter;
public Specification<ExampleLetter> generateQuery() {
return new Specification<ExampleLetter>() {
private static final long serialVersionUID = 1L;
#Override
public Predicate toPredicate(Root<ExampleLetter> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Predicate pred = null;
List<Predicate> predicates = new ArrayList<Predicate>();
if (this.exampleLetter.getExCd()!= null && !this.exampleLetter.getExCd().isEmpty()) {
predicates.add(builder.equal(root.<String>get("exCd"), this.exampleLetter.getExCd()));
}
...................
if (this.exampleLetter.getTheFieldYouNeed()!= null && !getTheFieldYouNeed.isEmpty()) {
predicates.add(builder.equal(root.<TheTypeOfTheField>get("theFieldYouNeed"), this.exampleLetter.getTheFieldYouNeed()));
}
if (!predicates.isEmpty()) {
pred = builder.and(predicates.toArray(new Predicate[] {}));
}
return pred;
}
};
}
public void setExampleLetter (ExampleLetter el) {
this.exampleLetter = el;
}
}
Finally, in your service class:
#Override
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg) {
ExampleLetter examp = new ExampleLetter();
examp.setExCd(exCd);
examp.getKey().setExampleNumber(exLetter);
examp.getKey().setLanguageType(exLang);
examp.getKey().setMethod(exMethod);
examp.getKey().setMarketOrg(exOrg);
examp.getKey().setType(exType);
GenerateQueryForExampleLetter queryGenerator = new GenerateQueryForExampleLetter ();
queryGenerator.setExampleLetter(examp);
return exampleRepo.findAll(queryGenerator.generateQuery());
}
Note that the JpaSpecificationExecutor interface adds a few utility methods for you to use which, besides filtering, supports sorting and pagination.
For more details, check here, here, or this answer.

How can I create unit test for void method which just change data and save it

I have some handler(in this example I just use controller):
#RestController
public class MessageController {
private final TaskExecutor taskExecutor;
private final Operation operation;
private final MessageLogService messageLogService;
public MessageController(TaskExecutor taskExecutor, Operation operation, MessageLogService messageLogService) {
this.taskExecutor = taskExecutor;
this.operation = operation;
this.messageLogService = messageLogService;
}
#PostMapping(value = "/process")
public String handleMessage(MessageRequest messageRequest){
MessageLog messageLog = messageLogService.createNewMessageLog();
taskExecutor.execute(() -> {
try {
operation.process(messageLog.getGuid(), messageRequest);
} catch (MessageLogDoesNotExistException e) {
throw new RuntimeException(e);
}
});
return "REQUEST_QUOTED";
}
}
I receive some request.
I create new MessageLog in DB with status "NEW" and some default(and some data from the request in the real project) values and save.
I send messageRequest and MessageLog's guid to the operation in the executor and return sync response "REQUEST_QUOTED" immediately.
#Service
public class MessageOperation implements Operation {
private final MessageLogService messageLogService;
public MessageOperation(MessageLogService messageLogService) {
this.messageLogService = messageLogService;
}
#Transactional
#Override
public void process(String guid, MessageRequest messageRequest) throws MessageLogDoesNotExistException {
MessageLog messageLog = messageLogService.getOne(guid);
if (messageLog == null)
throw new MessageLogDoesNotExistException();
try {
Message message = createMessage(messageRequest);
messageLog.setStatus("SUCCESS");
messageLog.setMessage(message);
} catch (MessageCreationException e) {
messageLog.setStatus("FAIL");
messageLog.setErrorCode(e.getCode());
}
messageLogService.save(messageLog);
}
private Message createMessage(MessageRequest messageRequest) throws MessageCreationException {
//logic
return null;
}
}
Into operation I create the message and bind it with messageLog. If I create and bind success - I set status 'SUCCESS' or 'FAIL' if not. And just save messageLog.
How can I create Unit test for operation's method process? It is void.
1) I get a request from the client
2) delegate the request to the new thread for the async process
3) return sync response.
And I don't understand how can I create a unit test for public void process(String guid, MessageRequest messageRequest)
In this case for MessageOperation I recommend using Mockito https://www.baeldung.com/mockito-annotations library for mocking the class attribute
#Mock
private final MessageLogService messageLogService;
Then in your unit test you use the verify()method to check that expected behaviour has happened. (save is called correctly for example).
I would also mock the response of getOne to fit your need
MessageLog messageLog = messageLogService.getOne(guid);
for example
MessageLog messageLog = new MessageLog();
when(messageLogService.getOne(eq("THE GUID YOU GIVE IN THE METHDO CALL"))).thenReturn(messageLog);
That way since you have the object reference to MessageLogyou can check for the status in the test code:
assertEquals("SUCCESS", messageLog.getStatus());
And use verify to check that the save method is called correctly:
verify(messageLogService).save(same(messageLog));
About the matchers I used https://www.baeldung.com/mockito-argument-matchers

Mockito Java: How to test a method which calls other class to retrieve value

I'm trying to test a method intialize transaction where the method creates a unique transaction Id. This method uses reference of other class to retrieve the properties.
After mocking the reference classes am still getting null pointer exception. when i try to test. Below is my code.
Note: JMockito
Any help appreciated
public ResponseDto initializeTransaction(RequestDTO request){
try {
String transactionId =getTransactionId(request);
ResponseDTO result = new ResponseDTO();
result.setTransactionId(transactionId);
return result;
}
}
public String getTransactionId(CreditCardGwtInitializeRequestDTO request){
StringBuffer transactionId = new StringBuffer();
String customerId = customerIdentifier.getCustomer();
UserDto userDto = user.getUserDetails(request.getKey());
String userWorkStationId =userDto.getWorkStationId();
transactionId.append(String.valueOf(System.currentTimeMillis()) + "-");
transactionId.append(userDto.getObjId()+ "-");
transactionId.append(transactionIdEncode.encode(customerId));
transactionId.append("-");
transactionId.append(transactionIdEncode.encode(userWorkStationId));
return transactionId.toString();
}
Test class
public class CreditCardGwtInitializeServiceImplTest {
private CreditCardGwtInitializeServiceImpl test;
#Mock
private CustomerIdentifier customerIdentifier;
#Mock
private UserDto userDto;
#Mock
private UserDetails user;
private CreditCardGwtInitializeRequestDTO request;
#Mock
TransactionIdCharacterEncoding transactionId;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void when_profilename_notNull_retrieveByName() throws Exception {
//test.setUser(user);
CreditCardGwtInitializeResponseDTO expected = new CreditCardGwtInitializeResponseDTO();
expected.setGatewayPassKey("");
String profileName="theName";
String connectionKey ="123456";
String custId ="custId";
request.setGatewayProfile(profileName);
request.setConnectionKey(connectionKey);
//userDto.setWorkStationId("12345");
//userDto.setObjId(12345L);
when(customerIdentifier.getCustomer()).thenReturn(custId);
when(user.getUserDetails(anyString())).thenReturn(userDto);
when(userDto.getWorkStationId()).thenReturn("RTYTYU");
when(userDto.getObjId()).thenReturn(1232324L);
when(transactionId.encode(anyString())).thenReturn("01010101");
CreditCardGwtInitializeResponseDTO response = test.initializeTransaction(request);
assertEquals(expected,response );
verifyZeroInteractions(gatewayProfileRetrievalService);
}
By adding thes lines solved my problem
MockitoAnnotations.initMocks(this);
test = new CreditCardGwtInitializeServiceImpl();
test.setUser(user);
test.setCustomerIdentifier(customerIdentifier);
test.setTransactionIdEncode(transactionId);
Here is an alternative solution that uses Mockito magic instead of manual initialization (replaces the setup method).
First override the default JUnit Runner class by the one provided by mockito (this has the same effect as MockitoAnnotations.initMocks(this)):
#RunWith(MockitoJUnitRunner.class)
public class CreditCardGwtInitializeServiceImplTest {
...
then instantiate your object right after your declare it and let mockito take care of the injection (this has the same effect as the 5 lines of your solution) :
#InjectMocks
private CreditCardGwtInitializeServiceImpl test = new CreditCardGwtInitializeServiceImpl();
...

Categories

Resources