Duvida.. JPA + Hibernate + Herança

11 respostas
L

Olá pessoal,

Estou com uma duvida para fazer o mapeamento JPA do seguinte relacionamento


“Pode existir Assinantes e pode existir Promotor, um Promotor é um Assinante com mais atributos”

Os códigos abaixo são as classes que eu consegui mapear até agora…
Minha duvida é em relação à como a PFK de Promotor irá se comportar no código…
Por que a maioria dos exemplos que eu vi em pesquisas, a classe mãe é abstract, mas neste caso eu não posso deixar minha classe Assinante como abstract, pois eu posso ter um objeto Assinante só em alguns casos que vou ter criar objetos Promotor…
Então pensei em deixar no construtor de Promotor informando somente o assinanteId (através de um super), mas estou com duvida… não seria melhor colocar um objeto Assinante dentro de Promotor ?

Qual seria a melhor saida para esse tipo de relacionamento… ahh se tiver errado o relacionamento podem canetar tbm…

Outra duvida que acabou de surgir é em relação as Daos, será necessário 2 Daos ? Uma para Assinante e uma para Promotor? Ou como há herança pode ser feito uma DAO só para as duas classes ?

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name = "assinante")
public class Assinante implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "assinante_id")
    private Long assinanteId;
}
@Entity
@Table(name = "promotor")
public class Promotor extends Assinante implements Serializable {
    private static final long serialVersionUID = 1L;

    @Basic(optional = false)
    @Column(name = "promotor_telefone")
    private String promotorTelefone;
    @Basic(optional = false)
    @Column(name = "promotor_celular")
    private String promotorCelular;
    @Basic(optional = false)
    @Column(name = "promotor_cpf_cnpj")
    private String promotorCpfCnpj;

    public Promotor() {
    }

    public Promotor(Long assinanteId) {
        super.setAssinanteId(assinanteId);
    }
}

11 Respostas

Andre_Brito

Então… Um Promotor é um Assinante? Digo, na regra de negócio, no mundo real do seu sistema, um Promotor é um Assinante (não pensando em termos de código)? Se ele não for, você pode ter uma superclasse (Pessoa, por exemplo) com os atributos que o Assinante tem hoje. Aí o Assinante e o Promotor são subclasses.

Se ele (Promotor) for um Assinante, aí acredito que do jeito que está codificado está correto. Aí tudo vai depender de como você configura a sua herança. Isso depende bastante do contexto, mas uma boa é fazer uma única tabela para toda a herança, porque quem vai controlar a parte de Id é a superclasse.

leolimas:
Os códigos abaixo são as classes que eu consegui mapear até agora…
Minha duvida é em relação à como a PFK de Promotor irá se comportar no código…
Por que a maioria dos exemplos que eu vi em pesquisas, a classe mãe é abstract, mas neste caso eu não posso deixar minha classe Assinante como abstract, pois eu posso ter um objeto Assinante só em alguns casos que vou ter criar objetos Promotor…

Correto, tudo certo até aqui.

Não entendi o porquê de fazer isso. Você não precisa deixar no construtor de Promotor informando o assinanteId porque quem vai cuidar disso é a Annotation GeneratedValue.

Isso você responde respondendo a primeira pergunta que fiz (na parte das regras de negócio do seu problema).

Isso depende bastante… Tem gente que gosta de deixar bem alto nível e acaba 1 genérico ( DAO ), 1 pro Hibernate ( DAOHibernate ) e outro pra entidade ( DAOHibernateEntidade ), com as buscas relacionadas somente àquela entidade.

L

Olá Andre,

Respondendo a questão, sim o Promotor é um Assinante…

Valeu pela ajuda, rsrs não sou muito bom em modelagem de dados ainda…
Vou tirar o assinanteId do construtor da classe Promotor, e fazer um teste para ver se funciona…
Sobre usar SINGLE_TABLE, andei lendo em varios posts que não é uma boa saida, que me diz?

Mas novamente, obrigado pela resposta… vou testar aqui :wink:

L

André…

Acabei de lembrar, por que eu deixei o assinanteId no Promotor…

Olha minha duvida…

Na regra, o Promotor é um Assinante que já existe e em um dado momento ele faz um “upgrade” para Promotor…
Então qual seria a lógica correta?

  1. Ao criar um Promotor usa-se o registro já existente em Assinante?
  2. Ao criar um Promotor duplica-se informação, criando um novo Assinante com Promotor?
B

A estratégia SINGLE_TABLE tem a vantagem de ser a mais rápida de todas as outras pois não existe a necessidade de fazer join, vc precisara utilizar descriminadores…O ruim dela é que sua tabela fica muuuuito grande e suas subclasses não podem ter atributos obrigatórios…

L

Pessoal não está dando certo não…

Quando é para salvar um Assinante está ocorrendo corretamente, agora quando mando salvar um Promotor está dando o seguinte erro…
O erro que eu identifiquei foi que está tentando adicionar um registro com ID 1 na tabela Assinante… mas não consegui identificar a causa deste erro…

org.hibernate.exception.ConstraintViolationException: could not insert: [br.eventos.model.Promotor]
	org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
	org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
	org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:40)
	org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
	org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
	org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
	org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
	org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
	org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
	org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
	org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
	org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
	org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
	org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
	org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
	br.eventos.dao.PromotorDao.salva(PromotorDao.java:23)
	org.apache.jsp.index_jsp._jspService(index_jsp.java:102)
	org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)


root cause 

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'assinante_idx'
	sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	java.lang.reflect.Constructor.newInstance(Constructor.java:513)
	com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
	com.mysql.jdbc.Util.getInstance(Util.java:381)
	com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)
	com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
	com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3515)
	com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3447)
	com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1951)
	com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2101)
	com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2554)
	com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1761)
	com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2046)
	com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1964)
	com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1949)
	org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:73)
	org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:33)
	org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
	org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
	org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
	org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
	org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
	org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
	org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
	org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
	org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
	org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
	org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
	org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
	org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
	br.eventos.dao.PromotorDao.salva(PromotorDao.java:23)
	org.apache.jsp.index_jsp._jspService(index_jsp.java:102)
	org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)

Classe Assinante

package br.eventos.model;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 *
 * @author leonardo
 */
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "assinante")
public class Assinante implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "assinante_id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "assinante_nome")
    private String nome;
    @Basic(optional = false)
    @Column(name = "assinante_apelido")
    private String apelido;
    @Basic(optional = false)
    @Column(name = "assinante_email")
    private String email;
    @Basic(optional = false)
    @Column(name = "assinante_senha")
    private String senha;
    @Column(name = "assinante_foto")
    private String foto;
    @Basic(optional = false)
    @Column(name = "assinante_sexo")
    private String sexo;
    @Basic(optional = false)
    @Column(name = "assinante_data_nascimento")
    @Temporal(TemporalType.TIMESTAMP)
    private Date dataNascimento;
    @Basic(optional = false)
    @Column(name = "assinante_data_cadastro")
    @Temporal(TemporalType.TIMESTAMP)
    private Date dataCadastro;
    @Column(name = "assinante_data_exclusao")
    @Temporal(TemporalType.TIMESTAMP)
    private Date dataExclusao;

    public Assinante() {
    }

    public Assinante(Long id) {
        this.id = id;
    }

    public Assinante(Long id, String nome, String apelido, String email, String assinanteSenha, String assinanteSexo, Date dataNascimento, Date dataCadastro) {
        this.id = id;
        this.nome = nome;
        this.apelido = apelido;
        this.email = email;
        this.senha = assinanteSenha;
        this.sexo = assinanteSexo;
        this.dataNascimento = dataNascimento;
        this.dataCadastro = dataCadastro;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getApelido() {
        return apelido;
    }

    public void setApelido(String assinanteApelido) {
        this.apelido = assinanteApelido;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String assinanteEmail) {
        this.email = assinanteEmail;
    }

    public String getSenha() {
        return senha;
    }

    public void setSenha(String senha) {
        this.senha = senha;
    }

    public String getFoto() {
        return foto;
    }

    public void setFoto(String foto) {
        this.foto = foto;
    }

    public String getSexo() {
        return sexo;
    }

    public void setSexo(String sexo) {
        this.sexo = sexo;
    }

    public Date getDataNascimento() {
        return dataNascimento;
    }

    public void setDataNascimento(Date dataNascimento) {
        this.dataNascimento = dataNascimento;
    }

    public Date getDataCadastro() {
        return dataCadastro;
    }

    public void setDataCadastro(Date dataCadastro) {
        this.dataCadastro = dataCadastro;
    }

    public Date getDataExclusao() {
        return dataExclusao;
    }

    public void setDataExclusao(Date dataExclusao) {
        this.dataExclusao = dataExclusao;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Assinante)) {
            return false;
        }
        Assinante other = (Assinante) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "br.eventos.model.Assinante[assinanteId=" + id + "]";
    }
}

Classe Promotor

package br.eventos.model;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "promotor")
public class Promotor extends Assinante implements Serializable {

    private static final long serialVersionUID = 1L;
    @Basic(optional = false)
    @Column(name = "promotor_telefone")
    private String telefone;
    @Basic(optional = false)
    @Column(name = "promotor_celular")
    private String celular;
    @Basic(optional = false)
    @Column(name = "promotor_cpf_cnpj")
    private String cpfCnpj;

    public Promotor() {
    }

    public Promotor(String telefone, String celular, String cpfCnpj) {
        this.telefone = telefone;
        this.celular = celular;
        this.cpfCnpj = cpfCnpj;
    }

    public String getTelefone() {
        return telefone;
    }

    public void setTelefone(String telefone) {
        this.telefone = telefone;
    }

    public String getCelular() {
        return celular;
    }

    public void setCelular(String celular) {
        this.celular = celular;
    }

    public String getCpfCnpj() {
        return cpfCnpj;
    }

    public void setCpfCnpj(String cpfCnpj) {
        this.cpfCnpj = cpfCnpj;
    }
}
B

Qual banco de dados vc está utilizando SQL Server??

L

Não… é MySQL mesmo… rsrs

B

Muda a estratégia da sua chave primária para AUTO e faz um teste…Desta forma o banco de dados irá fazer o incremento dela automaticamente… Nunca utilizei o IDENTITY, mas se não me engano é necessário fazer algumas configurações adicionais na coluna…

L

Consegui fazer, mas não ficou do modo que eu queria não…

Tava dando erro por causa das chaves naturais, por exemplo cpf e email, na forma que eu codifiquei e postei aqui, o que está acontecendo é… toda vez que crio um objeto Promotor, ele está criando um registro em Assinante e Promotor ao mesmo tempo, então se já existe por exemplo o email cadastrado em Assinante, está dando erro…

E o que realmente preciso é:

  1. Criei um obj Assinante, blz até ai tudo bem salvou e criou um registro no banco
  2. Agora na Tabela Promotor, eu preciso recuperar esse Assinante que eu já tenho salvo e criar um registro em Promotor…

Eu estava pensando em tirar a herança e colocar um objeto Promotor dentro de Assinante, ou teria uma forma de utilizar herança sem que ele um novo registro, tipo reaproveitando o Assinante que já existe, ou é isso mesmo, tenho de recuperar o Assinante existente, apagar o registro, e criar o obj Promotor ?

Valeu

B

Amigo isso ai que vc falou não tem nada haver com o erro…Toda vez que você persistir um Promotor SEMPRE será criado um Assinante pelo fato de você estar utilizando o mapeamento de herança(@Inheritance)…A chave que foi violada no seu erro foi a chave primária(seu id), que não deve ter duplicatas e não seu cpf ou email…

Quando você recupera um Promotor automaticamente você terá TODAS as informações de um Assinante(email,nome…),pelo fato de estar utilizando o mapeamento de herança…

É possível sim…Existe uma anotação que se chama @Embeddable que é utilizada para inserir um objeto separado em uma entidade e reaproveitar seus atributos através de @AttributeOverrides… Da uma lidinha na documentação do Hibernate ou JPA para conhecer melhor as anotações e seus comportamentos para decidir o que vc vai querer …

L

Humm, acho que não é bem isto que eu quero…
Pelo que eu li eu teria uma só classe representando as 2 tabelas…
Na verdade eu até quero ter 2 classes, só queria saber se há como reutilizar o registro já criado anteriormente em Assinante… ou seja se há como criar um Promotor com os dados de um Assinante que já existe sem antes apagar o registro na tabela Assinante…

Criado 10 de janeiro de 2010
Ultima resposta 11 de jan. de 2010
Respostas 11
Participantes 3