Erro ao gerar FK com Hibernate

15 respostas Resolvido
javawebhibernate
M

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

J

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

V

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

P

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

M

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)
M

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…

M

Bom dia Villagram!

Muito obrigado, vou dar uma olhada nesse projeto.

V

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

J

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.

M

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.

M

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…

V

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.

M

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.

J
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();
    }

}
M

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!

M

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