Dúvida para persistir em várias tabelas

Boa noite.

Estou montando uma API de uma agenda com o cadastro da pessoa e os tipos / números dos telefones.
Quando estuo enviando o JSon para testar pelo Postman, se enviar somente os dados do cadastro, ele grava normalmente.
Quando mando junto os dados da tabela de telefone, ele dá erro.
Vocês podem me ajudar a achar o erro.

Desde já, agradeço a todos pela ajuda.

Segue abaixo o erro e em segunda as entidades.

ERRO
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.ArrayList<com.pjem.pessoas.entity.Phone> from Object value (token JsonToken.START_OBJECT); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<com.pjem.pessoas.entity.Phone> from Object value (token JsonToken.START_OBJECT) at [Source: (PushbackInputStream); line: 14, column: 12] (through reference chain: com.pjem.pessoas.entity.Person[“telefone”])]

Entidade Person


@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "pessoas")
public class Person {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id_pessoa;
	
	@Column(nullable = false)
	private String nome;
	
	@Column(nullable = false)
	private String endereco;
	
	@Column(nullable = false)
	private int numero;
	
	private String complemento;
	
	@Column(nullable = false)
	private String bairro;
	
	@Column(nullable = false)
	private String cidade;
	
	@Column(nullable = false)
	private String uf;

	private LocalDate aniversario;
	
	@Column(nullable = false)
	private LocalDate  cadastro;
	
	@Column(nullable = false, unique = true)
	private String cpf;

	@OneToMany(fetch = FetchType.LAZY,
			    cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}) // define em quais situações será utlizado
	 private List<Phone> Telefone = new ArrayList<>(); // cria uma lista telefone através da classe Telefone
}

Entidade Phone

@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "telefone")
public class Phone {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id_telefone;

    private String tipo_telefone;

    private String numero;
}

Json enviado

{
 
"nome": "Joaquim da Silva",
"cpf":"12345678911",
"endereco":"Teste de endereço",
"numero":1234,
"complemento":"casa 123",
"Bairro":"Bairro",
"cidade":"Cidade",
"uf":"uf",
"aniversario":"1967-01-29",
"cadastro":"2022-01-29",

"telefone":{
    "tipo":"fixo",
    "numero":"(11) 47217680"
    }
}

Você está usando algum tipo de DTO para receber os dados ou está recebendo a própria entidade no seu Controller?

Direto.
A ideia é após feito o crud assim, passar para MySQL e c DTO

Recomendo vc usar um DTO para receber os dados, para depois converter para a entidade, e depois salvar.


Mesmo assim, da forma como está fazendo, o json deve refletir as propriedades da sua entidade.

  • sua entidade tem a propriedade Telefone, e no json está telefone (com t minúsculo);
  • e o telefone é uma lista, então deveria se chamar telefones, e o json deveria ser uma array ([]) e não um objeto ({})
  • evite declarar variáveis assim: tipo_telefone, pois não faz parte de convenção. Como já está na classe Phone, essa propriedade poderia simplesmente se chamar: tipo. E no json o nome está diferente também.

Lucas, obrigado pela ajuda. Consegui sanar os problemas com a gravação.
Um pouco de atenção na hora de montar as classes.

Como vc falou, estou tentando montar um DTO também para receber os dados.
Estou com algumas dúvidas:

Todas as operações estão funcionando normal. Somente a alteração que não estou conseguindo efetuar.

Gostaria de saber, com relação a datas, se você pode me orientar :

  1. Qual o procedimento para que quando eu gravar o registro, grave também a data de quando foi inserido?
  2. Estou utilizando uma classe chamada Mapper para transformar a data do formato que grava no BD para formato dd/MM/yyyy. Você conhece ela? Como estou passando o objeto inteiro, volto a situação de não persistir na segunda tabela (Phones).

E o último, que é o principal.
Ao efetuar a alteração no registro, como possuo o campo CPF que tem de ser único, ele me manda a mensagem de erro informando que o camo CPF já existe.

Abaixo, vou colocar o erro:

2022-03-03 21:26:49.100  WARN 10892 --- [nio-9000-exec-9] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2022-03-03 21:26:49.101 ERROR 10892 --- [nio-9000-exec-9] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "PUBLIC.UK_C7PQBMO6E96SLVONILYWSB8OE_INDEX_2 ON PUBLIC.PESSOAS(CPF) VALUES 3"; SQL statement:
insert into pessoas (id_pessoa, aniversario, bairro, cadastro, cidade, complemento, cpf, endereco, nome, numero, uf) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-200]
2022-03-03 21:26:49.110 ERROR 10892 --- [nio-9000-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PUBLIC.UK_C7PQBMO6E96SLVONILYWSB8OE_INDEX_2 ON PUBLIC.PESSOAS(CPF) VALUES 3"; SQL statement:
insert into pessoas (id_pessoa, aniversario, bairro, cadastro, cidade, complemento, cpf, endereco, nome, numero, uf) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.UK_C7PQBMO6E96SLVONILYWSB8OE_INDEX_2 ON PUBLIC.PESSOAS(CPF) VALUES 3"; SQL statement:
insert into pessoas (id_pessoa, aniversario, bairro, cadastro, cidade, complemento, cpf, endereco, nome, numero, uf) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-200]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:459) ~[h2-1.4.200.jar:1.4.200]

Se alguém puder ajudar nas dúvidas eu agradeço, pois ainda não achei uma resposta na net.

Abraço a todos.

Controller:

@RestController
@AllArgsConstructor
@RequestMapping("/api/v1")
public class PersonController {

    @Autowired
    private PersonService service;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Person cria(@RequestBody @Valid PersonDTO personDTO){
        return service.grava(personDTO);
    }

    @GetMapping
    public List<PersonDTO> lista(){
        return service.lista();
    }

    @GetMapping("/id/{id}")
    public PersonDTO listaId(@PathVariable int id) throws PersonNotFoundException {
        return service.listaId(id);
    }

    @DeleteMapping("/id/{id}")
    public void deleta(@PathVariable int id) throws PersonNotFoundException{
        service.delete(id);
    }

    @PutMapping("/id/{id}")
    public Person alteracao(@PathVariable int id,
                               @RequestBody @Valid PersonDTO personDTO) throws PersonNotFoundException{
        return service.altera(id, personDTO);
    }
}

Service


@Service
@AllArgsConstructor
public class PersonService {

    private final PersonRepository repository;
    private final PersonMapper personMapper;

    public Person grava(PersonDTO personDTO){
       Person person = personMapper.toModel(personDTO);
        return repository.save(person);
    }

    public List<PersonDTO> lista(){
        List<Person> pessoa = repository.findAll();
        return pessoa.stream()
                .map(personMapper::toDTO)
                .collect((Collectors.toList()));
    }

    public PersonDTO listaId(int id) throws PersonNotFoundException {
        Person person = repository.findById(id)
                .orElseThrow(() -> new PersonNotFoundException(id));
        return personMapper.toDTO(person);
    }
    public void delete(int id) throws PersonNotFoundException{
        repository.findById(id)
                .orElseThrow(() -> new PersonNotFoundException(id));
        repository.deleteById(id);
        System.out.println("Cadastro numero " +id + " apagado.");
    }

    public Person altera(int id, PersonDTO personDTO) throws PersonNotFoundException {
        Person person = repository.findById(id)
                .orElseThrow(() -> new PersonNotFoundException(id));
        Person updatePerson = personMapper.toModel(personDTO);
        return repository.save(updatePerson);
    }

}

Mapper


@Mapper(componentModel = "spring")

public interface PersonMapper {

    @Mapping(target = "aniversario", source = "aniversario", dateFormat = "dd-MM-yyyy")
    @Mapping(target = "cadastro", source = "cadastro", dateFormat = "dd-MM-yyyy")
    Person toModel(PersonDTO dto);
    PersonDTO toDTO(Person dto);
}

PersonalDTO

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PersonDTO {

    private int id_pessoa;

    @NotEmpty
    @Size(min = 3, max = 150)
    private String nome;

    @NotEmpty
    @Size(min = 3, max = 200)
    private String endereco;

    @NotEmpty
    @Size( max = 5)
    private int numero;

    @Size(max = 50)
    private String complemento;

    @NotEmpty
    @Size(min = 3, max = 100)
    private String bairro;

    @NotEmpty
    @Size(min = 3, max = 50)
    private String cidade;

    @NotEmpty
    @Size(min = 2, max = 2)
    private String uf;

    @NotNull
    private LocalDate aniversario;

    @NotNull
    private LocalDate  cadastro;

    @NotEmpty
    @CPF
    private String cpf;

    @Valid
    @NotEmpty
    private List<PhoneDTO> phones;
}

PhoneDTO

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PhoneDTO {

    private int id_phone;

    @Enumerated(EnumType.STRING)
    private String type;

    @NotEmpty
    @Size(min = 13, max = 14)
    private String numero;
}

Basta preencher o campo de data com a data atual. E sobre esse mapper, particularmente, não curto muito o uso desse tipo de lib, pq ele parece que complica mais do que ajuda, ainda mais para algo simples que é converter uma coisa em outra.


Isso é estranho, pq é uma alteração e está sendo executado um insert. Verifique se vc está preenchendo o ID da entidade antes de executar o save.

Opa Lucas. Desculpe a demora para posicionar.
Valeu a ajuda. Realmente, quando entrava com a id, ele não estava passando junto. Também consegui montar um método para converter as datas.

Desculpa te incomodar aqui, Lucas.
Conforme vamos resolvendo um problema vai aparecendo outros kkk
Isso que dá, começar a fazer o primeiro programa sozinho. Aos poucos vamos vendo onde estamos errando na nossa necessidade.

Agora, quando grava, ele atualiza direitinho. Único detalhe que estou verificando é que quando coloco outros fones vinculados a mesma pessoa ele grava normalmente, porém, ele deixa indicado apenas no último tel.

Se já tem um tel cadastrado como nº 1 e depois faço uma alteração para incluir mais um tel, ele indica apenas o último tel registrado. O anterior, ele apaga a indicação. Ex:

pessoa 1 - telefone 1
pessoa 2 - telefone 2
pessoa 1 - telefone 3

para pessoa 1, ficará marcado somente o telefone 3, dando a impressão que tem somente um tel cadastrado, como abaixo:
pessoa 1
pessoa 2 - telefone 2
pessoa 1 - telefone 3.

Com relação a data, também preciso verificar como alterar o formato de entrada na entidade, pois quando tento entrar no formato dd/MM/yyyy, ele dá erro de formatação na entrada.
Alguma besteira que estou fazendo na atualização? Estou dando uma olhada agora no BD, pois estou passando do H2 para o mysql.

Valeu mais uma vez, e desculpa o incomodo de um início de programador.

Em relação a data, vc pode sempre passa no padrão ISO, ex.: 2020-07-10 15:00:00.

Sobre os telefones, como estão modeladas as tabelas no banco de dados?

Então, para entender, no front, é inserida a data no formato normal, e ele transforma para o formato ISO, para o tratamento no back?

Com relação ao BD, segue modelagem abaixo:

Pessoas
PK - idpessoas

Phones
PK-idrelefone

Pessoas_phones