Erro ao inserir com Hibernate

15/03/2010

Olá,

Estou com dificuldade pra fazer um cadastro com hibernate.

Eis o cenário:
Tenho uma classe chamada ControleDocumentosEmpresa que se relaciona com as classes Cliente e Navio, além de outros relacionamentos.

Preciso cadastrar um documento, porem esse mesmo documento poderá ou não ter um navio, ou seja, dependendo do tipo de inserção minha fk na classe ControleDocumentosEmpresa poderá ser null...

Ao tentar cadastrar um documento sem informar o navio, estou recebendo o seguinte erro:

object references an unsaved transient instance - save the transient instance before flushing: modelo.Navio



Quando cadastro um documento com id de um navio já existente no banco, funciona legal.

Como sou iniciante em hibernate, esta dificil saber o que fazer...


Abaixo seguem minhas classes ...

ControleDocumentosEmpresa

public class ControleDocumentosEmpresa extends org.apache.struts.action.ActionForm implements Serializable {
   
private static final long serialVersionUID = 1L;

    public ControleDocumentosEmpresa(){
    ConvertUtils.register(new ConversorData(), Date.class);
    }
   
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column()
private int controle_cod;
@Column()
@Temporal(javax.persistence.TemporalType.DATE)
private Date data_emissao;
@Column()
@Temporal(javax.persistence.TemporalType.DATE)
private Date data_validade;
@Column()
@Version
private Timestamp update_em;
@Column()
private String update_por;
@Column()
private String comentarios;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cliente_cod")
@Fetch(FetchMode.JOIN)
private Cliente cliente = new Cliente();


@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="reparticao_cod")
@Fetch(FetchMode.JOIN)
private Reparticao reparticao = new Reparticao();

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="doc_cod")
@Fetch(FetchMode.JOIN)
private Doc doc = new Doc();


@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cod")
@Fetch(FetchMode.JOIN)
private Navio navio = new Navio();

//getters e setters



Classe Navio

public class Navio extends org.apache.struts.action.ActionForm implements Serializable {
   
    private static final long serialVersionUID = 1L;
   
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column()
private int cod;
@Column()
private String nome;
@Column(unique=true)
private String imo;
@Column()
private String bandeira;
@Column()
private String numeroegistro;
@Column()
private String inscricaocp;
@Column()
private String portoregistro;
@Column()
private String licconstrucao;
@Column()
private String classeembarcacao;
@Column()
private String tipoembarcacao;
@Column()
private String irin;
@Column()
private String classificadora;
@Column()
private String comprimento;
@Column()
private String  boca;
@Column()
private String pontal;
@Column()
private String calado;
@Column()
private String ab;
@Column()
private String al;
@Column()
private String tpb;
@Column()
private String construtor;
@Column()
private String batquilha;
@Column()
private String entradaeaceitacao;
@Column()
private String materialconstrucao;
@Column()
private String combustivel;
@Column()
private String propulsao;
@Column()
private String motores;
@Column()
private String potenciamotores;
@Column()
private String fabricantemotor;
@Column()
private String fretador;
@Column()
private String afretador;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cliente_cod")
@Fetch(FetchMode.JOIN)
@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Cliente cliente = new Cliente();


@OneToMany(mappedBy="navio", fetch=FetchType.LAZY)
private List<ControleDocumentosEmpresa> controle;

//getters e setters

Cleiton Tavares.

Cleiton Tavares.

Curtidas 0

Respostas

Cleiton Tavares.

Cleiton Tavares.

15/03/2010

ahhhhh ....

esse é o metodp que salva o documento


public void novoControle(ControleDocumentosEmpresa controle)throws Exception{
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle);
transaction.commit();
session.flush();
}
GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Oi Cleyton,

bom: vamos por partes:
a primeira coisa que você deve fazer é modificar como está anotando o atributo Navio da sua classe.

Ao invés de:
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cod")
@Fetch(FetchMode.JOIN)
private Navio navio = new Navio();


Faça assim:
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cod", nullable=true)
@Fetch(FetchMode.JOIN)
private Navio navio = new Navio();

Isto irá instruir o Hibernate a manter o valor null na tabela caso necessário. Lembre-se de verificar na sua tabela se ela está permitindo armazenar o valor null ok?


Outro ponto: a excessão com a qual você está topando ocorre pela seguinte razão: você está persistindo a classe ControleDocumentosEmpres, que já referencia uma instância de Navio. No entanto, a sua instância de Navio ainda não foi persistida na base de dados. Como o SessionFactory do Hibernate vai iniciar a persistência a partir da classe ControleDocumentosEmpresa, ele irá verificar que a classe Navio ainda não foi persistida e, em seguida, levantar esta excessão.
GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

Henrique,


Eu teria que persistir a entiadade Navio dentro de ControleDocumentoEmpresa neh?

Tentei assim e não recebi o erro, porém foi salvo um novo navio no banco com tudo em branco.

public void novoControle(ControleDocumentosEmpresa controle)throws Exception{
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.clear();
session.save(controle);
transaction.commit();
session.flush();
}
GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Oi Cleiton,

na realidade, o Hibernate por default sempre pré-compreende que os objetos relacionados já se encontram pré-persistidos na base de dados.

A melhor prática portanto é você primeiro salvar estes objetos relacionados, caso presentes (no seu caso, por exemplo, a classe Navio) para depois persistir os objetos que os referenciam.
GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

Henrique,

Esqueci de atualizar o codigo abaixo. É assim q estou fazendo:

Quando faço isso ele cria um novo navio no banco...com todos os campos vazios

public void novoControle(ControleDocumentosEmpresa controle)throws Exception{
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle.getNavio());
session.save(controle);
transaction.commit();
session.flush();
}


GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Oi Cleiton,

estranho isto: o mapeamento das colunas está certo? Digo: os nomes das colunas estão exatamente com o mesmo nome que os atributos?

Sugestão, passe o nome dos campos para as colunas incluindo o atributo NAME na anotação Column. Apesar de por default o JPA buscar por campos com o mesmo nome dos atributos, é sempre uma boa prática incluí-los na anotação também.
GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

Henrique,

Os atributos estão com os mesmos nomes dos campos no banco sim.

No log, quando faço o cadastro do documento fica assim:

Hibernate: insert into navio (ab, afretador, al, bandeira, batquilha, boca, calado, classeembarcacao, classificadora, cliente_cod, combustivel, comprimento, construtor, entradaeaceitacao, fabricantemotor, fretador, imo, inscricaocp, irin, licconstrucao, materialconstrucao, motores, nome, numeroegistro, pontal, portoregistro, potenciamotores, propulsao, tipoembarcacao, tpb) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Hibernate: insert into controle_documentos_empresa (cliente_cod, comentarios, data_emissao, data_validade, doc_cod, cod, reparticao_cod, update_em, update_por) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
 

Esta certo ele fazer esse insert de navio mesmo?

ta complicado .... : (


GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Cleiton,

sim: isto pode acontecer. Faça o seguinte: verifique se no seu código fonte o método get que retorna o Navio está retornando um objeto ao invés de null.

Se estiver, é bem possível que o Hibernate o esteja persistindo, pois o seu comportamento consistirá em percorrer o grafo de dependências da classe a ser persistida e, encontrando uma classe dependente, a persistir também.

No entanto, se esta estiver com seu valor definido como null isto não deverá ocorrer.
GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

Bom dia Henrique,

Com meu getNavio rettornando null, recebo a excessão:

attempt to create saveOrUpdate event with null entity


public void novoControle(ControleDocumentosEmpresa controle)throws Exception{
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle.getNavio());
session.clear();
session.save(controle);
transaction.commit();
session.flush();
}













public class ControleDocumentosEmpresa extends org.apache.struts.action.ActionForm implements Serializable {
   
private static final long serialVersionUID = 1L;

    public ControleDocumentosEmpresa(){
    ConvertUtils.register(new ConversorData(), Date.class);
    }
   
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column()
private int controle_cod;
@Column()
@Temporal(javax.persistence.TemporalType.DATE)
private Date data_emissao;
@Column()
@Temporal(javax.persistence.TemporalType.DATE)
private Date data_validade;
@Column()
@Version
private Timestamp update_em;
@Column()
private String update_por;
@Column()
private String comentarios;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cliente_cod")
@Fetch(FetchMode.JOIN)
private Cliente cliente = new Cliente();


@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="reparticao_cod")
@Fetch(FetchMode.JOIN)
private Reparticao reparticao = new Reparticao();

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="doc_cod")
@Fetch(FetchMode.JOIN)
private Doc doc = new Doc();


@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="cod", nullable=true, updatable=true)
@Fetch(FetchMode.JOIN)
private Navio navio = new Navio();


    public int getControle_cod() {
        return controle_cod;
    }

    public void setControle_cod(int controle_cod) {
        this.controle_cod = controle_cod;
    }

    public Date getData_emissao() {
        return data_emissao;
    }

    public void setData_emissao(Date data_emissao) {
        this.data_emissao = data_emissao;
    }

    public Date getData_validade() {
        return data_validade;
    }

    public void setData_validade(Date data_validade) {
        this.data_validade = data_validade;
    }

    public Timestamp getUpdate_em() {
        return update_em;
    }

    public void setUpdate_em(Timestamp update) {
        this.update_em = update_em;
    }

    public String getUpdate_por() {
        return update_por;
    }

    public void setUpdate_por(String update_por) {
        this.update_por = update_por;
    }

    public String getComentarios() {
        return comentarios;
    }

    public void setComentarios(String comentarios) {
        this.comentarios = comentarios;
    }

    public Cliente getCliente() {
        return cliente;
    }

    public void setCliente(Cliente cliente) {
        this.cliente = cliente;
    }

    public Reparticao getReparticao() {
        return reparticao;
    }

    public void setReparticao(Reparticao reparticao) {
        this.reparticao = reparticao;
    }

    public Doc getDoc() {
        return doc;
    }

    public void setDoc(Doc doc) {
        this.doc = doc;
    }

@Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ControleDocumentosEmpresa other = (ControleDocumentosEmpresa) obj;
        if (this.controle_cod != other.controle_cod) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 83 * hash + this.controle_cod;
        return hash;
    }

    public Navio getNavio() {
        return null;
    }

    public void setNavio(Navio navio) {
        this.navio = navio;
    }
   
  
}
GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Oi Cleiton.

Isto está acontecendo porque você reescreveu o seu método getNavio() para sempre retornar null.

GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Outro ponto Cleiton:

dei uma relida neste seu código:


public void novoControle(ControleDocumentosEmpresa controle)throws Exception{
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle.getNavio());
session.clear();
session.save(controle);
transaction.commit();
session.flush();
}

O que acontece: o Hibernate não executa sempre os comandos imediatamente na base de dados, mas sim após acumular uma quantidade x de comandos, que, por questão de performance, são executados em lote no SGBD.

Então, o que acontece: vou analisar seu código linha a linha ok?

session = HibernateUtil.getSession();
transaction = session.beginTransaction();

// Aqui você terá um erro, pois seu método está sempre retornando null
session.save(controle.getNavio());

Ok, o Hibernate mandou persistir o navio (mas talvez ainda não tenha enviado o comando para a base de dados)

session.clear();
Aqui você limpou da sessão a instância do barco que acabou de ser persistido

session.save(controle);
Como você limpou a sessão, no momento em que barco for salvo, você poderá obter uma excessão

transaction.commit();

// O método flush obriga a sessão a executar os comandos na base de dados
session.flush();


Sugestão: não escreva código como este. Se você executar só o session.save(controle), duas coisas podem acontecer:
1. Se seu navio não estiver persistido na base de dados uma excessão será disparada informando que você está persistindo um objeto que possui uma dependência ainda não persistida.

2. Se o seu navio já estiver persistido, o processo em teoria executará sem problemas.


GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

Olá Henrique,

Pra resmior bem, vou usar sua sugestão:


Sugestão: não escreva código como este. Se você executar só o session.save(controle), duas coisas podem acontecer:
1. Se seu navio não estiver persistido na base de dados uma excessão será disparada informando que você está persistindo um objeto que possui uma dependência ainda não persistida.

Quando o navio não existe na base de dados, com o metodo como na forma abaixo, ele lança a excessão:
object references an unsaved transient instance - save the transient instance before flushing: modelo.Navio

session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle);
transaction.commit();
session.flush();


se adicionada a linha session.save(controle.getNavio()); recebo a excessão:
object references an unsaved transient instance - save the transient instance before flushing: modelo.Cliente


Agora se deixar o método assim:

session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle.getNavio());
session.save(controle.getNavio().getCliente());//cliente é relaciona com Navio
session.save(controle);
transaction.commit();
session.flush();


São gravados um cliente em branco e um navio em branco relacionado a ele.

2. Se o seu navio já estiver persistido, o processo em teoria executará sem problemas.

Testado. Funciona certinho quando o navio já existe na base de dados.



Pelo que entendi, as linhas abaixo são necessárias
session.save(controle.getNavio());
session.save(controle.getNavio().getCliente());

Mas como fazer pro hibernate não gravar esses dados em branco?


Putz... é a ulima parte do projeto ... : o


GOSTEI 0
Henrique Weissmann

Henrique Weissmann

15/03/2010

Oi Cleiton.

Em casos como estes, a solução é a seguinte:

* Mapeie a associação para que aceite valores nulos (basta incluir o atributo nullable=true na anotação Column

* Verifique no seu código fonte se você não está acidentalmente criando uma nova instancia de Navio. Dou aqui uma sugestão: escreva um teste unitário com este código e o execute isolado do resto do sistema. Se executar sem problema, é sinal de que há a possibilidade disto estar ocorrendo.

* Caso vá salvar o navio primeiro, procure salvá-lo em sessões diferentes, lembrando-se de executar o método flush sempre. Como mencionei, isto garante que os comandos sejam enviados imediatamente para a base de dados.
GOSTEI 0
Cleiton Tavares.

Cleiton Tavares.

15/03/2010

oLÁ hENRIQUE

Consegui resolver assim:

Alterei o mapeamento na Classe ControleDocumentosEmpresa para:
  
   @ManyToOne 
   @JoinColumn(name="cod", referencedColumnName="cod", nullable=true)  
   private Navio navio;


e deixei meu método assim:

session = HibernateUtil.getSession();
transaction = session.beginTransaction();
session.save(controle);
transaction.commit();
session.flush();


Agora esta funcionando tudo certinho


Valew!!!
GOSTEI 0
POSTAR