【问题标题】:JPA only returns first instance of @ManyToOne objectJPA 仅返回 @ManyToOne 对象的第一个实例
【发布时间】:2018-09-15 18:57:47
【问题描述】:

问题

我有两个对象,DeckCard,它们具有多对多关系。连接表的实体是 DeckCard,包含一个嵌入的 ID DeckCardId,以方便从甲板对象内部访问卡片,而不会创建无限循环。 (所有代码都在下面。)

当我使用JpaRepository.findAll() 检索所有牌组时,我正确收到了所有牌组的列表,每个牌组都有一个包含牌组列表的cards 属性。当深入到DeckCard.id.card 时,我希望看到完整的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":{
                        "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"
                    }
                },
                ...
            }
        ]
    },
]

对于每个Card的第一次出现,这是真的;但是,对于该卡在其他套牌中的所有后续出现,我得到的只是卡的 ID:

[
    {
        "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"
                    }
                },
                ...
            }
        ]
    },
]

我猜这与默认设置有关,以避免不必要的重复,但我在 JPA 文档中没有找到任何内容来说明为什么会发生这种情况或如何防止它。

编辑:

我尝试将 Deck.cardsDeckCardId.deckDeckCardId.card 更改为 Eager,我得到了相同的结果。然而,我在调试中确实注意到,即使使用FetchType.LAZY,我也能在 Eclipse 中获得完整的DeckCard.id.card 对象,并且只有在List<Deck> 被序列化之后,JSON 才只包含用于随后出现的每张卡的 id。会不会是 Jackson 序列化问题,而不是 JPA 问题?

代码

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);
    }
}

解决方案

虽然我最初没有发布 Card 类的代码,但选定的答案让我意识到它包含与 Deck 相同的 JsonIdentityInfo 注释:

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

    // rest of class

通过从Card 中删除此注释,每个Card 的第一个之后的所有实例都将被完整序列化,而不是通过引用。

【问题讨论】:

  • 定义为懒惰。除非您调用 get 方法,否则不会加载详细信息。尝试更改为渴望并查看行为。
  • @notionquest 感谢您的建议!我会编辑这个问题,但简短的回答是它产生了相同的结果。

标签: java json spring hibernate jpa


【解决方案1】:

前几天我遇到了完全相同的问题。事实证明,这不是 JPA 的错(您可以在调试器中看到它正确映射了所有实例),而是 Jackson 序列化程序。

您需要从您的班级修改或删除JsonIdentityInfo,突然间所有实例的关系都将被映射!

【讨论】:

  • 这正是我所需要的。我没有将Card 的代码放入(我将编辑问题),但它也有JsonIdentityInfo,这导致第一个实例之后的所有实例都通过引用而不是完整序列化。
猜你喜欢
  • 1970-01-01
  • 2021-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
  • 2014-09-24
相关资源
最近更新 更多