Как получить объект из коллекции объектов OneToMany?

#java #json #spring #hibernate #jpa

#java #json #весна #спящий режим #jpa

Вопрос:

У меня есть Order сущность и OrderProduct . Я хочу показать детали заказа в интерфейсе и, конечно, заказать в нем продукты. Итак, как получить объект продукта в OrderProduct JSON. Мне не хватает объекта product в массиве products. Мне больше не нужен объект order, и я думаю, что это будет бесконечная рекурсия. 🙂

Моя Order сущность:

 @Entity
@Getter
@Setter
@Table(name ="orders")
public class Order{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    private BigDecimal totalPrice;

    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JsonManagedReference(value="orders")
    private List<OrderProduct> products = new ArrayList<>();

    private int userId;

    @DateTimeFormat(pattern="dd/MM/yyyy")
    private Date date = new Date();

    @DateTimeFormat(pattern="dd/MM/yyyy")
    private Date deliveryDate;

    @Enumerated(EnumType.STRING)
    private OrderType orderType;

}
 

Моя OrderProduct сущность:

 @Entity
@Setter
@Getter
public class OrderProduct {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JsonBackReference(value="product")
    @JoinColumn(name = "product_id")
    private Product product;

    @ManyToOne
    @JsonBackReference(value="orders")
    @JoinColumn(name = "order_id")
    private Order order;

    private Integer quantity;
}

 

Сущность продукта:

 @Entity
@Getter
@Setter
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String name;

    private double price;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    @JsonManagedReference(value="ingredients")
    private List<Ingredient> ingredients = new ArrayList<>();

    @OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
    @JsonManagedReference(value="product")
    private List<OrderProduct> products = new ArrayList<>();

    private String fileName;

}
 

заказать JSON

Ответ №1:

Это может помочь аннотировать один из ваших классов объектов с помощью

 @JsonIdentityInfo(
    property = "id",
    generator = ObjectIdGenerators.PropertyGenerator.class
)
 

Каждый раз, когда сериализация JSON идет по кругу, данные объекта будут заменены идентификатором объекта или другим полем объекта по вашему выбору.

Комментарии:

1. К сожалению, это не работает для меня. Я пробовал это. Я добавил этот код в свою сущность отдельно, и он все тот же. Мне все еще не хватает объекта product в products[] .

Ответ №2:

Вы можете использовать @JsonView аннотации для определения полей, которые вам нужно сериализовать в JSON

Как это работает:

  1. Вам нужно определить класс с интерфейсами. Например:
     public class SomeView {
    
        public interface  id {}
    
        public interface CoreData extends id {}
    
        public interface FullData extends CoreData {}
    
    }
     
  2. Отметьте поля сущностей с @JsonView(<some interface.class>)
     public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @JsonView(SomeView.id.class)
        private Long id;
    
        @Column(nullable = false)
        @JsonView(SomeView.CoreData.class)
        private String username;
    
        @Column(nullable = false)
        @JsonView(SomeView.FullData.class)
        private String email;
    }
     
  3. Аннотировать конечную точку с помощью @JsonView(<some interface.class>)
      @GetMapping()
     @JsonView(<some interface.class>)
     public User getUser() {
         return <get user entity somwhere>
     }
     

В случае @JsonView(SomeView.id.class) , если вы получите этот JSON:

     {
        id: <some id>
    }
 

В случае @JsonView(SomeView.CoreData.class) :

     {
        id: <some id>,
        username: <some username>
    }
 

В случае @JsonView(SomeView.FullData.class) :

     {
        id: <some id>,
        username: <some username>,
        email: <some email>
    }
 

@JsonView также работает со встроенными объектами, и вы можете аннотировать одно поле с помощью классов multiple views — @JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})

В вашем случае, я думаю, вы должны аннотировать все поля, которые вам нужны, кроме:

 @OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
@JsonManagedReference(value="product")
private List<OrderProduct> products = new ArrayList<>();
 

в Product

чтобы избежать циклической сериализации

Или в качестве альтернативы вы можете просто использовать классы DTO или сериализовать извлечение в JSON вручную (https://thepracticaldeveloper.com/java-and-json-jackson-serialization-with-objectmapper /)

Ответ №3:

Это можно сделать с помощью моей библиотеки beanknife

 // This configure generate a class named ProductInfo which has the same shape with Product without property "products"
@ViewOf(value = Product.class, genName="ProductInfo", includePattern = ".*", excludes = {"products"})
class ProductInfoConfigure {}

// This configure generate a class named OrderProductRelation with the same shape of OrderProduct.
// But it has not order property and the type of its product property is change to ProductInfo generated above.
@ViewOf(value = OrderProduct.class, genName="OrderProductRelation", includePattern = ".*", excludes = {"order"})
class OrderProductRelationConfigure {
    @OverrideViewProperty("product")
    private ProductInfo product;
}

// This configure generate a class named OrderDetail with the same shape of Order. 
// But the type of its products property is change to List<OrderProductRelation>
@ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
    @OverrideViewProperty("products")
    private List<OrderProductRelation> products;
}

 

будет генерировать эти классы:

 class ProductInfo {
    private Long id;
    private String name;
    private double price;
    private List<Ingredient> ingredients; // it is not processed because you have not provide the class Ingredient
    private String fileName;
}

public class OrderProductRelation {
    private Long id;
    private ProductInfo product;
    private Integer quantity;
}

public class OrderDetail {
    public Long id;
    private BigDecimal totalPrice;
    private List<OrderProductRelation> products;
    private int userId;
    private Date date = new Date();
    private Date deliveryDate;
    private OrderType orderType;
}
 

Затем

 Order order = ...
OrderDetail orderDetail = OrderDetail.read(order);
// serialize the otherDetail instead of order.

List<Order> orders = ...
List<OrderDetail> orderDetails = OrderDetail.read(orders);
// serialize the orderDetails instead of orders.
 

Возможные проблемы:

  1. Я не использую Lombok, поэтому Lombok, возможно, потребуется адаптировать, потому что он изменяет байтовый код на лету. Но это не большая проблема, я постараюсь ее адаптировать, если кто-то зафиксирует проблему и предоставит достаточно вариантов использования.
  2. Сгенерированный класс не наследует аннотацию исходного класса. В следующем выпуске я предоставлю sulotion. На данный момент, в качестве обходного пути, мы можем использовать пользовательский метод для преобразования свойства вручную. например
 @ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
    @OverrideViewProperty("products")
    private List<OrderProductRelation> products;
    @OverrideViewProperty("orderType")
    public static String orderType(Order source) {
        return source.getOrder().name();
    }
}
 

Сгенерированный класс будет изменен на

 public class OrderDetail {
    public Long id;
    private BigDecimal totalPrice;
    private List<OrderProductRelation> products;
    private int userId;
    private Date date = new Date();
    private Date deliveryDate;
    private String orderType;
}
 

Обновить
Выпущена версия 1.2.0. Добавьте поддержку наследования аннотаций.

 @ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
@UseAnnotation({DateTimeFormat.class, Enumerated.class, JsonProperty.class})
class OrderDetailConfigure {
    @OverrideViewProperty("products")
    private List<OrderProductRelation> products;
}
 

генерировать

 public class OrderDetail {
    public Long id;
    private BigDecimal totalPrice;
    private List<OrderProductRelation> products;
    private int userId;
    @DateTimeFormat(pattern="dd/MM/yyyy")
    private Date date;
    @DateTimeFormat(pattern="dd/MM/yyyy")
    private Date deliveryDate;
    @Enumerated(EnumType.STRING)
    private OrderType orderType;
}