Solucionado (ver solução)
Solucionado
(ver solução)
10
respostas

[Bug] Não consigo criar mais de uma requisição e tratamento de erro 404

Oi!

Estou com dois problemas que gostaria de ajustar no meu código e não estou conseguindo. O primeiro é tentar criar mais de uma transação, depois que eu crio a conta. A primeira transação ele cria, sem problemas, inclusive, fazendo o cálculo que precisa ser feito, que coloquei no enum. O problema está na segunda transação que eu faço, utilizando a mesma conta, retornando o erro 500. Erro: Insira aqui a descrição dessa imagem para ajudar na acessibilidadeO segundo erro (ou tratamento) que também não estou conseguindo fazer, é ajustar, quando a conta não existir no banco, deverá retornar o erro 404. Eu tentei colocar uma condição na classe service, mas pelo tipo de retorno que eu declarei, pede para eu ajustar no ResponseEntity, que não está fazendo o tratamento e dá erro de compilação e só queria o retorno dessa transformação na classe controller. Poderia me ajudar, por favor o que estou fazendo errado? Seguem as classes controller e service:

@RestController
@RequestMapping("transacao")
@SecurityRequirement(name = "bearer-key")
public class TransactController {

    @Autowired
    private TransactionService transactionService;

    @Autowired
    private AccountService accountService;

    @PostMapping
    @Transactional
    public ResponseEntity<DataDetailingTransaction> createTransaction(@RequestBody @Valid DataRegistrationTransaction request, UriComponentsBuilder uriBuilder) {
        DataRegistrationTransaction transaction = new DataRegistrationTransaction(request);

        Optional<Account> optionalAccount = accountService.findAccount(transaction.numeroConta());

        var dataDetailingTransaction = transactionService.saveTransaction(transaction);

//        BigDecimal fundValue = optionalAccount.get().getSaldo();
//        BigDecimal transactionValue = transaction.valor();
//        int diferenca = transactionValue.compareTo(fundValue);
//
//        if (diferenca < 0) {
//            return ResponseEntity.notFound().build();
//        }
//        Até pensei em colocar a condição por aqui, mas vi que não são boas práticas e também não faz sentido, se eu devo configurar na classe service.

        var uri = uriBuilder
                .path("/transacao/{id}")
                .buildAndExpand(dataDetailingTransaction)
                .toUri();

        return ResponseEntity.created(uri).body(dataDetailingTransaction);
    }
}
@Service
@Transactional
@RequiredArgsConstructor
public class TransactionService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private TransactionRepository transactionRepository;

    public DataDetailingTransaction saveTransaction(DataRegistrationTransaction data) {

        var findAccount = accountRepository.findAccountByAtivoTrue(data.numeroConta());
        if (findAccount == null) {
            throw new ValidationException("Conta não cadastrada na base de dados.");
//            return new TreatErrors().treatError404();
//            Acima foi feito o tratamento de retorno que tentei fazer para retornar o erro 404
        }

        // Outras validações ou regras de negócio aqui...
        if (data.numeroConta() == null) {
            throw new ValidationException("Conta não cadastrada na base de dados.");
//            return new TreatErrors().treatError404();
        }

        Optional<Transaction> optTransaction = Optional.ofNullable(transactionRepository.findTransactionByAtivoTrue(data.numeroConta()));

        BigDecimal valorCalculado = computeTaxTransaction(data);

        Optional<Account> optAccount = Optional.ofNullable(accountRepository.findAccountByAtivoTrue(data.numeroConta()));

        BigDecimal saldoInicial;

        saldoInicial = optAccount.get().getSaldo();

        BigDecimal saldo;
        saldo = saldoInicial.subtract(valorCalculado);

        var account = new Account(optAccount.get().getId(), data.numeroConta(), saldo, true);
        accountRepository.save(account);

        var transaction = new Transaction(data, findAccount);
        transactionRepository.save(transaction);

        return new DataDetailingTransaction(transaction);
    }
    
Optional.ofNullable(transactionRepository.findTransactionByAtivoTrue(transactions.getAccount().getNumeroConta()));

private BigDecimal computeTaxTransaction(DataRegistrationTransaction transactions) {
        if (transactions.formaPagamento() == null) {
            throw new ValidationException("É obrigatório informar a forma de pagamento para validar o processo de transação.");
        }
        BigDecimal taxTransaction;
        PaymentForm paymentForm = transactions.formaPagamento();

        taxTransaction = paymentForm.computeTaxTransaction(transactions.valor());

        return taxTransaction;
    }
}
10 respostas

Oi Alexandre!

Vamos tentar resolver os dois problemas que você mencionou.

Problema 1: Erro 500 ao criar a segunda transação

O erro ConversionFailedException indica que há um problema ao converter um tipo de dado. Isso geralmente acontece quando o tipo de dado retornado pelo repositório não é o esperado. No seu caso, parece que o repositório está retornando um array de objetos (Object[]) em vez de uma instância da classe Transaction.

Para resolver isso, verifique se o método findTransactionByAtivoTrue no TransactionRepository está corretamente definido para retornar uma Transaction. Aqui está um exemplo de como o método pode ser definido:

@Repository
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
    Transaction findTransactionByAtivoTrue(String numeroConta);
}

Certifique-se de que o método esteja retornando uma instância de Transaction e não um array de objetos.

Problema 2: Tratamento de erro 404

Para retornar um erro 404 quando a conta não é encontrada, você pode usar a anotação @ResponseStatus junto com uma exceção personalizada. Aqui está um exemplo de como fazer isso:

  1. Crie uma exceção personalizada:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class AccountNotFoundException extends RuntimeException {
    public AccountNotFoundException(String message) {
        super(message);
    }
}
  1. Lance essa exceção na sua classe TransactionService quando a conta não for encontrada:
@Service
@Transactional
@RequiredArgsConstructor
public class TransactionService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private TransactionRepository transactionRepository;

    public DataDetailingTransaction saveTransaction(DataRegistrationTransaction data) {

        var findAccount = accountRepository.findAccountByAtivoTrue(data.numeroConta());
        if (findAccount == null) {
            throw new AccountNotFoundException("Conta não cadastrada na base de dados.");
        }

        // Outras validações ou regras de negócio aqui...
        if (data.numeroConta() == null) {
            throw new AccountNotFoundException("Conta não cadastrada na base de dados.");
        }

        Optional<Transaction> optTransaction = Optional.ofNullable(transactionRepository.findTransactionByAtivoTrue(data.numeroConta()));

        BigDecimal valorCalculado = computeTaxTransaction(data);

        Optional<Account> optAccount = Optional.ofNullable(accountRepository.findAccountByAtivoTrue(data.numeroConta()));

        BigDecimal saldoInicial;

        saldoInicial = optAccount.get().getSaldo();

        BigDecimal saldo;
        saldo = saldoInicial.subtract(valorCalculado);

        var account = new Account(optAccount.get().getId(), data.numeroConta(), saldo, true);
        accountRepository.save(account);

        var transaction = new Transaction(data, findAccount);
        transactionRepository.save(transaction);

        return new DataDetailingTransaction(transaction);
    }

    private BigDecimal computeTaxTransaction(DataRegistrationTransaction transactions) {
        if (transactions.formaPagamento() == null) {
            throw new ValidationException("É obrigatório informar a forma de pagamento para validar o processo de transação.");
        }
        BigDecimal taxTransaction;
        PaymentForm paymentForm = transactions.formaPagamento();

        taxTransaction = paymentForm.computeTaxTransaction(transactions.valor());

        return taxTransaction;
    }
}

Com essas mudanças, quando a conta não for encontrada, a exceção AccountNotFoundException será lançada e o Spring retornará automaticamente um erro 404.

Espero ter ajudado e bons estudos!

Oi!

Então, sobre os problemas, estou encontrando os seguintes erros depois dos ajustes:

Sobre o problema 1, eu tinha criado uma query jpql na interface repository, ficando dessa forma:

public interface TransactionRepository extends JpaRepository<Transaction, Long> {
    @Query("""
            select t.id
                 , t.formaPagamento
                 , a.numeroConta
                 , t.valor
                 , a.saldo
              from Transaction t
                 , Account a
            where
            a.numeroConta = :numeroConta
            """)
    Transaction findTransactionByAtivoTrue(String numeroConta);
}

Se eu retirar a anotação, vai aparecer o erro (ao inicializar a aplicação): Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Para o problema 2, eu já tinha feito duas classes para tratamento de exceção, uma, localizada na package domain e a outra na package infra/exception (de onde está a classe que criei antes e não consegui fazer o tratamento). Ficou duas dúvidas: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Todas as classes de exceção, por organização, não deveriam constar somente na package infra/exception? Mesmo ajustando, como tinha orientado, retorna o mesmo erro de antes (500). Será que o problema está vindo da configuração que tinha feito na interface TransactionRepository? Insira aqui a descrição dessa imagem para ajudar na acessibilidade

public interface TransactionRepository extends JpaRepository<Transaction, Long> {
    @Query("""
            select t.id
                 , t.formaPagamento
                 , a.numeroConta
                 , t.valor
                 , a.saldo
              from Transaction t
                 , Account a
            where
            a.numeroConta = :numeroConta
            """)
    Transaction findTransactionByAtivoTrue(String numeroConta);
}

Aliás, depois dos ajustes, quando eu fui testar para criar uma nova transação, com um numeroConta válido, foi retornado o erro 500, que não estava acontecendo.

Esse seu método no repository não deveria ter o @Query, ele pode ser escrito assim:

Transaction findByAtivoTrueAndAccountNumeroConta(String numeroConta);

Então, quando eu retirei a anotação @Query, dá erro ao tentar subir a aplicação, falando que não tem o atributo 'ativo' na classe Transaction, que realmente não coloquei e não vi necessidade, para colocar. Mesmo que eu não precise desse atributo, eu preciso colocar da mesma forma?

2024-07-11T08:49:26.399-03:00  WARN 15656 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'transactController': Unsatisfied dependency expressed through field 'transactionService': Error creating bean with name 'transactionService': Unsatisfied dependency expressed through field 'transactionRepository': Error creating bean with name 'transactionRepository' defined in com.banking.account.transact.domain.transaction.TransactionRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract com.banking.account.transact.domain.transaction.Transaction com.banking.account.transact.domain.transaction.TransactionRepository.findTransactionByAtivoTrueAndAccountNumeroConta(java.lang.String); Reason: Failed to create query for method public abstract com.banking.account.transact.domain.transaction.Transaction com.banking.account.transact.domain.transaction.TransactionRepository.findTransactionByAtivoTrueAndAccountNumeroConta(java.lang.String); No property 'ativo' found for type 'Transaction'
2024-07-11T08:49:26.400-03:00  INFO 15656 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-07-11T08:49:26.615-03:00  WARN 15656 --- [  restartedMain] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-220]
2024-07-11T08:49:26.615-03:00  INFO 15656 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-07-11T08:49:26.623-03:00  INFO 15656 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2024-07-11T08:49:26.631-03:00  INFO 15656 --- [  restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2024-07-11T08:49:26.674-03:00  INFO 15656 --- [  restartedMain] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-07-11T08:49:26.703-03:00 ERROR 15656 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

Não precisa então. Foi usado no curso para fazer a exclusão lógica dos registros. Basta renomear o seu método então:

Transaction findByAccountNumeroConta(String numeroConta);

Isso, exatamente, por isso que eu não coloquei o atributo 'ativo'. Renomeei o método, está funcionando para gerar as transações, somente as duas primeiras. A partir da terceira transação, está retornando agora este erro:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

2024-07-11T09:34:20.279-03:00  WARN 9908 --- [nio-8080-exec-9] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.IncorrectResultSizeDataAccessException: Query did not return a unique result: 2 results were returned]

Outro ponto é quando eu tento forçar um erro, ao criar uma nova transação, com um numeroConta que não existe no banco, não trata o erro, está retornando o erro 500. As regras foram feitas na classe TransactionService. Será que eu preciso ajustar também a classe controller TransactController? Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Na verdade esse seu método não faz sentido, porque ele busca apenas uma única transação filtrando pelo número da conta, mas no seu projeto podem ter várias transações vinculadas a uma mesma conta.

Por isso deu o erro. O método deveria devolver então uma lista de transações:

List<Transaction> findByAccountNumeroConta(String numeroConta);

Mas com essa mudança, você vai precisar alterar o algoritmo que está chamando esse método.

Bom, sobre o método, um dos pontos que abordei, agora estão funcionando, está certo, faltando eu apenas ajustar alguns detalhes. Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Outro ponto que ainda preciso ajustar é o tratamento de erro, quando eu crio uma transação com um número de conta inexistente, está retornando erro 500, ao invés de ser 404... Os tratamentos que eu coloquei na classe de serviço não estão validando... Insira aqui a descrição dessa imagem para ajudar na acessibilidadeA configuração da classe de serviço, até o momento é essa:

@Service
@Transactional
@RequiredArgsConstructor
public class TransactionService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private TransactionRepository transactionRepository;

    public DataDetailingTransaction saveTransaction(DataRegistrationTransaction data) {
        var findAccount = accountRepository.findAccountByAtivoTrue(data.numeroConta());
        if (findAccount == null) {
            throw new AccountNotFoundException("Conta não cadastrada na base de dados.");
        }

        // outras validações ou regras de negócio aqui...
        if (data.numeroConta() == null) {
            throw new AccountNotFoundException("Conta não cadastrada na base de dados.");
        }

        Optional<List<Transaction>> optTransaction = Optional.ofNullable(transactionRepository.findByAccountNumeroConta(data.numeroConta()));

        BigDecimal valorCalculado = computeTaxTransaction(data);

        Optional<Account> optAccount = Optional.ofNullable(accountRepository.findAccountByAtivoTrue(data.numeroConta()));

        BigDecimal saldoInicial;

        saldoInicial = optAccount.get().getSaldo();

        BigDecimal saldo;
        saldo = saldoInicial.subtract(valorCalculado);

        var account = new Account(optAccount.get().getId(), data.numeroConta(), saldo, true);
        accountRepository.save(account);

        var transaction = new Transaction(data, findAccount);
        transactionRepository.save(transaction);

        return new DataDetailingTransaction(transaction);
    }
    
    private BigDecimal computeTaxTransaction(DataRegistrationTransaction transactions) {
        if (transactions.formaPagamento() == null) {
            throw new ValidationException("É obrigatório informar a forma de pagamento para validar o processo de transação.");
        }
        BigDecimal taxTransaction;
        PaymentForm paymentForm = transactions.formaPagamento();

        taxTransaction = paymentForm.computeTaxTransaction(transactions.valor());

        return taxTransaction;
    }
}
solução!

Basta adicionar mais um método na classe de tratamento de erros:

@ExceptionHandler(AccountNotFoundException.class)
public ResponseEntity tratarErroAccountNotFound() {
    return ResponseEntity.notFound().build();
}

Entendi. Sem os dois tratamentos que apliquei nas classes de testes, eu não vou conseguir validar. Obrigado, professor!