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

[Dúvida] Testar método POST

Estou com um problema para testar uma requisição do método POST, quando eu subo a aplicação. Ela subiu normalmente, junto com o H2, quando eu configurei, criando as duas tabelas, sem problemas. O estranho é que veio um erro depois que eu fiz uma chamada via Insomnia, dizendo que a coluna não foi encontrada, só que no log, o estranho é que está aparecendo como se eu tivesse separado por "_", sendo que eu criei tanto as tabelas do banco, via script, no flyway, como as classes, colocando "dataAtualizacao". Nessa situação, eu preciso fazer alguma correção, ajustando as tabelas, criando uma nova versão, fazendo um "alter table", para renomear o nome das colunas?

insert into beneficiarios (data_atualizacao,data_inclusao,data_nascimento,nome,telefone,id) values (?,?,?,?,?,default) [42122-224]
2024-03-07T15:50:42.969-03:00 ERROR 13108 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement [Column "DATA_ATUALIZACAO" not found; SQL statement:
insert into beneficiarios (data_atualizacao,data_inclusao,data_nascimento,nome,telefone,id) values (?,?,?,?,?,default) [42122-224]] [insert into beneficiarios (data_atualizacao,data_inclusao,data_nascimento,nome,telefone,id) values (?,?,?,?,?,default)]; SQL [insert into beneficiarios (data_atualizacao,data_inclusao,data_nascimento,nome,telefone,id) values (?,?,?,?,?,default)]] with root cause

org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "DATA_ATUALIZACAO" not found; SQL statement:
insert into beneficiarios (data_atualizacao,data_inclusao,data_nascimento,nome,telefone,id) values (?,?,?,?,?,default) [42122-224]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) ~[h2-2.2.224.jar:2.2.224]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) ~[h2-2.2.224.jar:2.2.224]
    at org.h2.message.DbException.get(DbException.java:223) ~[h2-2.2.224.jar:2.2.224]

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

Só que, se eu testar, enviando a data, separado por "/", retorna erro 400. Acredito que seja a forma correta que eu devo enviar na requisição, para formato de data, mas não estou acertando para passar no tratamento. As classes estão esperando no formato Date, até incluí a anotação para pegar a formatação, mas não está funcionando.

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

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

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

26 respostas

Oii, Alexandre, tudo bem?

Um dos erros que aparece é pelo formato da data: você colocou o formato brasileiro sendo que para o Spring o ideal é o formato estadunidense. Recomendo dar uma olhadinha neste artigo.

Quanto ao outro erro: houve algum erro com a sua coluna pela nomenclatura. O que posso te sugerir é dar uma olhadinha na tabela, pois talvez você escreveu errado em algum canto (e como não temos acesso - em alguns lugares mostra como beneficiário, e no Insomnia está como beneficiares -, fica um pouco difícil avaliar).

Se outra dúvida surgir, estamos aqui.

Abraços!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Bom dia!

Então, depois que eu li o artigo que me passou, eu alterei o tipo, como pode observar, mas o erro continua. Acho que estou colocando a anotação no lugar errado, ao invés de ser na classe Controller, será que é isso? Insira aqui a descrição dessa imagem para ajudar na acessibilidadeAgora, sobre as tabelas do banco, elas chegaram a ser criadas no H2 e aparecem normalmente, com as colunas, mas quando eu faço a chamada, ou retorna erro 500, ou erro 400. Qual a forma correta que eu devo configurar? Insira aqui a descrição dessa imagem para ajudar na acessibilidadeInsira aqui a descrição dessa imagem para ajudar na acessibilidade

Oi!

Manda aqui o seu script V1 do flyway.

Segue o script:

create table beneficiarios (

    id bigint not null auto_increment,
    nome varchar(100) not null,
    telefone varchar(20) not null,
    dataNascimento date not null,
    dataInclusao date not null,
    dataAtualizacao date not null,
    ativo tinyint not null,

    primary key(id)

);

create table documentos (

    id bigint not null auto_increment,
    tipoDocumento varchar(10) not null,
    descricao varchar(100),
    dataInclusao date not null,
    dataAtualizacao date not null,
    ativo tinyint not null,

    primary key(id)

);

No seu script você configurou para as colunas serem criadas com camelCase, ao invés de underline.

O padrão do Hibernate é gerar os comandos SQL considerando que as colunas estão com underline e por isso está dando erro no seu projeto. Você pode alterar esse padrão adicionando a seguinte propriedade:

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

Olha, funcionou, certinho, quando eu testei no Insomnia. O problema está no relacionamento entre as tabelas, porque não inseriu os dados da segunda tabela, que acabou não fazendo. Veja só o que aconteceu: Insira aqui a descrição dessa imagem para ajudar na acessibilidadeConsulta no banco H2: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Nas classes de entidade, eu coloquei os relacionamentos dessa forma:

package com.plano.saude.cadastro.beneficiario;

import com.plano.saude.cadastro.documento.Documento;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;
import java.util.List;

@Table(name = "beneficiarios")
@Entity(name = "Beneficiario")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Beneficiario {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    private String telefone;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dataNascimento;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dataInclusao;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dataAtualizacao;

    @OneToMany(mappedBy = "beneficiario", fetch = FetchType.EAGER)
    private List<Documento> documentos;

    private Boolean ativo;

    public Beneficiario(DadosCadastroBeneficiario dados){
        this.nome = dados.nome();
        this.telefone = dados.telefone();
        this.dataNascimento = dados.dataNascimento();
        this.dataInclusao = dados.dataInclusao();
        this.dataAtualizacao = dados.dataAtualizacao();
        this.documentos = dados.documentos();
        this.ativo = true;
    }
}
package com.plano.saude.cadastro.documento;

import com.plano.saude.cadastro.beneficiario.Beneficiario;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;

@Table(name = "documentos")
@Entity(name = "Documento")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Documento {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private TipoDocumento tipoDocumento;

    @ManyToOne
    private Beneficiario beneficiario;

    private String descricao;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dataInclusao;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dataAtualizacao;

    private Boolean ativo;

    public Documento(DadosCadastroDocumento dados){
        this.tipoDocumento = dados.tipoDocumento();
        this.beneficiario = dados.beneficiario();
        this.descricao = dados.descricao();
        this.dataInclusao = dados.dataInclusao();
        this.dataAtualizacao = dados.dataAtualizacao();
        this.ativo = true;
    }
}

A lista de documentos não é salva automaticamente. Precisa do cascade para ter esse efeito:

@OneToMany(mappedBy = "beneficiario", fetch = FetchType.EAGER, cascade = CascadeType.ALL)

Olha, mesmo eu adicionando o cascade, não funcionou, dando o erro abaixo, não localizando a coluna "beneficiario_id", só que eu não criei essa coluna para a minha tabela.

2024-03-13T12:12:48.717-03:00 WARN 10920 --- [nio-8080-exec-9] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 42122, SQLState: 42S22 2024-03-13T12:12:48.717-03:00 ERROR 10920 --- [nio-8080-exec-9] o.h.engine.jdbc.spi.SqlExceptionHelper : Column "BENEFICIARIO_ID" not found; SQL statement: insert into documentos (ativo,beneficiario_id,dataAtualizacao,dataInclusao,descricao,tipoDocumento,id) values (?,?,?,?,?,?,default) [42122-224] 2024-03-13T12:12:48.747-03:00 ERROR 10920 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement [Column "BENEFICIARIO_ID" not found; SQL statement: insert into documentos (ativo,beneficiario_id,dataAtualizacao,dataInclusao,descricao,tipoDocumento,id) values (?,?,?,?,?,?,default) [42122-224]] [insert into documentos (ativo,beneficiario_id,dataAtualizacao,dataInclusao,descricao,tipoDocumento,id) values (?,?,?,?,?,?,default)]; SQL [insert into documentos (ativo,beneficiario_id,dataAtualizacao,dataInclusao,descricao,tipoDocumento,id) values (?,?,?,?,?,?,default)]] with root cause

Pergunta: O "id" da tabela beneficiario, que eu criei na entidade Beneficiario eu não poderia fazer um join com o id da tabela documento que eu criei na entidade Documento? Ou será necessário eu adicionar a coluna beneficiario_id na tabela beneficiario como chave estrangeira para termos efeito de cascata?

Na sua tabela de documentos está faltando a coluna de relacionamento com a tabela de beneficiarios e por isso deu o erro. Precisa criar uma migration para incluir essa coluna.

Olha, acho que estou criando errado... rs Eu criei uma migration para incluir a coluna na tabela documentos, mas o erro continua: Insira aqui a descrição dessa imagem para ajudar na acessibilidadeEu vi aqui que faltou eu adicionar a anotação @JoinColumn com a nova coluna. Mesmo assim, retornou com esse erro: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Association 'com.plano.saude.cadastro.beneficiario.Beneficiario.documentos' is 'mappedBy' another entity and may not specify the '@JoinColumn' 2024-03-13T16:43:42.739-03:00 WARN 10648 --- [ 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 ";DBCLOSEONEXIT=FALSE" to the db URL) [90121-224]

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

A coluna devereia ser criada assim na tabela documentos:

alter table documentos add column beneficiario_id bigint;

E a anotação adicionada no atributo beneficiario da classe Documento:

@ManyToOne
@JoinColumn(name = "beneficiario_id")
private Beneficiario beneficiario;

Bom, a coluna foi criada na tabela e a anotação foi adicionada no atributo beneficiario da classe Documento. Só que agora deu outro erro, dizendo que estou passando um valor nulo para a coluna "ATIVO" na hora de inserir a tabela documentos, só que é um campo interno e fiz um tratamento no construtor, considerando como "true". De qualquer forma, eu preciso colocar a anotação @Column? Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Agora vai depender da lógica que você fez para cadastrar as informações. Manda o código da sua classe controller e service (caso tenha)

Por enquanto não tem a classe service, porque eu não fiz nenhum tipo de tratamento, da forma simples, somente a controller. Segue:

package com.plano.saude.cadastro.controller;

import com.plano.saude.cadastro.beneficiario.Beneficiario;
import com.plano.saude.cadastro.beneficiario.BeneficiarioRepository;
import com.plano.saude.cadastro.beneficiario.DadosCadastroBeneficiario;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("beneficiaries")
public class BeneficiarioController {

    @Autowired
    private BeneficiarioRepository beneficiarioRepository;

    @PostMapping
    @Transactional
    public void createBeneficiary(@RequestBody @Valid DadosCadastroBeneficiario dados){
        beneficiarioRepository.save(new Beneficiario(dados));
    }
}

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

Manda sua classe DadosCadastroDocumento

Segue a classe DadosCadastroDocumento:

package com.plano.saude.cadastro.documento;

import com.plano.saude.cadastro.beneficiario.Beneficiario;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDate;

public record DadosCadastroDocumento(

        @NotNull
        TipoDocumento tipoDocumento,
        Beneficiario beneficiario,
        String descricao,
        @NotNull
        LocalDate dataInclusao,
        LocalDate dataAtualizacao) {
}

Esqueci de pedir a outra: DadosCadastroBeneficiario

Sem problemas. Segue a classe DadosCadastroBeneficiario:

package com.plano.saude.cadastro.beneficiario;

import com.plano.saude.cadastro.documento.Documento;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDate;
import java.util.List;

public record DadosCadastroBeneficiario(
        @NotBlank
        String nome,
        @NotBlank
        String telefone,
        @NotBlank
        LocalDate dataNascimento,
        @NotBlank
        LocalDate dataInclusao,
        LocalDate dataAtualizacao,
        @NotNull
        @Valid
        List<Documento> documentos
        ) {
}

No seu record DadosCadastroBeneficiario o atributo documentos está como tipo uma lista de Documento, que é a entidade JPA, mas deveria ser uma lista de DadosCadastroDocumento.

E no record DadosCadastroDocumento não dee ter o atributo beneficiario.

Vai precisar realizar essa mudança e fazer os ajustes nas outras partes do código.

Bom, as alterações nos record foram feitas. Só que precisa fazer um ajuste na classe de entidade Beneficiario, quando eu declaro o atributo e o construtor que estava apontando para a lista de Documento, que alterei da entidade JPA para uma lista de DadosCadastroDocumento Por enquanto está assim:

@OneToMany(mappedBy = "beneficiario", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Documento> documentos;

E o construtor dessa forma:

    public Beneficiario(DadosCadastroBeneficiario dados){
        this.nome = dados.nome();
        this.telefone = dados.telefone();
        this.dataNascimento = dados.dataNascimento();
        this.dataInclusao = dados.dataInclusao();
        this.dataAtualizacao = dados.dataAtualizacao();
        **this.documentos = dados.documentos();**
        this.ativo = true;
    }

Na linha acima que eu destaquei é onde está dando erro de compilação, porque não estou mais apontando para a classe de entity, porque vai precisar. Se eu alterar o tipo para o record "DadosCadastroDocumento", pode até compilar, mas vai voltar a ter o mesmo erro de antes. Nesse caso, se eu colocar também o record como @Entity também vai perder todo o sentido do contexto, correto? Como que eu faço, por favor, para o record receber a lista?

Vai ter que ficar assim esse construtor, fazendo a conversão entre os objetos:

public Beneficiario(DadosCadastroBeneficiario dados){
    this.nome = dados.nome();
    this.telefone = dados.telefone();
    this.dataNascimento = dados.dataNascimento();
    this.dataInclusao = dados.dataInclusao();
    this.dataAtualizacao = dados.dataAtualizacao();
    
    this.documentos = dados.documentos().stream().map(Documento::new).toList();
    
    this.ativo = true;
}

Obs: o atributo da entidade deve continar sendo um List<Documento>

Funcionou, vou deixar o exemplo que utilizei no Insomnia para testar e como ficou no banco. Só que o campo beneficiario_id retornou null para o banco, porque não atribuiu nada. Mas se este campo é responsável para fazer o join com a tabela beneficiario, não deveria retornar o mesmo valor do id desta tabela? Só uma dúvida. Insira aqui a descrição dessa imagem para ajudar na acessibilidadeInsira aqui a descrição dessa imagem para ajudar na acessibilidade

@ManyToOne
@JoinColumn(name = "beneficiario_id")
private Beneficiario beneficiario;

Ah sim, precisa setar o beneficiario nos documentos:

this.documentos = dados.documentos().stream().map(Documento::new).toList();
this.documentos.forEach(d -> d.setBeneficiario(this));

Então, quando eu tentei setar o beneficiario nos documentos, fazendo dessa forma, para tentar mapear a lista, só que ele retorna o erro abaixo na entity. O tratamento eu preciso fazer campo a campo? Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Na classe documento você precisa gerar o método setter do atributo beneficiario. Apague esse que foi gerado e gere manualmente:

public void setBeneficiario(Beneficiario beneficiario) {
    this.beneficiario = beneficiario;
}
solução!

Funcionou, certinho! Pensei que era uma lista que tinha setar e acabei fazendo errado. Muito obrigado!!!

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

Quer mergulhar em tecnologia e aprendizagem?

Receba a newsletter que o nosso CEO escreve pessoalmente, com insights do mercado de trabalho, ciência e desenvolvimento de software