JPA only returns first instance of @ManyToOne object Code Answer

Hello Developer, Hope you guys are doing great. Today at Tutorial Guruji Official website, we are sharing the answer of JPA only returns first instance of @ManyToOne object without wasting too much if your time.

The question is published on by Tutorial Guruji team.

Problem

I have two objects, Deck and Card, that have a many-to-many relationship. The entity for the join table is DeckCard, containing an embedded id DeckCardId to facilitate accessing cards from inside deck objects without creating an infinite loop. (All the code is below.)

When I retrieve all decks using JpaRepository.findAll(), I correctly receive a list of all decks, each of which has a cards attribute containing a list of DeckCards. When drilling down into DeckCard.id.card, I expect to see the full Card object with all its attributes, like this:

[
    {
        "id":1,
        "name":"Deck 1",
        "cards":[
            {
                "id":{
                    "deck":1,
                    "card":{
                        "id":1,
                        "name":"Card 1"
                    }
                },
                ...
            },
            {
                "id":{
                    "deck":1,
                    "card":{
                        "id":2,
                        "name":"Card 2"
                    }
                },
                ...
            }
        ]
    },
    {
        "id":2,
        "name":"Deck 2",
        "cards":[
            {
                "id":{
                    "deck":2,
                    "card":{
                        "id":1,
                        "name":"Card 1"
                    }
                },
                ...
            },
            {
                "id":{
                    "deck":2,
                    "card":{
                        "id":3,
                        "name":"Card 3"
                    }
                },
                ...
            }
        ]
    },
    {
        "id":3,
        "name":"Deck 3",
        "cards":[
            {
                "id":{
                    "deck":3,
                    "card":{
                        "id":3,
                        "name":"Card 3"
                    }
                },
                ...
            },
            {
                "id":{
                    "deck":3,
                    "card":{
                        "id":4,
                        "name":"Card 4"
                    }
                },
                ...
            }
        ]
    },
]

For the first appearance of each Card, this is true; however, for all subsequent appearances of that card in other decks, all I get is the id of the card:

[
    {
        "id":1,
        "name":"Deck 1",
        "cards":[
            {
                "id":{
                    "deck":1,
                    "card":{
                        "id":1,
                        "name":"Card 1"
                    }
                },
                ...
            },
            {
                "id":{
                    "deck":1,
                    "card":{
                        "id":2,
                        "name":"Card 2"
                    }
                },
                ...
            }
        ]
    },
    {
        "id":2,
        "name":"Deck 2",
        "cards":[
            {
                "id":{
                    "deck":2,
                    "card":1 // I expected this to be the full model of Card 1
                },
                ...
            },
            {
                "id":{
                    "deck":2,
                    "card":{
                        "id":3,
                        "name":"Card 3"
                    }
                },
                ...
            }
        ]
    },
    {
        "id":3,
        "name":"Deck 3",
        "cards":[
            {
                "id":{
                    "deck":3,
                    "card":3 // I expected this to be the full model of Card 3
                },
                ...
            },
            {
                "id":{
                    "deck":3,
                    "card":{
                        "id":4,
                        "name":"Card 4"
                    }
                },
                ...
            }
        ]
    },
]

I’d guess that this has something to do with a default setting to avoid unnecessary repetition, but I haven’t found anything in the JPA documentation to suggest why this is happening or how to prevent it.

Edit:

I’ve tried changing Deck.cards, DeckCardId.deck, and DeckCardId.card to eager, and I get the same result. I did notice in debugging, however, that even with FetchType.LAZY I get the full DeckCard.id.card object in Eclipse, and it’s only after the List<Deck> has been serialized that the JSON only contains the id for subsequent appearances of each card. Could it be a Jackson serialization issue, rather than a JPA issue?

Code

Deck.java

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id",
    scope = Deck.class
)
public class Deck {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @OneToMany(
        fetch = FetchType.LAZY,
        mappedBy = "id.deck",
        cascade = CascadeType.ALL
    )
    private List<DeckCard> cards;

    // standard getters & setters
}

DeckCard.java

@Entity
@AssociationOverrides({
    @AssociationOverride(name = "id.deck", joinColumns = @JoinColumn(name = "deck_id")),
    @AssociationOverride(name = "id.card", joinColumns = @JoinColumn(name = "card_id"))
})
public class DeckCard implements Serializable {

    @EmbeddedId
    private DeckCardId id = new DeckCardId();

    private int quantity;

    public DeckCard() {}

    // standard getters & setters for id & quantity

    @Transient
    public Deck getDeck() {
        return getId().getDeck();
    }

    public void setDeck(Deck deck) {
        getId().setDeck(deck);
    }

    @Transient
    public Card getCard() {
        return getId().getCard();
    }

    public void setCard(Card card) {
        getId().setCard(card);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        DeckCard that = (DeckCard) o;
        if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null)
            return false;

        return true;
    }

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

DeckCardId.java

@Embeddable
public class DeckCardId implements Serializable {

    private static final long serialVersionUID = -6470278480687272622L;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    private Deck deck;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    private Card card;

    // standard getters & setters

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        DeckCardId that = (DeckCardId) o;
        if (deck != null ? !deck.equals(that.deck) : that.deck != null)
            return false;
        if (card != null ? !card.equals(that.card) : that.card != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = (deck != null ? deck.hashCode() : 0);
        return 31 * result + (card != null ? card.hashCode() : 0);
    }
}

Solution

While I hadn’t originally posted the code for the Card class, the selected answer made me realize that it contained the same JsonIdentityInfo annotation that Deck did:

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id",
    scope = Card.class
)
public class Card {

    // rest of class

By removing this annotation from Card, all instances after the first of each Card are serialized in full instead of by reference.

Answer

I ran into the exact same problem the other day. Turns out that it’s not JPA’s fault (you can see in the debugger that it correctly maps all instances) but rather the Jackson serializer.

You need to modify or remove the JsonIdentityInfo from your class and suddenly all instances will have their relationships mapped!

We are here to answer your question about JPA only returns first instance of @ManyToOne object - If you find the proper solution, please don't forgot to share this with your team members.

Related Posts

Tutorial Guruji