Simple Banking application in Spring boot, JPA, REST where balance can be transferred among predefined accounts and transaction history retrievable

I want to create a very simple banking application where (i) predefined accounts with a positive balance can send money (ii) requesting balance and a list of transactions can be found. I have created API for those operations. All the operations are persisted into the database. I also wrote test cases to verify. I always try to learn from different sources and use the best practices related to project structure and clean code. It would be great if somebody gives me suggestions and ideas about what improvement I can make on the codes. Here is my project structure:

 ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── forhadmethun │ │ │ └── banking │ │ │ ├── BankingApplication.java │ │ │ ├── config │ │ │ │ └── documentation │ │ │ │ └── DocumentationConfig.java │ │ │ ├── controller │ │ │ │ ├── api │ │ │ │ │ └── AccountController.java │ │ │ │ └── request │ │ │ │ ├── AccountStatementRequest.java │ │ │ │ └── TransferBalanceRequest.java │ │ │ ├── dto │ │ │ │ ├── model │ │ │ │ │ └── AccountStatement.java │ │ │ │ └── response │ │ │ │ ├── ResponseError.java │ │ │ │ └── Response.java │ │ │ ├── entity │ │ │ │ ├── Account.java │ │ │ │ └── Transaction.java │ │ │ ├── repository │ │ │ │ ├── AccountRepository.java │ │ │ │ └── TransactionRepository.java │ │ │ └── service │ │ │ ├── AccountService.java │ │ │ └── impl │ │ │ └── AccountServiceImpl.java │ │ └── resources │ │ ├── application.properties │ │ ├── static │ │ └── templates │ └── test │ └── java │ └── com │ └── forhadmethun │ └── banking │ └── service │ └── impl │ └── AccountServiceImplTest.java 

AccountController.java

@RestController @RequestMapping("/api/account") public class AccountController < @Autowired private AccountService accountService; @RequestMapping("/create") public Listcreate(@RequestBody Account account) < accountService.save(account); return accountService.findAll(); >@RequestMapping("/all") public List all() < return accountService.findAll(); >@RequestMapping("/sendmoney") public Response sendMoney( @RequestBody TransferBalanceRequest transferBalanceRequest ) < return Response.ok().setPayload( accountService.sendMoney( transferBalanceRequest ) ); >@RequestMapping("/statement") public Response getStatement( @RequestBody AccountStatementRequest accountStatementRequest ) < return Response.ok().setPayload( accountService.getStatement(accountStatementRequest.getAccountNumber()) ); >> 

AccountStatementRequest.java

@Getter @Setter @NoArgsConstructor public class AccountStatementRequest

TransferBalanceRequest.java

@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class TransferBalanceRequest

AccountStatement.java

@AllArgsConstructor @NoArgsConstructor @Data public class AccountStatement < BigDecimal currentBalance; ListtransactionHistory; > 

Response.java

package com.forhadmethun.banking.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; import java.util.Date; @Getter @Setter @Accessors(chain = true) @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class Response  < private Status status; private T payload; private Object errors; private Object metadata; public static Response badRequest() < Responseresponse = new Response<>(); response.setStatus(Status.BAD_REQUEST); return response; > public static Response ok() < Responseresponse = new Response<>(); response.setStatus(Status.OK); return response; > public static Response unauthorized() < Responseresponse = new Response<>(); response.setStatus(Status.UNAUTHORIZED); return response; > public static Response validationException() < Responseresponse = new Response<>(); response.setStatus(Status.VALIDATION_EXCEPTION); return response; > public static Response wrongCredentials() < Responseresponse = new Response<>(); response.setStatus(Status.WRONG_CREDENTIALS); return response; > public static Response accessDenied() < Responseresponse = new Response<>(); response.setStatus(Status.ACCESS_DENIED); return response; > public static Response exception() < Responseresponse = new Response<>(); response.setStatus(Status.EXCEPTION); return response; > public static Response notFound() < Responseresponse = new Response<>(); response.setStatus(Status.NOT_FOUND); return response; > public static Response duplicateEntity() < Responseresponse = new Response<>(); response.setStatus(Status.DUPLICATE_ENTITY); return response; > public void addErrorMsgToResponse(String errorMsg, Exception ex) < ResponseError error = new ResponseError() .setDetails(errorMsg) .setMessage(ex.getMessage()) .setTimestamp(new Date()); setErrors(error); >public enum Status < OK, BAD_REQUEST, UNAUTHORIZED, VALIDATION_EXCEPTION, EXCEPTION, WRONG_CREDENTIALS, ACCESS_DENIED, NOT_FOUND, DUPLICATE_ENTITY >@Getter @Accessors(chain = true) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public static class PageMetadata < private int size; private long totalElements; private int totalPages; private int number; public PageMetadata(int size, long totalElements, int totalPages, int number) < this.size = size; this.totalElements = totalElements; this.totalPages = totalPages; this.number = number; >> > 

ResponseError.java

@Getter @Setter @Accessors(chain = true) @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class ResponseError

Account.java

@Builder @AllArgsConstructor @NoArgsConstructor @Data @Entity @Table(name = "account") public class Account

Transaction.java

@NoArgsConstructor @AllArgsConstructor @Data @Entity @Table(name = "transaction") public class Transaction

AccountRepository.java

@Repository public interface AccountRepository extends JpaRepository

TransactionRepository.java

@Repository public interface TransactionRepository extends JpaRepository  < ListfindByAccountNumberEquals(String accountNumber); > 

AccountService.java

public interface AccountService < ListfindAll(); Account save(Account account); Transaction sendMoney( TransferBalanceRequest transferBalanceRequest ); AccountStatement getStatement(String accountNumber); > 

AccountServiceImpl.java

@Service public class AccountServiceImpl implements AccountService < @Autowired private AccountRepository accountRepository; @Autowired TransactionRepository transactionRepository; public Account save(Account account)< accountRepository.save(account); return accountRepository.findByAccountNumberEquals(account.getAccountNumber()); >public List findAll() < return accountRepository.findAll(); >public Account findByAccountNumber(String accountNumber) < Account account = accountRepository.findByAccountNumberEquals(accountNumber); return account; >@Override public Transaction sendMoney( TransferBalanceRequest transferBalanceRequest ) < String fromAccountNumber = transferBalanceRequest.getFromAccountNumber(); String toAccountNumber = transferBalanceRequest.getToAccountNumber(); BigDecimal amount = transferBalanceRequest.getAmount(); Account fromAccount = accountRepository.findByAccountNumberEquals( fromAccountNumber ); Account toAccount = accountRepository.findByAccountNumberEquals(toAccountNumber); if(fromAccount.getCurrentBalance().compareTo(BigDecimal.ONE) == 1 && fromAccount.getCurrentBalance().compareTo(amount) == 1 )< fromAccount.setCurrentBalance(fromAccount.getCurrentBalance().subtract(amount)); accountRepository.save(fromAccount); toAccount.setCurrentBalance(toAccount.getCurrentBalance().add(amount)); accountRepository.save(toAccount); Transaction transaction = transactionRepository.save(new Transaction(0L,fromAccountNumber,amount,new Timestamp(System.currentTimeMillis()))); return transaction; >return null; > @Override public AccountStatement getStatement(String accountNumber)

AccountServiceImplTest.java

@RunWith(SpringRunner.class) @DataJpaTest public class AccountServiceImplTest < @TestConfiguration static class AccountServiceTestContextConfiguration < @Bean public AccountServiceImpl accountServiceImplTest() < return new AccountServiceImpl(); >> @Autowired private AccountServiceImpl accountService; @Test public void sendMoneyTest() < Account account1 = new Account(0L, "1001", new BigDecimal(50000)); Account account2 = new Account(0L, "2002", new BigDecimal(2000)); accountService.save(account1); accountService.save(account2); TransferBalanceRequest transferBalanceRequest = new TransferBalanceRequest( account1.getAccountNumber(), account2.getAccountNumber(), new BigDecimal(3000) ); accountService.sendMoney(transferBalanceRequest); assertThat(accountService.findByAccountNumber(account1.getAccountNumber()) .getCurrentBalance()) .isEqualTo(new BigDecimal(47000)); assertThat(accountService.findByAccountNumber(account2.getAccountNumber()) .getCurrentBalance()) .isEqualTo(new BigDecimal(5000)); >@Test public void getStatement() < Account account1 = new Account(0L, "1001", new BigDecimal(50000)); Account account2 = new Account(0L, "2002", new BigDecimal(2000)); accountService.save(account1); accountService.save(account2); TransferBalanceRequest transferBalanceRequest = new TransferBalanceRequest( account1.getAccountNumber(), account2.getAccountNumber(), new BigDecimal(3000) ); accountService.sendMoney(transferBalanceRequest); assertThat(accountService.getStatement(account1.getAccountNumber()) .getCurrentBalance()) .isEqualTo(new BigDecimal(47000)); accountService.sendMoney(transferBalanceRequest); assertThat(accountService.getStatement(account1.getAccountNumber()) .getCurrentBalance()).isEqualTo(new BigDecimal(44000)); assertThat(accountService.getStatement(account1.getAccountNumber()) .getTransactionHistory().size()).isEqualTo(2); >> ```