Erro ao gerar FK com Hibernate

15 respostas Resolvido
javawebhibernate
Moreira89

Boa noite!

Estou com erro ao tentar gerar FK com Hibernate, ele não reconhece como FK e dá erro de registro duplicados.

Tenho uma classe Pessoa e uma classe Peso, a pessoa pode ter muitos pesos.

Classe Pessoa

@Entity
public class Pessoa {

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

	@NotNull
	@NotEmpty
	private String nome;

	@NotNull
	@DateTimeFormat(iso = ISO.DATE)
	@Past
	private Calendar dataNascimento;

	@Digits(integer = 1, fraction = 2)
	private double altura;

	@OneToOne
	private Usuario usuario;

	@NotNull
	@NotEmpty
	private String sexo;

Classe Peso

@Entity
public class Peso {


	@ManyToOne
	@NotNull
	private Pessoa pessoa;

	@Id
	@DateTimeFormat(iso = ISO.DATE, pattern = "yyyy-MM-dd")
	private Calendar data;

	@NotNull
	private double peso;

	@NotNull
	private double imc;

PesoController

@Transactional
	@RequestMapping("registraPeso")
	public String registraPeso(@Valid Peso peso, BindingResult result, @RequestParam("idPessoa") long idPessoa) {

		System.out.println(peso.getData());

		Pessoa pessoa = pessoaDao.findOne(idPessoa);
		peso.setPessoa(pessoa);

		pesoDao.save(peso);

		return "redirect:meusDados";
	}

Erro:

mar 24, 2020 10:29:24 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/controledepeso] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '2020-03-01 03:00:00' for key 'peso.PRIMARY'

O erro ocorre quanto eu tento inserir o peso para outra pessoa, porém numa data que já existe. No caso tentei inserir para a pessoa id 2.

Tabela

mysql> select * from peso;
+---------------------+-------+------+-----------+
| data                | imc   | peso | pessoa_id |
+---------------------+-------+------+-----------+
| 2020-03-01 03:00:00 | 24.51 |   70 |         1 |
| 2020-03-02 03:00:00 | 25.21 |   72 |         1 |
| 2020-03-03 03:00:00 | 23.81 |   68 |         1 |
+---------------------+-------+------+-----------+

15 Respostas

Jonathan_Medeiros

Como está o script de criação da sua tabela que registra os pesos?

Villagram

Bom dia, coloquei um projeto de exemplo baseado no seu post.

pmlm

Se a data é chave primária da tabela, só pode haver um peso por dia, independentemente de quem é a pessoa.

Moreira89

Jonathan, bom dia!

O script do Hibernate que cria as tabelas são:

Hibernate: alter table Peso drop foreign key FK_1aiiko3dkrf1e5nbjnto9dr4h
Hibernate: alter table Pessoa drop foreign key FK_89i5o79j9g0uov0by7xg4bdgp
Hibernate: alter table Role_Usuario drop foreign key FK_1wcv1khmdbce4qa9bmvu81qjp
Hibernate: alter table Role_Usuario drop foreign key FK_lmmlhi2cos4sy9okkofjllbl2
Hibernate: alter table usuario_role drop foreign key FK_qpqh5on1cqa0ktsitg2vhmirv
Hibernate: alter table usuario_role drop foreign key FK_55sbft3wldu0yr078kdq6hwxe
Hibernate: drop table if exists Peso
Hibernate: drop table if exists Pessoa
Hibernate: drop table if exists Role
Hibernate: drop table if exists Role_Usuario
Hibernate: drop table if exists Usuario
Hibernate: drop table if exists usuario_role
Hibernate: create table Peso (data datetime not null, imc double precision not null, peso double precision not null, pessoa_id bigint not null, primary key (data))
Hibernate: create table Pessoa (id bigint not null auto_increment, altura double precision not null, dataNascimento datetime not null, nome varchar(255) not null, sexo varchar(255) not null, usuario_id integer not null, primary key (id))
Hibernate: create table Role (nome varchar(255) not null, primary key (nome))
Hibernate: create table Role_Usuario (Role_nome varchar(255) not null, usuarios_id integer not null)
Hibernate: create table Usuario (id integer not null auto_increment, cadastroAtivado bit not null, dataCadastro datetime not null, email varchar(255) not null, senha varchar(255) not null, primary key (id))
Hibernate: create table usuario_role (usuario_id integer not null, role_id varchar(255) not null)
Hibernate: alter table Peso add constraint FK_1aiiko3dkrf1e5nbjnto9dr4h foreign key (pessoa_id) references Pessoa (id)
Hibernate: alter table Pessoa add constraint FK_89i5o79j9g0uov0by7xg4bdgp foreign key (usuario_id) references Usuario (id)
Hibernate: alter table Role_Usuario add constraint FK_1wcv1khmdbce4qa9bmvu81qjp foreign key (usuarios_id) references Usuario (id)
Hibernate: alter table Role_Usuario add constraint FK_lmmlhi2cos4sy9okkofjllbl2 foreign key (Role_nome) references Role (nome)
Hibernate: alter table usuario_role add constraint FK_qpqh5on1cqa0ktsitg2vhmirv foreign key (role_id) references Role (nome)
Hibernate: alter table usuario_role add constraint FK_55sbft3wldu0yr078kdq6hwxe foreign key (usuario_id) references Usuario (id)
Moreira89

Bom dia!

Então, eu tentei de várias maneiras, mas nenhuma funcionou, que só fosse possível um peso por dia por pessoa. Queria que o id da pessoa fosse parte da chave da tabela peso. Não consegui com as anotações do Hibernate fazer isso ainda…

Moreira89

Bom dia Villagram!

Muito obrigado, vou dar uma olhada nesse projeto.

Villagram

Tranquilo amigo, se estiver usando algum tipo de framework é só postar aqui que crio um repositório de exemplo usando o mesmo.

Jonathan_Medeiros

O problema é que somente a coluna data é a PK, e mesmo tentando inserir pessoas diferentes se a data se repetir a restrição da PK vai barrar a inserção do registro!

Você poderia trabalhar com chave composta (pessoa_id e data), desta forma atenderia a regra que você deseja aplicar, que é um peso para cada pessoa por dia.

Moreira89

Cara, eu não conhecia o Lombok, fui procurar o que significava algumas anotações do projeto que você subiu e acabei dando uma estudada, depois vou procurar mais sobre. MUITO legal, agiliza bastante o desenvolvimento.

Então, eu estou desenvolvendo com Spring e Hibernate.

Moreira89

Então, eu tentei criar a chave composta com estes dois campos, pesquisei algumas coisas no Google, tentei usar algumas anotações, mas não funcionou…

Villagram

O lombok ainda é muito mal visto por algumas empresas, mas ajuda muito na hora de criar as entidades similar ao C# mas com anotações.
Sobre o Spring e Hibernate. Pessoalmente eu acho o hibernate muito lento e uso mais o Eclipselink e com o spring ou springboot você tem muitas facilidades para criar a camada de persistência do projeto.
Caso você quiser/puder postar o projeto no github ou preferir um repositório de exemplo usando o hibernate, posso cria-lo sem problemas. Assim facilita o aprendizado de outras pessoas que poderão ver o seu código ou os exemplos que criarmos aqui.

Moreira89

Claro, vou subir no github e mando o link daqui a pouco.

Eu ainda não estudei o Eclipselink, vou dar uma pesquisada, ver como ele funciona e se consigo criar a chave composta nele mais facilmente.

Jonathan_Medeiros
Solucao aceita

Você pode fazer da seguinte forma, crie uma classe para representar a composição de chaves, depois utilize essa classe como atributo na sua classe Peso representando o Id, no atributo Pessoa você utiliza a anotação @MapsId referenciando o atributo da chave contido dentro da classe que compõe os atributos que formam a PK, dessa forma é possível manter a relação PK/FK de pessoa com peso.

Exemplo:

@Data
@Entity
@Table(name = "pessoa")
public class Pessoa {

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

    private String nome;

}


@Data
@AllArgsConstructor
@NoArgsConstructor
@Embeddable
public class PesoId implements Serializable {

    private Calendar data;

    private Long pessoaId;

}


@Data
@Entity
@Table(name = "peso")
public class Peso {

    @EmbeddedId
    private PesoId id;

    @ManyToOne
    @MapsId("pessoaId")
    @JoinColumn(name = "pessoa_id")
    private Pessoa pessoa;

    private Double peso;

    private Double imc;

}

Fiz uma classe de teste para simular as inserções dos dados, uma inserção simples, e outra tentando inserir 2 vezes o mesmo registro (repetindo data/pessoa) que é onde a restrição de PK é aplicada, e uma com exemplo de consulta.

public class EntityManagerTest {

    protected static EntityManagerFactory entityManagerFactory;
    protected EntityManager entityManager;

    @BeforeClass
    public static void setUpBeforeClass() {
        entityManagerFactory = Persistence.createEntityManagerFactory("SuaUnidadeDePersistencia");
    }

    @AfterClass
    public static void tearDownAfterClass() {
        entityManagerFactory.close();
    }

    @Before
    public void setUp() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @After
    public void tearDown() {
        entityManager.close();
    }

}


public class Teste extends EntityManagerTest {

    @Test
    public void testandoInsertRegistroUnico() {
        entityManager.getTransaction().begin();

        Pessoa pessoa = new Pessoa();
        pessoa.setNome("João da silva");

        entityManager.persist(pessoa);

        Peso peso = new Peso();
        peso.setId(new PesoId(Calendar.getInstance(), pessoa.getId()));
        peso.setPessoa(pessoa);
        peso.setImc(0D);
        peso.setPeso(0D);

        entityManager.persist(peso);
        entityManager.getTransaction().commit();

        entityManager.clear();

        entityManager.find(Peso.class, new PesoId(peso.getId().getData(), peso.getId().getPessoaId())); //Exemplo de consulta
    }

    @Test(expected = Exception.class)
    public void testandoInsertRegistroDuplicado() {
        entityManager.getTransaction().begin();

        Pessoa pessoa = new Pessoa();
        pessoa.setNome("João da silva");

        entityManager.persist(pessoa);

        Peso peso = new Peso();
        peso.setId(new PesoId(Calendar.getInstance(), pessoa.getId()));
        peso.setPessoa(pessoa);
        peso.setImc(0D);
        peso.setPeso(0D);

        entityManager.persist(peso);
        entityManager.getTransaction().commit();

        entityManager.clear();

        entityManager.getTransaction().begin();
        entityManager.persist(peso);
        entityManager.getTransaction().commit();
    }

}
Moreira89

Jonathan,

muito obrigado!

Desta forma a tabela ficou do jeito que eu queria:

mysql> desc peso;
+-----------+----------+------+-----+---------+-------+
| Field     | Type     | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+-------+
| data      | datetime | NO   | PRI | NULL    |       |
| pessoa_id | bigint   | NO   | PRI | NULL    |       |
| imc       | double   | NO   |     | NULL    |       |
| peso      | double   | NO   |     | NULL    |       |
+-----------+----------+------+-----+---------+-------+

mysql> desc pessoa;
+----------------+--------------+------+-----+---------+----------------+
| Field          | Type         | Null | Key | Default | Extra          |
+----------------+--------------+------+-----+---------+----------------+
| id             | bigint       | NO   | PRI | NULL    | auto_increment |
| altura         | double       | NO   |     | NULL    |                |
| dataNascimento | datetime     | NO   |     | NULL    |                |
| nome           | varchar(255) | NO   |     | NULL    |                |
| sexo           | varchar(255) | NO   |     | NULL    |                |
| usuario_id     | int          | YES  | MUL | NULL    |                |
+----------------+--------------+------+-----+---------+----------------+

Agora começou a dar um erro pra fazer login, vou tentar corrigir e depois testo certinho.

Obrigado pela ajuda!

Moreira89

Jonathan,

Corrigi o outro problema que deu e ajustei os métodos de gravar.

Está funcionando corretamente.

Muito obrigado pela ajuda novamente!

Criado 25 de março de 2020
Ultima resposta 25 de mar. de 2020
Respostas 15
Participantes 4