@OneToMany - Exclusão de filhos em cascata

13 respostas
Mickdark

Olá!!

Estou fazendo o mapeamento de uma lista de privilegios, 1 privilegio pode ter varios recurso, enquanto 1 recurso pode ter apenas 1 privilegio, quando eu passo da view para a aplicação ele grava no banco, mas quando eu altero ele faz o update da chave estrangeira para null e faz outro insert com os novos dados!!! Eu queria que ele excluisse do banco ao invés de gerar o update da chave estrangeira, dei uma olhada em dois posts feitos com o mesmo problema mais para a minha aplicaçao não resolveu!! Segue o código!!

Entity Recurso

@ManyToOne(cascade={CascadeType.ALL}, targetEntity=Privilegio.class) @JoinColumn(name="privilegio_cod") private Privilegio privilegio;

Entity Privilegio

@OneToMany(cascade={CascadeType.ALL}, targetEntity=Recurso.class, fetch=FetchType.EAGER, orphanRemoval=true) @JoinColumn(name="privilegio_cod") private Set<Recurso> recursos = new HashSet<Recurso>();

13 Respostas

T

Mickdark:
Olá!!

Estou fazendo o mapeamento de uma lista de privilegios, 1 privilegio pode ter varios recurso, enquanto 1 recurso pode ter apenas 1 privilegio, quando eu passo da view para a aplicação ele grava no banco, mas quando eu altero ele faz o update da chave estrangeira para null e faz outro insert com os novos dados!!! Eu queria que ele excluisse do banco ao invés de gerar o update da chave estrangeira, dei uma olhada em dois posts feitos com o mesmo problema mais para a minha aplicaçao não resolveu!! Segue o código!!

Entity Recurso

@ManyToOne(cascade={CascadeType.ALL}, targetEntity=Privilegio.class) @JoinColumn(name="privilegio_cod") private Privilegio privilegio;

Entity Privilegio

@OneToMany(cascade={CascadeType.ALL}, targetEntity=Recurso.class, fetch=FetchType.EAGER, orphanRemoval=true) @JoinColumn(name="privilegio_cod") private Set<Recurso> recursos = new HashSet<Recurso>();

Não sei se irá resolver seu problema mas tente tirar o cascade da entidade Recurso

Mickdark

Olá Thiago!

Tentei e não deu certo!! Tens mais alguma sugestão?

@ManyToOne(targetEntity=Privilegio.class) @JoinColumn(name="privilegio_cod")

T

Está usando qual provider? Imagino que isso deve ser configurável, não? Utilizo o Hibernate e nunca passei por esse tipo de situação

Mickdark

Então eu estou utilizando o Hibernate 3.6.8, no entando eu dei uma olhada nas documentações e em alguns foruns e percebi que pra usar um save ou update no hibernate para ele persistir eu tenho que implementar o equals e o HashCode!! Eu implementei o equals, agora o HashCode eu não sei como funciona, por isso estava procurando, e lendo uns tutos para aprender como ele funciona e como implementar esse método!!

marciobarroso

Se estiver utilizando o Eclipse, ele gera para você. Acho que o netbeans também deve ter algo parecido.

no eclipse, acesse o menu source e generate hashCode() and equals() …

Segue um exemplo gerado no eclipse:

import java.io.Serializable;

public class User implements Serializable {

	private static final long serialVersionUID = -1033450779801862683L;

	private Long id;
	private String name;
	private String lastName;

	public User() {

	}

	public Long getId() {
		return this.id;
	}

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

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getLastName() {
		return this.lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result	+ ((lastName == null) ? 0 : lastName.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (lastName == null) {
			if (other.lastName != null)
				return false;
		} else if (!lastName.equals(other.lastName))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}
Tchello

Ta usando hibernate?
Use a annotation no relacionamento:

Mickdark
marciobarroso:
Se estiver utilizando o Eclipse, ele gera para você. Acho que o netbeans também deve ter algo parecido.

no eclipse, acesse o menu source e generate hashCode() and equals() ...

Segue um exemplo gerado no eclipse:

import java.io.Serializable;

public class User implements Serializable {

	private static final long serialVersionUID = -1033450779801862683L;

	private Long id;
	private String name;
	private String lastName;

	public User() {

	}

	public Long getId() {
		return this.id;
	}

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

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getLastName() {
		return this.lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result	+ ((lastName == null) ? 0 : lastName.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (lastName == null) {
			if (other.lastName != null)
				return false;
		} else if (!lastName.equals(other.lastName))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}

Obrigado Marcio, mas quando eu gerei o código ele está dando uma exception do hibernate, "org.hibernate.LazyInitializationException"

Mickdark
Tchello:
Ta usando hibernate? Use a annotation no relacionamento:
@org.hibernate.annotations.Cascade(value = CascadeType.ALL)
Sim sim, eu estou tentando desta maneira depois que me responderam a primeira vez, mas ainda nada, olha o código como está neste momento!!!
@Entity
@Component
public class Privilegio implements Serializable {
	private static final long serialVersionUID = -6175377460054211138L;
	@Id
	@GeneratedValue(generator = "Privilegio")
	@SequenceGenerator(name = "Privilegio", initialValue = 20, sequenceName = "sequence_privilegio")
	private Long cod;
	@Column(name="Nome")
	private String nome;
	@ManyToOne(cascade={CascadeType.ALL}, targetEntity=Empresa.class )
	@ForeignKey(name="CodEmpresa_Privilegio_FK")
	private Empresa empresa;
	
	@OneToMany(targetEntity=Recurso.class, orphanRemoval=true)//, orphanRemoval=true
	@Cascade(value={org.hibernate.annotations.CascadeType.ALL})
	@JoinColumn(name="privilegio_cod")
	private Set<Recurso> recursos = new HashSet<Recurso>();
marciobarroso

Você precisa utilizar o FetchType para dizer ao hibernate que sua coleção deve ser inteiramente carregada logo de cara. Se você não configura isso, o hibernate só leva uma referencia dos itens da lista, tendo posteriormente que carregá-los quando for necessário.

Ai, surgem outros problemas … se você estiver enviando estes pojos para o jsp para renderização, você vai precisar implementar um filtro do tipo OpenSessionInView, para carregar a lista, ou então, carrega a lista antes de enviar para o jsp.

Boa sorte

Mickdark

marciobarroso:
Você precisa utilizar o FetchType para dizer ao hibernate que sua coleção deve ser inteiramente carregada logo de cara. Se você não configura isso, o hibernate só leva uma referencia dos itens da lista, tendo posteriormente que carregá-los quando for necessário.

Ai, surgem outros problemas … se você estiver enviando estes pojos para o jsp para renderização, você vai precisar implementar um filtro do tipo OpenSessionInView, para carregar a lista, ou então, carrega a lista antes de enviar para o jsp.

Boa sorte

É verdade, eu tinha esquecido de colocar o EAGER no fetch, bom, o problema, desse fetch foi resolvido de outra forma, quando o eclipse implementou o HashCode ele colocou a igualdade para todos os campos, mas no meu caso eu queria apenas do nome do privilegio e da empresa, então eu comentei os outros dois campos, e esse erro do FetchTypeLazyException não apareceu mais, agora vou testar se arrumando o HashCode da outra tabela ele exclui os valores depois de atualizar XD!!!

Como o Eclipse Implementou

@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((cod == null) ? 0 : cod.hashCode()); result = prime * result + ((empresa == null) ? 0 : empresa.hashCode()); result = prime * result + ((nome == null) ? 0 : nome.hashCode()); result = prime * result + ((recursos == null) ? 0 : recursos.hashCode()); return result; }

Como eu arrumei

@Override public int hashCode() { final int prime = 31; int result = 1; // result = prime * result + ((cod == null) ? 0 : cod.hashCode()); result = prime * result + ((empresa == null) ? 0 : empresa.hashCode()); result = prime * result + ((nome == null) ? 0 : nome.hashCode()); // result = prime * result + ((recursos == null) ? 0 : recursos.hashCode()); return result; }

Mickdark

Finalmente eu consegui XD!!!!

Eu mudei alguns mapeamentos e mudei algumas coisas no equals e hashCode, no final das contas tenho quase certeza de que o problema estava realmente no mapeamento da entidade colocando Merge e alterando por merge também e não mais por saveOrUpdate, bom pra quem estiver passando por este problema segue meu código completo!! Ha esqueci de dizer, eu estou usando o VRaptor como MVC, por isso algumas annotations que não fazem parte da JPA, como @Component !!

Explicação: Tabela de Privilegios, 1 privilegio pode ter 0 ou mais recursos (OneToMany), e cada recurso tem que ter pelo menos 1 privilegio (ManyToOne)
@Entity
@Component
public class Recurso implements Serializable, Comparable<Recurso>{
	private static final long serialVersionUID = 826924108132014590L;
	@Id
	@GeneratedValue(generator = "Recurso")
	@SequenceGenerator(name = "Recurso", initialValue = 1, sequenceName = "sequence_recurso")
	private Long cod;
	@Column
	private String nome;
	@Transient
	private String descRecurso;
	@ManyToOne
	private Privilegio privilegio;

	@Override
	public int compareTo(Recurso recurso) {
		return this.descRecurso.compareTo(recurso.getDescRecurso());
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		
		if (!(cod==null)){
			return prime * result + ((cod == null) ? 0 : cod.hashCode());
		}
//		result = prime * result + ((descRecurso == null) ? 0 : descRecurso.hashCode());
		result = prime * result + ((nome == null) ? 0 : nome.hashCode());
//		result = prime * result + ((privilegio == null) ? 0 : privilegio.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Recurso other = (Recurso) obj;
		
		if(!(cod==null)){
			return cod.equals(other.getCod());
		}else if(!(nome==null)){
			return nome.equals(other.getNome());
		}
		return false;
	}

Getters e Setters...
@Entity
@Component
public class Privilegio implements Serializable {
	private static final long serialVersionUID = -6175377460054211138L;
	@Id
	@GeneratedValue(generator = "Privilegio")
	@SequenceGenerator(name = "Privilegio", initialValue = 1, sequenceName = "sequence_privilegio")
	private Long cod;
	@Column(name="Nome")
	private String nome;
	@ManyToOne(cascade={CascadeType.ALL}, targetEntity=Empresa.class, fetch=FetchType.EAGER )
	@Cascade(value={
			org.hibernate.annotations.CascadeType.SAVE_UPDATE,
			org.hibernate.annotations.CascadeType.REMOVE})
	@ForeignKey(name="CodEmpresa_Privilegio_FK")
	private Empresa empresa;
	
	@OneToMany(targetEntity=Recurso.class, fetch=FetchType.EAGER, orphanRemoval=true)//, orphanRemoval=true
	@Cascade(value={
			org.hibernate.annotations.CascadeType.SAVE_UPDATE,
			org.hibernate.annotations.CascadeType.DELETE,
			org.hibernate.annotations.CascadeType.MERGE
			})
	@JoinColumn(name="privilegio_cod")
	private Set<Recurso> recursos = new HashSet<Recurso>();

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		
		result = prime * result + ((cod == null) ? 0 : cod.hashCode());
		if (cod==null){
			result = prime * result + ((empresa == null) ? 0 : empresa.hashCode());
			result = prime * result + ((nome == null) ? 0 : nome.hashCode());
		}
//		result = prime * result;// + ((recursos == null) ? 0 : recursos.hashCode())
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		Privilegio privilegio = (Privilegio) obj;
		
		if (!(cod==null)){
			return cod.equals(privilegio.getCod());
		}else if (!(empresa==null && nome==null)){
				if(!(privilegio.getEmpresa()==null && privilegio.getNome()==null)){
					return empresa.equals(privilegio.empresa) && nome.equals(privilegio.nome);
				}
		}
		return false;
	}
Getters e Setters...
Código que faz o merge de acordo com a requisição na JSP!!
this.privDao.mergePrivilegio(privilegio);
Metodo criado no privilegioDao!!
public void mergePrivilegio(Privilegio privilegio) {
		operacoes.merge(privilegio);
	}
Eu fiz uma classe que guarda todas as consultas padrões, só não sei se é uma boa idéia, mas ta ai!!
public class HibernateOperacoes {
	private final Session session;
	private final Object obj;
	
	public HibernateOperacoes(Object obj, Session sessao) throws Throwable {
		System.out.println("Entrou no Construtor do Hibernate");
		this.obj = obj;
		this.session = sessao;
	}

	public void merge(Object obj) {
		session.beginTransaction();
		session.merge(obj);
		session.getTransaction().commit();
	}
...
}

Agora só preciso ver o de exclusão em cascata !!!

Mickdark

O cascateamento utilizando Delete não funcionou de jeito nenhum, gostaria de saber se alguem ja tentou fazer um delete em um objeto desanexado apenas com o id?

Eu fiz um teste com JUnit, assim!!
//Cria um recurso para salvar no objeto privilegio
		Recurso recurso = new Recurso();
		recurso.setNome("Teste_Exclusao (Rec)");
		
		//Cria o Objeto privilegio para ser salvo
		Privilegio privilegio = new Privilegio();
		privilegio.setNome("Teste_Exclusao (Priv)");
		privilegio.getRecursos().add(recurso);
		
		//Salva o Objeto privilegio
		sessao.beginTransaction();
		sessao.save(privilegio);
		sessao.getTransaction().commit();
		sessao.close();
		
		//Cria um novo privilegio so com o codigo settado para testar a exclusao
		Privilegio privilegio2 = new Privilegio();
		privilegio2.setCod(privilegio.getCod());
		
		//Exclui o segundo privilegio para ver se ele tambem exclui os recursos
		sessao = factory.openSession();
		sessao.beginTransaction();
		System.out.println("-------Vai Deletar-------");
		
//		privilegio2 = (Privilegio) sessao.load(Privilegio.class, privilegio.getCod());
		sessao.delete(privilegio2);
		
		System.out.println("---------Deletou---------");
		sessao.getTransaction().commit();
		
		//Pega a lista de recursos sem o privilegio na FK
		List<Recurso> recursos = (List<Recurso>) sessao.createQuery("from Recurso where privilegio_cod is null").list();
		
		sessao.close();
		
		//Verifica se a lista de recursos possui algumsa coisa, se possuir esta errado, porque significa que ele
		//nao excluiu a lista de recursos no banco
		assertEquals(0, recursos.size());
Mickdark

A unica maneira de fazer com que ele realizasse a exclusão em cascata, “Alem de ter o @Cascade(value={CascateType.DELETE}”, foi chamando um refresh antes de excluir o objeto só com o id setado!!

Alguem sabe me dizer se desta maneira está correta?
//Exclui objeto pai e os objetos contidos na lista do HashSet!!
session.beginTransaction();
session.refresh(obj);
session.delete(obj)
session.getTransaction().commit();
session.close();
Criado 18 de janeiro de 2012
Ultima resposta 9 de fev. de 2012
Respostas 13
Participantes 4