Задача
У меня есть два объекта, Deck
и Card
, которые имеют отношение многие ко многим. Сущность для таблицы соединения - DeckCard
, содержащая встроенный идентификатор DeckCardId
, чтобы облегчить доступ к карточкам из объектов колоды без создания бесконечного цикла. (Весь код ниже.)
Когда я получаю все колоды, используя JpaRepository.findAll()
, я правильно получаю список всех колод, каждая из которых имеет атрибут cards
, содержащий список карт DeckCards. При детализации до 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":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, чтобы предположить, почему это происходит или как это предотвратить.
Edit:
Я пытался изменить Deck.cards
, DeckCardId.deck
и DeckCardId.card
на eager, и я получил тот же результат. Однако при отладке я заметил, что даже с FetchType.LAZY
я получаю полный объект DeckCard.id.card
в Eclipse, и только после сериализации List<Deck>
JSON содержит идентификатор только для последующих появлений каждой карты. Может ли это быть проблема сериализации Джексона, а не проблема 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
, выбранный ответ заставил меня понять, что он содержит ту же аннотацию JsonIdentityInfo
, что и Deck
:
@Entity
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = Card.class
)
public class Card {
// rest of class
При удалении этой аннотации из Card
все экземпляры после первого каждого Card
сериализуются полностью, а не по ссылке.