[Resolvido] Alternativa ao XmlTransient em Webservice REST com JPA e Jersey

Buenas galera!

Tenho um serviço RESTFull utilizando Jersey 1.9 e Hibernate…

Em uma das classes mapeadas no Hibernate eu tenho um mapeamento de referencias many-to-one com uma outra classe da seguinte forma:

GrupoProdutos -> SubgrupoProdutos -> Produtos

Onde: um grupo tem vários subgrupos, um subgrupo tem um grupo, um subgrupo vários produtos e um produto apenas um subgrupo.

Enfim, meu problema é que qndo faço o mapeamento o Jersey lança um erro:

com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: jb.model.bean.GrupoProduto@101fed39 -> jb.model.bean.SubgrupoProduto@dfc9d74 -> jb.model.bean.GrupoProduto@101fed39

sei que é devido as referencias das classes que formam um loop… mas a questão é: Como contornar isso sem ser com o @XmlTransient??

Se uso ele, fico sem a lista dos subgrupos dentro dos grupos… e isso esta deixando a pesquisa um pouco complicada…

alguém pode dar uma ajuda?

E ai galera? ninguém?

Estou usando o Jackson JSON, ele tem duas anotações que teoricamente seriam pra resolver este problema, mas no meu caso nao deu certo!

Esse é o doc que tem no wiki do Jackson Json mas nao da tando certo nas minhas classes, continuo tendo o erro!

http://wiki.fasterxml.com/JacksonFeatureBiDirReferences

Preciso muito da list! será que alguém nunca passou por esse problema?

minhas classes:

@Entity
@XmlRootElement
@Table(name="grupo_produto")
public class GrupoProduto implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="grupo_id", unique=true, nullable=false)
    private int id;
    @Column(name="nome", length=90, nullable=false)
    private String nome;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="data_inclusao", nullable=false)
    private Date dataInclusao;
    @Column(name="incluido_por", length=45, nullable=false)
    private String incluidoPor;
    
    @Cascade(CascadeType.ALL)
    @OneToMany(mappedBy="grupo", fetch=FetchType.LAZY)
    private List<SubgrupoProduto> subgrupos;
    
    public GrupoProduto() {
        
        this.id = 0;
        this.nome = null;
        this.dataInclusao = SQLClock.getTimestamp();
        this.incluidoPor = null;
        this.subgrupos = new ArrayList<SubgrupoProduto>();
    }
}

// gets and sets

e o subgrupo:

@Entity
@XmlRootElement
@Table(name="subgrupo_produto")
public class SubgrupoProduto implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="subgrupo_id", unique=true, nullable=false)
    private int id;
    @Column(name="nome", length=90, nullable=false)
    private String nome;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="data_inclusao", nullable=false)
    private Date dataInclusao;
    @Column(name="incluido_por", length=45, nullable=false)
    private String incluidoPor;
    
    @Cascade(CascadeType.ALL)
    @ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name="grupo_id", insertable=true, updatable=true)
    private GrupoProduto grupo;
    
    @Cascade(CascadeType.ALL)
    @OneToMany(mappedBy="subgrupo", fetch=FetchType.LAZY)
    private List<Produto> produtos;

    public SubgrupoProduto() {
        
        this.id = 0;
        
        this.nome = null;
        this.dataInclusao = SQLClock.getTimestamp();
        this.incluidoPor = null;
        
        this.grupo = null;
        this.produtos = new ArrayList<Produto>();
    }
}

// gets and sets

Voce conseguiu resolver? estou com o mesmo problema aqui.

é, não foi o que eu queria mas resolvi.

Seguinte:

1 - muda o processador de Json para o Jackson JSON, add as libs no classpath
2 - colocar ele no init do servlet no web.xml

        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>

3 - no seu servidor, anote seus pojos com o @JsonBackReference(“label”) e @JsonManagedReference(“label”) no item que retorna a referencia da classe e a lista, por exemplo:

@Entity
@Table(name="grupo")
public class Grupo implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="grupo_id", unique=true, nullable=false)
    private int id;
    
    // [...] outros atributos que vc tenha
    
    @OneToMany(mappedBy="grupo", fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
    private List<Usuarios> usuarios;

    // gets e sets
    
    /**
     * @return the grupo
     */
    @JsonManagedReference("grupo-usuario")
    public List<Usuarios> getUsuarios() {
        return usuarios;
    }
}

e na classe que é referenciada:

@Entity
@Table(name="usuarios")
public class Usuarios implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="usuario_id", unique=true, nullable=false)
    private int id;
    
    // [...] outros atributos que vc tenha
    
    @ManyToOne(fetch=FetchType.EAGER, cascade=CascadeType.PERSIST)
    @JoinColumn(name="grupo_id", insertable=true, updatable=true, nullable=false)
    private Grupo grupo;

    // gets e sets
    
    /**
     * @return the grupo
     */
    @JsonBackReference("grupo-usuario")
    public Grupo getGrupo() {
        return grupo;
    }
}

Ai quando vc executar e chamar o resource que usa estes pojos, ele nao vai mais dar o erro de referencias no XML, entretanto! o metodo que esta com a anotacao @JsonBackReference() não retorna nada por que ele nao eh serializado…

Se vc precisa desta referencia e da referencia da lista ao mesmo tempo, desista… fique com a referencia da classe e esqueca a lista (fica mais facil pesquisar quem é daquele grupo por outra criteria em outro metodo e retornar so a lista)

Se vc tem que usar os mesmos pojos do server no cliente, precisara configurar o cliente para usar o Jackson JSON no momento de criar o cliente:

    public static void setBaseURI(String uri) {
        
        baseUri = uri;
        
        ClientConfig config = new DefaultClientConfig();
        
        config.getClasses().add(JacksonJsonProvider.class);
        
        Client client = Client.create(config);

        resource = client.resource(getBaseURI());
    }

e colocar as anotacoes como feito no server, porem ao contrário… ou a referencia ao grupo no usuario (nesse exemplo) não seria gravada pois o @JsonBackReference nao serializa para JSON.

é isso! se tiver duvida, posta ai!

OK!

Encontrei mais uma alternativa ao XMLTransient, ou melhor fiz uma alternativa…

quem quiser conferir, da uma olhada ai: http://arthurgregorio.eti.br/blog/79-programacao/java/94-restful-com-jersey-e-o-problema-do-cyclic-reference