[RESOLVIDO] Hibernate - OneToOne - optional

Olá, bom dia pessoal!

Tenho uma classe EQ, que tem uma SolicitacaoMotorista.
Estou usando VRaptor 3

@Entity
public class EQ {

	@Id @GeneratedValue(generator="seq_eq_id", strategy=GenerationType.SEQUENCE)
	@SequenceGenerator(name="seq_eq_id", sequenceName="seq_eq_id", allocationSize=1)
	private long idEQ;
	
	@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})
	@JoinColumn
	private SolicitacaoMotorista solicitacaoMotorista;
}

Quando envio o formulário sem preencher a solicitação é lançado a expection org.hibernate.TransientObjectException reclamando da SolicitacaoMotorista.
Pelo que pesquisei, essa é a unica config que preciso fazer para dizer que um objeto é opcional no relacionamento.

Alguém pode me dar uma dica?

Desde já agradeço!

org.hibernate.TransientObjectException provavelmente é pq vc está tentando salvar EQ sem ter salvo antes a SolicitacaoMotorista.

abrassss

Boa Tarde.

Então, de acordo com seu mapeamento,

@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})  
@JoinColumn  
private SolicitacaoMotorista solicitacaoMotorista; 

Você só ira salvar uma SolicitacaoMotorista quando estiver inserindo um EQ. Ou seja, como o relacionamento é opcional, voce pode gravar uma EQ sem uma solicitação e depois atualizar essa EQ adicionando uma solicitação, porém, de acordo com o mapeamento, quando ele atualizar a EQ, ele não irá salvar ou atualizar uma solicitação.

Não sei se essa regra existe no seu projeto. Mas parece meio estranho.

Verifique se quando voce inserir uma EQ e tiver uma solicitacao populada se está gravando corretamente ou e gera a mesma exceção. Não pode ser uma atualização de EQ.

Para atualizar ou inserir uma solicitacao quando estiver atualizando uma EQ, basta adicionar o cascade

CascadeType.MERGE

Acho que é isso.

[]'s

Mais um detalhe …

Se os seus imports de mapeamento, mas especificamente o import do Cascade forem

import javax.persistence.CascadeType

Na hora de inserir ou atualizar, você deve utilizar os métodos persit e merge, respectivamente.

Se você utilizar um método específico do provider, por exemplo, save ou update, o cascade não vai funcionar, se você importou o CascadeType da jpa, como acima. Se for um import do proprio provider, ai beleza.

[]'s

o alex.brito tem razao, nao tinha me atendado para o CascadeType.

com as explicacoes dele creio que ira funcionar.

abrasss

Obrigado Galera.

[quote="]alex.brito"]
Você só ira salvar uma SolicitacaoMotorista quando estiver inserindo um EQ. Ou seja, como o relacionamento é opcional, voce pode gravar uma EQ sem uma solicitação e depois atualizar essa EQ adicionando uma solicitação, porém, de acordo com o mapeamento, quando ele atualizar a EQ, ele não irá salvar ou atualizar uma solicitação.
[/quote]

Então cara, ele ta salvando e atualizando sim, normal, com esse mapeamento.
O problema é quando eu não preencho a solicitação.

E estou importando sim o CascadeType assim

Eu tenho um DAO “genérico”, dessa maneira

public abstract class DAO<T> implements Repository<T> {

	private Session session;
	
	@SuppressWarnings("rawtypes")
	private Class persistentClass;
	
	@SuppressWarnings("rawtypes")
	public DAO(Session session, Class persistentClass) {
		this.session = session;
		this.persistentClass = persistentClass;
	}

	public void adiciona(T t) throws ErroAdicionarException {
		try {
			this.session.save(t);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new ErroAdicionarException("Não foi possível adicionar!");
		}
	}

	public void atualiza(T t) throws ErroAtualizarException {
		try {
			this.session.update(t);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new ErroAtualizarException("Não foi possível atualizar!");
		}
	}

	public void remove(long id) throws ErroRemoverException {
		try {
			this.session.delete(this.session.load(this.persistentClass, id));
		} catch (Exception e) {
			e.printStackTrace();
			throw new ErroRemoverException("Não foi possível remover!");
		}
	}
}

Eu tenho uma classe EqDAO que estende esse DAO de cima.
Nesse caso então eu teria que sobrescrever o método adicionar e atualizar pra colocar o merge?

O que é melhor, eu importar do javax.persistence ou do org.hibernate.annotations?
Não compreendi ainda as diferenças.

Ele consegue inserir e atualizar a Solicitacao só com aquele mapeamento ?? sem você fazer isso no “braço” ?? Agora fiquei confuso … hehehehe.
Até onde eu sei, ele só irá fazer alguma coisa com a Solicitação que está dentro da EQ, somente quando você inserir(PERSIST) ou buscar(REFRESH) uma EQ.

Então, depende.

Depende do seu projeto, dos seus requisitos, pode até depender do seu cliente.

O que eu penso:
Se você importar o cascade do "javax.persistence.* " Você está seguindo a especificação JPA. Assim, ficaria livre para escolher qualquer provider.
Mas em contra partida, você não pode utilizar os métodos save e update do hibernate, muito menos o saveOrUpdate, porque a Cascade não irá “interceptar” essas ações, ele espera algo como persist e merge.

Em teoria, se você fizer todo o seu DAO, seguindo a especificação JPA, você ficará livre para escolher qualquer provider (Hibernate, EclipseLink, OpenJPA, etc).

Isso é interessante pra você / seu projeto ??? Se sim, siga a especificação. Se não, utilize o Hibernate, pois ele tem muitos recursos extras.

Agora, a nível de teste, troca os imports para os do hibernate e verifique se funciona. Se funcionar, você mantém o DAO genérico.
Se você continuar com o import do javax.persistence, você pode mudar o save e o update no próprio DAO generico, em teoria, não irá quebrar nada. Tudo tem que continuar funcionando.

Acho que é isso.

[]'s

Consegue sim, eu só recebo no meu controller, no método adiciona/atualiza uma EQ, e delego pro DAO, daquela maneira que mostrei mesmo rs
Quem seta a Solicitação na EQ é o próprio VRaptor, não?

E se eu troco os imports do javax.persistence pro do hibernate da erro de compilação, o eclipse logo avisa

E uma coisa eu acho que não entendi.
Eu teria que mudar todos os imports do javax.persistence pro do hibernate?
Porque algumas annotations só tem no javax.persistence

Então, nunca trabalhei com o vRaptor …

Sobre o mapeamento, voce pode tentar assim:

@OneToOne(optional=true, fetch=FetchType.LAZY)  
@JoinColumn  
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private SolicitacaoMotorista solicitacaoMotorista; 

Mas primeiro, acho mais facil, voce mudar o método adiciona para usar o persist e o método atualiza para usar o merge. Essa alteralção não deve gerar problemas no restante do projeto.

O import que deve mudar é só o do cascade mesmo …

Vê ai o que você acha melhor.

[]'s

Ok, vou implementar aqui.

Sabe me dizer a diferença entre eu fazer

@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn
private SolicitacaoMotorista solicitacaoMotorista;

e assim

@OneToOne(optional=true, fetch=FetchType.LAZY)
@JoinColumn
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private SolicitacaoMotorista solicitacaoMotorista;

O que muda quando eu uso diretamente o @Cascade? Só o pacote mesmo?

[quote]Ok, vou implementar aqui.

Sabe me dizer a diferença entre eu fazer
view plaincopy to clipboardprint?

@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})  
@JoinColumn  
private SolicitacaoMotorista solicitacaoMotorista;  

e assim

view plaincopy to clipboardprint?

@OneToOne(optional=true, fetch=FetchType.LAZY)  
@JoinColumn  
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})  
private SolicitacaoMotorista solicitacaoMotorista;  

O que muda quando eu uso diretamente o @Cascade? Só o pacote mesmo?[/quote]

Isso mesmo, basicamente dentro da annotation @OneToOne ele espera um cascade do pacote … javax.persistence … que tem as operações basicas de CRUD {MERGE, PERSIST, REMOVE, REFRESH, ALL}.

Por isso, quando você tentou passar o cascade do hiberante o eclipse “xiou”.

Já o @Cascade é do hibernate e com ele você tem mais opções: {PERSIST, MERGE, REMOVE, REFRESH, DELETE, SAVE_UPDATE, REPLICATE, DELETE_ORPHAN, LOCK, EVICT}

Deu pra entender mais ou menos ???

[]'s

Lembrando, (eu acho) que dá pra mesclar os dois. Exemplo:

@OneToMany( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@Cascade(org.hibernate.annotations.CascadeType.REPLICATE)
public Collection<Employer> getEmployers()

fonte: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/

[]'s

bruno vc já tentou como o alex.brito falou ?

@OneToOne(optional=true, fetch=FetchType.LAZY)    
@JoinColumn    
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})  
private SolicitacaoMotorista solicitacaoMotorista;   

?

Implementei.

Mas, se comportou de uma maneira que não é a que eu quero.

@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private SolicitacaoMotorista solicitacaoMotorista;

Quando o mapeamento estava assim e eu não preencho a Solicitação, ele seta tudo nulo na tabela de solicitação.

@OneToOne(optional=true, fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn
@Cascade({org.hibernate.annotations.CascadeType.MERGE})
private SolicitacaoMotorista solicitacaoMotorista;

Já assim, quando não preencho a solicitação, ele grava uma nova solicitação na tabela, com todas colunas nulas.

Uma coisa que não disse no começo, mas acho que é isso que faz a diferença. As solicitações já estarão cadastradas, no form da EQ, eu só vou atrela-las.
No form da EQ então eu só preciso do Id da Solicitação, não preciso de todas informações.

caso todas as solicitacoes existam vc poderia fazer o seguinte:

tire o cascade do model

associe a solicitacao a eq e salve a eq.

renanreismartins, elas existem já sim no banco.

Como assim tirar a associação do model e associar a solicitação a EQ ?

Isso já é o que estou fazendo, não?

nao eh tirar a associação… é tirar o cascade.

pq vc quer cascade ai nessa associação ?

abrasss

Eu só preciso relacionar uma solicitação já cadastrada com uma EQ, e esse relacionamento é opcional.
Será que não preciso do Cascade?

Bom, tirei o cascade, e ele da a mesma Exception do começo, a TOE.

Inclusive, obrigado pela Ajuda!

[quote]Uma coisa que não disse no começo, mas acho que é isso que faz a diferença. As solicitações já estarão cadastradas, no form da EQ, eu só vou atrela-las.
No form da EQ então eu só preciso do Id da Solicitação, não preciso de todas informações. [/quote]

Com essa informação, concordo com o renanreismartins. Acredito que o cascade não é o caso.

O que você precisa e recuperar a Solicitação antes de gravar a EQ.

Eu estou imaginando que essa Solicitação está em um combo ou uma lista que o usuario clica e associa essa EQ a uma Solicitação.
Porém, esse “combo” ou “lista” foi carregado em que momento ?? provavelmente já está fora da sessão do hibernate.

Por isso, você está recebendo um TransientException …

Uma alternativa, antes de salvar a EQ, você teria que obter a Solicitação vinculada a ela. Para traze-la para o estado de persitente

Voce poderia fazer isso, efetuando uma busca pela PK da Solicitacao e adicionando essa solicitacao na EQ, ou fazer um merge.

Ex. (Algo parecido com isso)

Solicitacao sol = null;

sol = (fazOCast) session.load(Solicitacao, id);
	
// ou o merge
		
sol = (fazOCast) session.merge(solicitacao);

eq.setSolcitacao(sol);

session.save(eq)

Tenta isso também.

[]'s

Então galera, mas ele salva corretamente.
O problema é quando eu não preciso relacionar.

Ou seja, eu teria uma EQ vindo do formulário, com uma solicitação null,
Esse é o problema.

/* o VRaptor faria isso */
Solicitacao sol = null;
eq.setSolcitacao(sol);

/* e eu receberia a EQ no controller, com a sollicitação null */
/* e só passaria a EQ pro método adiciona/atualiza */

session.save(eq)