Relação de muitos para muitos não está recuperando os dados - REST (Jersey)

15 respostas
J

galera, criei um serviço web REST baseado na seguinte estrutura:

Bem, observando:

  • para cada product eu tenho uma imagem
  • para cada hotspot eu tenho um video
  • uma relação de muitos para muitos entre product e hotspot

Mandei o IDE (Netbenas) criar um serviço REST a partir desse banco de dados, e ao dar GET em product eu consigo recuperar os dados de product e image relacionada a esse produto, porém os hotspots relacionados a esse product não estou conseguindo recuperar.

Vou postar aqui a classe entidade product e a hotspot para caso alguém puder ajudar, analisar se esqueci algo, agradeço.

Classe product

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package entities;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;


@Entity
@Table(name = "PRODUCT")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Product.findAll", query = "SELECT p FROM Product p"),
    @NamedQuery(name = "Product.findByProductId", query = "SELECT p FROM Product p WHERE p.productId = :productId"),
    @NamedQuery(name = "Product.findByTitle", query = "SELECT p FROM Product p WHERE p.title = :title")})
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    // @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "PRODUCT_ID")
    private BigDecimal productId;
    @Size(max = 550)
    @Column(name = "TITLE")
    private String title;
    @Lob
    @Column(name = "DESCRIPTION")
    private String description;
    @ManyToMany(mappedBy = "productCollection")
    private Collection<Hotspot> hotspotCollection;
    @JoinColumn(name = "IMAGE", referencedColumnName = "IMAGE_ID")
    @ManyToOne(optional = false)
    private Image image;

    public Product() {
    }

    public Product(BigDecimal productId) {
        this.productId = productId;
    }

    public BigDecimal getProductId() {
        return productId;
    }

    public void setProductId(BigDecimal productId) {
        this.productId = productId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @XmlTransient
    public Collection<Hotspot> getHotspotCollection() {
        return hotspotCollection;
    }

    public void setHotspotCollection(Collection<Hotspot> hotspotCollection) {
        this.hotspotCollection = hotspotCollection;
    }

    public Image getImage() {
        return image;
    }

    public void setImage(Image image) {
        this.image = image;
    }

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

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

E a classe hotspot

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package entities;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;


@Entity
@Table(name = "HOTSPOT")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Hotspot.findAll", query = "SELECT h FROM Hotspot h"),
    @NamedQuery(name = "Hotspot.findByHotspotId", query = "SELECT h FROM Hotspot h WHERE h.hotspotId = :hotspotId"),
    @NamedQuery(name = "Hotspot.findByX", query = "SELECT h FROM Hotspot h WHERE h.x = :x"),
    @NamedQuery(name = "Hotspot.findByY", query = "SELECT h FROM Hotspot h WHERE h.y = :y"),
    @NamedQuery(name = "Hotspot.findByWidth", query = "SELECT h FROM Hotspot h WHERE h.width = :width"),
    @NamedQuery(name = "Hotspot.findByHeight", query = "SELECT h FROM Hotspot h WHERE h.height = :height"),
    @NamedQuery(name = "Hotspot.findByTitle", query = "SELECT h FROM Hotspot h WHERE h.title = :title"),
    @NamedQuery(name = "Hotspot.findByLink", query = "SELECT h FROM Hotspot h WHERE h.link = :link")})
public class Hotspot implements Serializable {
    private static final long serialVersionUID = 1L;
    // @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "HOTSPOT_ID")
    private BigDecimal hotspotId;
    @Column(name = "X")
    private BigInteger x;
    @Column(name = "Y")
    private BigInteger y;
    @Column(name = "WIDTH")
    private BigInteger width;
    @Column(name = "HEIGHT")
    private BigInteger height;
    @Size(max = 200)
    @Column(name = "TITLE")
    private String title;
    @Lob
    @Column(name = "DESCRIPTION")
    private String description;
    @Size(max = 200)
    @Column(name = "LINK")
    private String link;
    @JoinTable(name = "PRODUCT_HAS_HOTSPOT", joinColumns = {
        @JoinColumn(name = "HOTSPOT_HOTSPOT_ID", referencedColumnName = "HOTSPOT_ID")}, inverseJoinColumns = {
        @JoinColumn(name = "PRODUCT_PRODUCT_ID", referencedColumnName = "PRODUCT_ID")})
    @ManyToMany
    private Collection<Product> productCollection;
    @JoinColumn(name = "VIDEO", referencedColumnName = "VIDEO_ID")
    @ManyToOne(optional = false)
    private Video video;

    public Hotspot() {
    }

    public Hotspot(BigDecimal hotspotId) {
        this.hotspotId = hotspotId;
    }

    public BigDecimal getHotspotId() {
        return hotspotId;
    }

    public void setHotspotId(BigDecimal hotspotId) {
        this.hotspotId = hotspotId;
    }

    public BigInteger getX() {
        return x;
    }

    public void setX(BigInteger x) {
        this.x = x;
    }

    public BigInteger getY() {
        return y;
    }

    public void setY(BigInteger y) {
        this.y = y;
    }

    public BigInteger getWidth() {
        return width;
    }

    public void setWidth(BigInteger width) {
        this.width = width;
    }

    public BigInteger getHeight() {
        return height;
    }

    public void setHeight(BigInteger height) {
        this.height = height;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    @XmlTransient
    public Collection<Product> getProductCollection() {
        return productCollection;
    }

    public void setProductCollection(Collection<Product> productCollection) {
        this.productCollection = productCollection;
    }

    public Video getVideo() {
        return video;
    }

    public void setVideo(Video video) {
        this.video = video;
    }

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

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

Obrigado!

15 Respostas

Alexandre_Saudate

Você tem um DAO, ou algo relacionado? E os serviços REST que foram criados, são transacionais?

J

Não, pelo que vi meus serviços não são transacionais, acho que tem a ver com algo do tipo @transient, certo?

Vou dar uma pesquisada de como isso se aplica…

Quanto ao meu serviço product, segue, caso ajude:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package service;

import entities.Product;
import java.math.BigDecimal;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.*;

/**
 *
 * @author gregori
 */
@Stateless
@Path("entities.product")
public class ProductFacadeREST extends AbstractFacade<Product> {
    @PersistenceContext(unitName = "RestPOCPU")
    private EntityManager em;

    public ProductFacadeREST() {
        super(Product.class);
    }

    @POST
    @Override
    @Consumes({"application/xml", "application/json"})
    public void create(Product entity) {
        super.create(entity);
    }

    @PUT
    @Override
    @Consumes({"application/xml", "application/json"})
    public void edit(Product entity) {
        super.edit(entity);
    }

    @DELETE
    @Path("{id}")
    public void remove(@PathParam("id") BigDecimal id) {
        super.remove(super.find(id));
    }

    @GET
    @Path("{id}")
    @Produces({"application/xml", "application/json"})
    public Product find(@PathParam("id") BigDecimal id) {
        return super.find(id);
    }

    @GET
    @Override
    @Produces({"application/json"})
    public List<Product> findAll() {
        return super.findAll();
    }

    @GET
    @Path("{from}/{to}")
    @Produces({"application/xml", "application/json"})
    public List<Product> findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) {
        return super.findRange(new int[]{from, to});
    }

    @GET
    @Path("count")
    @Produces("text/plain")
    public String countREST() {
        return String.valueOf(super.count());
    }

    @java.lang.Override
    protected EntityManager getEntityManager() {
        return em;
    }
    
}
J

Um @GET no product me retorna esse JSON

[ { "description" : "Descrição",
    "image" : { "imageId" : 1,
        "imagename" : "Imagem teste",
        "imageurl" : "http://www.google.com.br/image.jpg",
        "thumbnailurl" : "http://www.google.com.br/thumb_image.jpg"
      },
    "productId" : 1,
    "title" : "Produto teste"
  } ]

Repare que somente a image relacionada está presente, os hotspots não, mas deveriam.

Obrigado.

Alexandre_Saudate

É porque, de fato, eles não estão sendo carregados. Esse relacionamento é, por padrão, do tipo Lazy. O ideal seria você fazer a carga deles quando precisar, ou seja, no seu serviço REST. Se você anotar os seus serviços com @TransactionAttribute , você pode fazer um getHotspots() no seu bean que ele vai recuperar os dados do banco e você pode retorná-los no seu serviço.

J

Obrigado, vou tentar seguir essas instruções e ver o que acontece.

J

asaudate, cheguei num ponto que me faltou um pouco de feeling da coisa…

Como sou novato ainda estou me adaptando com o que é um Bean e tal, as vezes me confundo um pouco, se não for pedir demais será que poderia me dar um exemplo, baseado nesse que eu enviei?

Assim eu vou me familiarizando com as notações, termos e convenções e entendo como resolver meu problema. Acredito que meu problema esteja próximo de ser resolvido e que não seja algo difícil, porém não estou conseguindo resolver por falta de alguns aspectos que ainda não estou bem inteirado.

Obrigado.

Alexandre_Saudate

jmountain:
asaudate, cheguei num ponto que me faltou um pouco de feeling da coisa…

Como sou novato ainda estou me adaptando com o que é um Bean e tal, as vezes me confundo um pouco, se não for pedir demais será que poderia me dar um exemplo, baseado nesse que eu enviei?

Assim eu vou me familiarizando com as notações, termos e convenções e entendo como resolver meu problema. Acredito que meu problema esteja próximo de ser resolvido e que não seja algo difícil, porém não estou conseguindo resolver por falta de alguns aspectos que ainda não estou bem inteirado.

Obrigado.

‘Bean’ é diminutivo de Java Bean - que também pode ser referenciado como POJO. É a classe que você está utilizando como modelo e/ou tráfego de dados - no seu caso, Product e Hotspot são beans. Mas tome cuidado para não confundir com EJB (Enterprise Java Bean), que é um bicho completamente diferente.

J

CONSEGUI…

No meu Bean eu tenho isso

@XmlTransient
    public Collection<Hotspot> getHotspotCollection() {
        return hotspotCollection;
    }

eu removi o @XmlTransient e funcionou, agora um @GET em product me retorna os hotspots relacionados.

Só não entendi porque, mas funcionou, vou pesquisar sobre essa anotação para entender melhor.

Mais uma vez obrigado asaudate pela força!

Alexandre_Saudate

jmountain:
CONSEGUI…

No meu Bean eu tenho isso

@XmlTransient
    public Collection<Hotspot> getHotspotCollection() {
        return hotspotCollection;
    }

eu removi o @XmlTransient e funcionou, agora um @GET em product me retorna os hotspots relacionados.

Só não entendi porque, mas funcionou, vou pesquisar sobre essa anotação para entender melhor.

Mais uma vez obrigado asaudate pela força!

Ops… não tinha visto essa anotação no bean :oops:

Ela serve para ‘avisar’ ao parser JSON que o conteúdo deste campo não vai ser serializado (seja por XML ou JSON).

J

Hmmmm… entendi!

Pra variar, corrigi esse problema mas surgiu outro, pelo jeito é ciclo, como product tem relação com hotspot e hotspot com product, agora meu JSON parece estar em loop infinito, pois cada elemento product que ele encontra ele pega seu hotspot correspondente, que pega se product correspondente, e assim por diante…

Tentei resolver isso com isso implementando CycleRecoverable no meu bean e sobrescrevendo o método onCycleDetected, porém não funcionou, será que fiz certo? alguma outra solução?

:shock:

Alexandre_Saudate

jmountain:
Hmmmm… entendi!

Pra variar, corrigi esse problema mas surgiu outro, pelo jeito é ciclo, como product tem relação com hotspot e hotspot com product, agora meu JSON parece estar em loop infinito, pois cada elemento product que ele encontra ele pega seu hotspot correspondente, que pega se product correspondente, e assim por diante…

Tentei resolver isso com isso implementando CycleRecoverable no meu bean e sobrescrevendo o método onCycleDetected, porém não funcionou, será que fiz certo? alguma outra solução?

:shock:

Você retirou @XmlTransient do Hotspot também?

J

Sim retirei… É Isso?

J

Coloquei de volta e resolveu, agora ficou claro e está fazendo exatamente da forma que eu imaginava!

Obrigado pela paciência! :?

J

Criei um novo hotspot relacionado a um product já existente, esse product já se relacionava com um hotspot antes, ou seja, com esse, ele agora se relaciona com dois, porém está pegando somente um dos registros relacionados e não todos, alguma sugestão?

J

Na verdade se eu quero um JSON dá certo, só ocorre isso em XML!

Criado 14 de fevereiro de 2012
Ultima resposta 15 de fev. de 2012
Respostas 15
Participantes 2