javaspringspring-bootjdbcspring-data-jdbc

How to save one-to many entity using Spring Data JDBC only?


I use only Spring Boot default configuration and Spring Data JDBC. I have 2 Entity. Order and DetailOrder

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

    @Id
    @Column("id")
    private Long id;

    @Column(value = "order_number")
    private String orderNumber;

    @Column(value = "total_amount")
    private Long totalAmount;

    @Column(value = "order_date")
    private Date orderDate;

    @Column(value = "customer_name")
    private String customerName;

    @Column(value = "address")
    private String deliveryAddress;

    @Column(value = "payment_type")
    private String paymentType;

    @Column(value = "delivery_type")
    private String deliveryType;

    @MappedCollection(idColumn = "order_id", keyColumn = "id")
    private List<DetailOrder> detailOrders;

public Order(Long id, String orderNumber, Long totalAmount, Date orderDate, String customerName, String deliveryAddress, String paymentType, String deliveryType, List<DetailOrder> detailOrders) {
        this.id = id;
        this.orderNumber = orderNumber;
        this.totalAmount = totalAmount;
        this.orderDate = orderDate;
        this.customerName = customerName;
        this.deliveryAddress = deliveryAddress;
        this.paymentType = paymentType;
        this.deliveryType = deliveryType;
        this.detailOrders = detailOrders;
    }

and all getters and setters

@Table(name = "products")
public class DetailOrder {

    @Id
    private Long id;

    @Column(value = "product_article")
    private Long productArticle;

    @Column(value = "product_name")
    private String productName;

    @Column(value = "product_amount")
    private int productAmount;

    @Column(value = "product_price")
    private int productPrice;

    @Column(value = "order_id")
    private Long orderId;

public DetailOrder(Long id, Long productArticle, String productName, int productAmount, int productPrice, Long orderId) {
        this.id = id;
        this.productArticle = productArticle;
        this.productName = productName;
        this.productAmount = productAmount;
        this.productPrice = productPrice;
        this.orderId = orderId;
    }

OrderRepository

public interface OrderRepository extends ListCrudRepository<Order, Long> {

    List<Order> findByOrderDateAndTotalAmountGreaterThan(Date date, long amount);


}

OrderService

public interface OrderService {

    Order createOrder(Order order);
    Optional<Order> getOrder(long id);
    List<Order> findAll();
    List<Order> findByOrderDateAndTotalAmountGreaterThan(Date date, long amount);
}

OrderServiceImpl

@Service
public class OrderServiceImpl implements OrderService {

    private final OrderRepository orderRepository;

    @Autowired
    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public Order createOrder(Order order) {
        try {
            order.setOrderNumber("generated");
            order.setOrderDate(Date.valueOf(LocalDate.now()));
            order.setTotalAmount(order.getDetailOrders().stream().mapToLong(detail -> detail.getProductPrice() * detail.getProductAmount()).sum());
            return orderRepository.save(order);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

OrderController

  @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        try {
            Order response = orderService.createOrder(order);
            System.out.println(response);
            return new ResponseEntity<>(response, HttpStatus.CREATED);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

Table Created like this

CREATE TABLE products (
                          id BIGSERIAL PRIMARY KEY ,
                          product_article BIGINT NOT NULL,
                          product_name VARCHAR(255) NOT NULL,
                          product_amount INT NOT NULL,
                          product_price INT NOT NULL,
                          order_id BIGINT,
                          CONSTRAINT fk_order
                              FOREIGN KEY (order_id)
                                  REFERENCES orders (id)
                                  ON DELETE SET NULL
);

CREATE TABLE orders(
    id BIGSERIAL PRIMARY KEY,
    order_number  varchar(255)                                        not null,
    total_amount  bigint                                              not null,
    customer_name varchar(255)                                        not null,
    address       varchar(255)                                        not null,
    delivery_type varchar(255),
    payment_type  varchar(255),
    order_date    date
)

When i try POST request

{
        "customerName": "Andrew Kramer",
        "deliveryAddress": "London",
        "paymentType": "Wired",
        "deliveryType": "TEST",
        "detailOrders": [
            {
                "productArticle": 52341252,
                "productName": "test8",
                "productAmount": 4,
                "productPrice": 2000
            },
            {
                "productArticle": 52341252,
                "productName": "test9",
                "productAmount": 2,
                "productPrice": 2000
            }
        ]
}

Failed to execute BatchWithValue{actions=[Insert{entity=DetailOrder{id=null, productArticle=12151, and this TRACE

 Executing prepared SQL statement [INSERT INTO "products" ("id", "order_id", "product_amount", "product_article", "product_name", "product_price") VALUES (?, ?, ?, ?, ?, ?)]
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 1, parameter value [0], value class [java.lang.Integer], SQL type 4
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 2, parameter value [25], value class [java.lang.Long], SQL type -5
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 3, parameter value [4], value class [java.lang.Integer], SQL type 4
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 4, parameter value [12151], value class [java.lang.Long], SQL type -5
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 5, parameter value [Morojja], value class [java.lang.String], SQL type 12
2024-12-02T21:44:36.809+03:00 TRACE 16736 --- [orders_service] [           main] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 6, parameter value [1200], value class [java.lang.Integer],

What am i doing wrong?????

I am trying all things. Change constructors. Use @PersistenceCreator. Try delete keyColumn = "id". But if delete i have new error about orders_key Executing prepared SQL statement [INSERT INTO "products" ("order_id", "orders_key", "product_amount", "product_article", "product_name", "product_price") VALUES (?, ?, ?, ?, ?, ?)]. I tried change List to Set and have same error


Solution

  • You are explicitly trying to manage foreign key in DetailOrder class , which is causing the issues. let spring data jpa take care of it automatically as you are using @MappedCollection in your Order class. So you need to remove below code from your DetailOrder class.

    @Column(value = "order_id") private Long orderId;

    And also remove it from your constructor as well , remove this line this.orderId = orderId;

    Also, i see that there is no need for having keyColumn = "id" in your @MappedCollection in your Order class , so remove it . so it should look something like this

    @MappedCollection(idColumn = "order_id") private List detailOrders;

    Your updated code should be like this for DetailOrder class and Order class :

    @Table(name = "products") public class DetailOrder {

    @Id
    private Long id;
    
    @Column(value = "product_article")
    private Long productArticle;
    
    @Column(value = "product_name")
    private String productName;
    
    @Column(value = "product_amount")
    private int productAmount;
    
    @Column(value = "product_price")
    private int productPrice;
    
    public DetailOrder(Long id, Long productArticle, String productName, int productAmount, int productPrice) {
        this.id = id;
        this.productArticle = productArticle;
        this.productName = productName;
        this.productAmount = productAmount;
        this.productPrice = productPrice;
    }
    
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public Long getProductArticle() {
        return productArticle;
    }
    
    public void setProductArticle(Long productArticle) {
        this.productArticle = productArticle;
    }
    
    public String getProductName() {
        return productName;
    }
    
    public void setProductName(String productName) {
        this.productName = productName;
    }
    
    public int getProductAmount() {
        return productAmount;
    }
    
    public void setProductAmount(int productAmount) {
        this.productAmount = productAmount;
    }
    
    public int getProductPrice() {
        return productPrice;
    }
    
    public void setProductPrice(int productPrice) {
        this.productPrice = productPrice;
    }
    

    }

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

    @Id
    @Column("id")
    private Long id;
    
    @Column(value = "order_number")
    private String orderNumber;
    
    @Column(value = "total_amount")
    private Long totalAmount;
    
    @Column(value = "order_date")
    private Date orderDate;
    
    @Column(value = "customer_name")
    private String customerName;
    
    @Column(value = "address")
    private String deliveryAddress;
    
    @Column(value = "payment_type")
    private String paymentType;
    
    @Column(value = "delivery_type")
    private String deliveryType;
    
    @MappedCollection(idColumn = "order_id")
    private List<DetailOrder> detailOrders;
    
    
    public Order(Long id, String orderNumber, Long totalAmount, Date orderDate, String customerName, String deliveryAddress, String paymentType, String deliveryType, List<DetailOrder> detailOrders) {
        this.id = id;
        this.orderNumber = orderNumber;
        this.totalAmount = totalAmount;
        this.orderDate = orderDate;
        this.customerName = customerName;
        this.deliveryAddress = deliveryAddress;
        this.paymentType = paymentType;
        this.deliveryType = deliveryType;
        this.detailOrders = detailOrders;
    }
    
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public String getOrderNumber() {
        return orderNumber;
    }
    
    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }
    
    public Long getTotalAmount() {
        return totalAmount;
    }
    
    public void setTotalAmount(Long totalAmount) {
        this.totalAmount = totalAmount;
    }
    
    public Date getOrderDate() {
        return orderDate;
    }
    
    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }
    
    public String getCustomerName() {
        return customerName;
    }
    
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    
    public String getDeliveryAddress() {
        return deliveryAddress;
    }
    
    public void setDeliveryAddress(String deliveryAddress) {
        this.deliveryAddress = deliveryAddress;
    }
    
    public String getPaymentType() {
        return paymentType;
    }
    
    public void setPaymentType(String paymentType) {
        this.paymentType = paymentType;
    }
    
    public String getDeliveryType() {
        return deliveryType;
    }
    
    public void setDeliveryType(String deliveryType) {
        this.deliveryType = deliveryType;
    }
    
    public List<DetailOrder> getDetailOrders() {
        return detailOrders;
    }
    
    public void setDetailOrders(List<DetailOrder> detailOrders) {
        this.detailOrders = detailOrders;
    }
    

    }