Create a REST API that will help to fetch the account and customer details present inside the database by accepting the mobileNumber as an input.
Any end user or client application, they’ll pass the mobileNumber to our REST API and based upon the data present inside the database, it is going to send all the customer and bank account details.
In the AccountsController class, add a new method.
@GetMapping("/fetch")
public ResponseEntity<CustomerDto> fetchAccountDetails(
@RequestParam String mobileNumber)
{
}
@GetMapping annotation on top of the method because this method is going to help you to fetch the record details from the database. That’s why we need to use the @GetMapping whenever we are trying to read the data from the database.
@RequestParam – since we are trying to accept only one request value, which is mobileNumber, with the help of query params, we need to make sure we are mentioning @RequestParam.
Inside the service interface, add a new method to fetch account details.
import com.eazybytes.accounts.dto.CustomerDto;
public interface IAccountsService {
/**
*
* @param customerDto - CustomerDto object
*/
void createAccount(CustomerDto customerDto);
/**
*
* @param mobileNumber - Input Mobile Number
* @return Accounts Details based on a given mobileNumber
*/
CustomerDto fetchAccount(String mobileNumber);
}
In the service implementation class, inside this fetchAccount() method, we need to write a logic to fetch the account details and customer details from the database.
→ When we try to fetch the details from the database based upon a mobile number, there is a good chance there might be no customer, no account details for a given mobile number. In such scenarios, we should be able to throw a custom business exception.
Custom ResourceNotFound exception
In the exception package, create a new custom exception class.
I’m going to pass multiple parameters to throw a detailed exception to my end user or to the client application. The 3 fields are going to be what is the resource name like, whether it is accounts or customer and what is the field name and what is the field value?
The field name, obviously we are trying to fetch based upon the mobile number, so it will throw a detailed message to the end user or client application that you are trying to fetch account details based upon your mobile number with so and so value, and we are not able to find that.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String resourceName, String fieldName, String fieldValue) {
super(String.format("%s not found with the given input data %s : '%s'", resourceName, fieldName, fieldValue));
}
}
Register the custom exception with GlobalExceptionHandler
As a next step, we need to go to the GlobalExceptionHandler class and we need to make sure we are writing a method to handle this custom business exception ResourceNotFoundException.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponseDto> handleResourceNotFoundException(
ResourceNotFoundException exception,
WebRequest webRequest)
{
ErrorResponseDto errorResponseDTO = new ErrorResponseDto(
webRequest.getDescription(false),
HttpStatus.NOT_FOUND,
exception.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(errorResponseDTO, HttpStatus.NOT_FOUND);
}
Implementing the service logic – fetchAccount
Now let’s go to the AccountServiceImpl. So here we need to write the logic to fetch the account details and customer details based upon the mobile number.
Customer customer =
customerRepository.findByMobileNumber(mobileNumber)
.orElseThrow(
() -> new ResourceNotFoundException(
"Customer", "mobileNumber", mobileNumber)
);
I’m trying to invoke the method findByMobileNumber(). To this method I’m going to pass the received mobile number.
If there is no record with the given mobile number, I’m going to invoke the orElseThrow() method because this findByMobileNumber is going to return an Optional<Customer>.
Inside Optional, we have a method called orElseThrow(), using which we can throw an exception if there is no record present inside the Optional.
So here I’m going to write a lambda expression which is not going to accept any input parameter, but I’m simply going to throw an error which is a new ResourceNotFound() exception.
Using the very similar logic, I need to make sure I’m fetching the account details as well. Because whenever someone is trying to invoke my REST API, they need all the details, both customer and accounts details.
Now inside my accounts table there is no mobileNumber using which I can query and at the same time I cannot invoke findByID() method, because inside my accounts entity the primary key value is accountNumber.
But I received only mobileNumber from my client application. So what should I do here?
I need to create a new method inside AccountsRepository just like we created findByMobileNumber inside CustomerRepository.
@Repository
public interface AccountsRepository extends JpaRepository<Accounts, Long> {
Optional<Accounts> findByCustomerId(Long customerId);
}
After adding this method in AccountsRepository, we can now fetch the accounts details using customer id.
Accounts accounts =
accountsRepository.findByCustomerId(customer.getCustomerId())
.orElseThrow(
() -> new ResourceNotFoundException("Account", "customerId",
customer.getCustomerId().toString())
);
Now, instead of mobileNumber, I’m going to pass customer.getCustomerId() from this customer object.
If there is no account with the given customer ID, we are going to throw a ResourceNotFoundException.
How and What data to send data to client
Now we have a customer entity and accounts entity, can we send this directly to the client?
Of course not, because these entities have a lot of sensitive information and metadata information like createdAt createdBy, at the same time customerId, which is unnecessary for my client application. They only want useful information.
That’s why we need to convert these entities into the DTO classes.
→ As of now we have these CustomerDto and AccountsDto as two separate classes like AccountsDto holds strictly the data related to accounts, and similarly, CustomerDto holds the data related to customer only.
If you want to send the combined information or aggregated information of both CustomerDto and AccountsDto, we have two options.
- Either we can create one more DTO class where we can try to refer both these DTO classes (or)
- inside the CustomerDto, we can create one more field of type AccountsDto and with the field name as accountsDto. So this way also we can achieve the DTO pattern.
I’m going to follow the second approach, but if you want, you can also create a separate DTO class. I would recommend that approach only if your DTO has a lot of fields and they are very big in nature.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomerDto {
private String name;
private String email;
private String mobileNumber;
private AccountsDto accountsDto;
}
Here, we have added accountsDto field to CustomerDto class.
Full Service layer logic to fetch customer and accounts data
@Override
public CustomerDto fetchAccount(String mobileNumber) {
Customer customer =
customerRepository.findByMobileNumber(mobileNumber)
.orElseThrow(
() -> new ResourceNotFoundException(
"Customer", "mobileNumber", mobileNumber)
);
Accounts accounts =
accountsRepository.findByCustomerId(customer.getCustomerId())
.orElseThrow(
() -> new ResourceNotFoundException("Account", "customerId",
customer.getCustomerId().toString())
);
CustomerDto customerDto =
CustomerMapper.mapToCustomerDto(customer, new CustomerDto());
customerDto.setAccountsDto(
AccountsMapper.mapToAccountsDto(accounts, new AccountsDto()));
return customerDto;
}
Fetch data REST API in Controller layer
Now use this method in the Controller layer.
@GetMapping("/fetch")
public ResponseEntity<CustomerDto> fetchAccountDetails(
@RequestParam String mobileNumber)
{
CustomerDto customerDto = service.fetchAccount(mobileNumber);
return ResponseEntity.status(HttpStatus.OK).body(customerDto);
}
Build the application and deploy.My application started successfully at port 8080.Fetching the details Response – Bad mobileNumber – Build and Deploy