Web Service+EJB: problema com loop infinito (relacionamento bidirecional entre entidades)

Bom dia pessoal, estou iniciando meus estudos em web services e ejb e estava tentando elaborar um exemplo. Resumidamente, tenho o seguinte:

  • O relacionamento entre duas entidades: (Cursos e Alunos) relacionamento de 1 x N, ou seja, um curso pode ter muitos alunos, e um aluno pode estar matriculado em apenas um curso.
    |Cursos|(1) <-----> (N)|Alunos|

  • Os mapeamentos (parte do mapeamento) das entidades seguem abaixo:

[code]@Entity
@Table(name = “ACADEMICO_CURSOS”)
public class CursoEntity implements Serializable {

@OneToMany(mappedBy = “curso”, cascade = CascadeType.ALL)
private Collection alunos = new ArrayList();

@Entity
@Table(name = “ACADEMICO_ALUNOS”)
public class AlunoEntity implements Serializable {

@ManyToOne
@JoinColumn(name = “ALUNO_CURSO_ID”)
private CursoEntity curso;
…[/code]

Quando testo o ejb a parte (solicitando a busca do curso por meio de um id: find(CursoEntity.class, id)), ele funciona. Entretanto, quando crio o webservice baseado nele, nao funciona e ocorre o seguinte erro:

[code]ERROR [RequestHandlerImpl] Error processing web service request
org.jboss.ws.WSException: javax.xml.ws.WebServiceException: javax.xml.bind.MarshalException

  • with linked exception:
    [com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: academico.entity.CursoEntity[id=44] -> academico.entity.AlunoEntity[id=45] -> academico.entity.CursoEntity[id=44]][/code]

Alguem teria a solucao?

E aê amigo,

Conseguiu entender isso ?

Então, cara, consegui a solução sim. Desculpem não ter postado antes.

O seu entity beans precisa implementar a interface CycleRecoverable, o método onCycleDetected. Não estou aqui com o código, porque fiz isso em um projeto no ano passado, e não estou mais na empresa. Vou ver se faço um exemplo aqui e depois eu posto, ok?

Achei um exemplo aqui, dá uma olhada e vê se ajuda:

[code]@Entity

Public class MyObject implements Serializable, CycleRecoverable {

Private String id;

Public MyObject(String id){

This.id = id

}

public Object onCycleDetected(CycleRecoverable.Context context) {

MyObject obj = new MyObject(this.id);

return obj;

}

} [/code]

Então, a explicação era mais ou menos assim: quando ocorre o Marshalling (Obj -> XML), como se trata de um relacionamento bidirecional (Aluno<->Curso) o que ocorre é o seguinte: Aluno tem referência para Curso e Curso para Aluno e Aluno para Curso e Curso para Aluno (…) e assim vai. Por isso ocorria o ciclo. Então, quando você implementa essa interface (CycleRecoverable), é possível detectar esse ciclo, e quando isso ocorre, o método onCycleDetected é chamado. Então, “quebra-se” o ciclo, ao criar um novo objeto sem a referência novamente para o próximo objeto, ou seja, é reconhecido o relacionamento Aluno->Curso->Aluno. (O que é possível obter a referência de Curso a partir de Aluno e de Aluno a partir de Curso). Bom, se a memória não me falha, creio que seja algo próximo disso… hehehe

Espero ter ajudado.

Olá Bom Dia,

Muito obrigado por dividir esta solução. Já estava ficando meio maluco com isso. Muito obrigado.
Abraços

Então iblanco, funcionou pra vc? Estava usando uma notação pra resolver o problema @XmlTransient mas resolveu parcialmente. Antes de excluir a anotacao que ria um feedback.

Valeu

Ola todos!
Alterei todas as minhas classes conforme o post do ln30. Deem uma olhada.

package entities;

import com.sun.xml.internal.bind.CycleRecoverable;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "AFILIADO")
@NamedQueries({
    @NamedQuery(name = "Afiliado.findAll", query = "SELECT a FROM Afiliado a"),
    @NamedQuery(name = "Afiliado.findByIdAfiliado", query = "SELECT a FROM Afiliado a WHERE a.idAfiliado = :idAfiliado"),
    @NamedQuery(name = "Afiliado.findByNome", query = "SELECT a FROM Afiliado a WHERE a.nome = :nome"),
    @NamedQuery(name = "Afiliado.findByTel", query = "SELECT a FROM Afiliado a WHERE a.tel = :tel"),
    @NamedQuery(name = "Afiliado.findBySenha", query = "SELECT a FROM Afiliado a WHERE a.senha = :senha")})
public class Afiliado implements Serializable, CycleRecoverable {
// Tive que adicionar aqui o @Transient
    private String id;
    public Afiliado(String id) {
        this.id = id;
    }
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "ID_AFILIADO")
    private Integer idAfiliado;
    @Basic(optional = false)
    @Column(name = "NOME")
    private String nome;
    @Basic(optional = false)
    @Column(name = "TEL")
    private String tel;
    @Basic(optional = false)
    @Column(name = "SENHA")
    private String senha;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "idAfiliado")
    private List<Pedido> pedidoList;

    public Afiliado() {
    }

    public Afiliado(Integer idAfiliado) {
        this.idAfiliado = idAfiliado;
    }

    public Afiliado(Integer idAfiliado, String nome, String tel, String senha) {
        this.idAfiliado = idAfiliado;
        this.nome = nome;
        this.tel = tel;
        this.senha = senha;
    }

    public Integer getIdAfiliado() {
        return idAfiliado;
    }

    public void setIdAfiliado(Integer idAfiliado) {
        this.idAfiliado = idAfiliado;
    }

    public String getNome() {
        return nome;
    }

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

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getSenha() {
        return senha;
    }

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

    public List<Pedido> getPedidoList() {
        return pedidoList;
    }

    public void setPedidoList(List<Pedido> pedidoList) {
        this.pedidoList = pedidoList;
    }

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

    @Override
    public boolean equals(Object object) {

        if (!(object instanceof Afiliado)) {
            return false;
        }
        Afiliado other = (Afiliado) object;
        if ((this.idAfiliado == null && other.idAfiliado != null) || (this.idAfiliado != null && !this.idAfiliado.equals(other.idAfiliado))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "entities.Afiliado[idAfiliado=" + idAfiliado + "]";
    }

    public Object onCycleDetected(CycleRecoverable.Context context) {
        Afiliado afiliado = new Afiliado(this.id);
        return afiliado;
    }
}

Mas agora tenho problemas com o import com.sun.xml.internal.bind.CycleRecoverable; Olha o erro ao compilar!

C:\Users\Boss\Documents\0-Desenvolvimento\GED\WS5\src\java\entities\Afiliado.java:4: package com.sun.xml.internal.bind does not exist
import com.sun.xml.internal.bind.CycleRecoverable;
C:\Users\Boss\Documents\0-Desenvolvimento\GED\WS5\src\java\entities\Afiliado.java:30: cannot find symbol
symbol: class CycleRecoverable
public class Afiliado implements Serializable, CycleRecoverable {
C:\Users\Boss\Documents\0-Desenvolvimento\GED\WS5\src\java\entities\Pedido.java:3: package com.sun.xml.internal.bind does not exist
import com.sun.xml.internal.bind.CycleRecoverable;

Importei pra minha lib (netbeans) o jaxb-impl.jar e nada.

Vcs tiveram o mesmo problema? Isso ta me deixando louco!

Algumas classes em que o id é uma string (ESTADO) tive que comentar um dos construtores. Vejam

package entities;

import com.sun.xml.internal.bind.CycleRecoverable;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
//import javax.xml.bind.annotation.XmlTransient;

/**
 *
 * @author Applied Minds
 */
@Entity
@Table(name = "ESTADO")
@NamedQueries({
    @NamedQuery(name = "Estado.findAll", query = "SELECT e FROM Estado e"),
    @NamedQuery(name = "Estado.findByIdEstado", query = "SELECT e FROM Estado e WHERE e.idEstado = :idEstado"),
    @NamedQuery(name = "Estado.findByCidade", query = "SELECT e FROM Estado e WHERE e.cidade = :cidade")})
public class Estado implements Serializable, CycleRecoverable {
    @Transient
    private String id;
    public Estado(String id) {
        this.id = id;
    }
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "ID_ESTADO")
    private String idEstado;
    @Basic(optional = false)
    @Column(name = "CIDADE")
    private String cidade;
//    @XmlTransient Aqui
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "estado")
    private List<ValorPrazo> valorPrazoList;

    public Estado() {
    }

//    public Estado(String idEstado) {
//        this.idEstado = idEstado;
//    }

    public Estado(String idEstado, String cidade) {
        this.idEstado = idEstado;
        this.cidade = cidade;
    }

    public String getIdEstado() {
        return idEstado;
    }

    public void setIdEstado(String idEstado) {
        this.idEstado = idEstado;
    }

    public String getCidade() {
        return cidade;
    }

    public void setCidade(String cidade) {
        this.cidade = cidade;
    }
//    @XmlTransient Aqui

    public List<ValorPrazo> getValorPrazoList() {
        return valorPrazoList;
    }

    public void setValorPrazoList(List<ValorPrazo> valorPrazoList) {
        this.valorPrazoList = valorPrazoList;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (idEstado != null ? idEstado.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 Estado)) {
            return false;
        }
        Estado other = (Estado) object;
        if ((this.idEstado == null && other.idEstado != null) || (this.idEstado != null && !this.idEstado.equals(other.idEstado))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "entities.Estado[idEstado=" + idEstado + "]";
    }

    public Object onCycleDetected(CycleRecoverable.Context context) {
        Estado estado = new Estado(this.id);
        return estado;
    }
}

Muito provavelmente estou fazendo algo errado. So nao sei o que.
Agradeco qualquer ajuda dos senhores.
Abraço