Damian
Damian

Reputation: 11

How to implement JPA composite key properly? Throws an error

I'm working on a simple store project in spring. First, I implemented Item to Order mapping as another entity called Position. It had a sequentially generated id before, but to my knowledge composite key (order_id, item_id) was a better solution. I've read a lot of internet articles regarding this topic, but I'm in a dead-end right now. The application launches correctly and generates all the tables correctly, but after inserting throws an error.

2020-07-02 11:38:01.864 ERROR 4676 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: Could not set field value [2] value by reflection : [class com.wave.greenboxrest.model.PositionId.itemId] setter of com.wave.greenboxrest.model.PositionId.itemId; nested exception is org.hibernate.PropertyAccessException: Could not set field value [2] value by reflection : [class com.wave.greenboxrest.model.PositionId.itemId] setter of com.wave.greenboxrest.model.PositionId.itemId] with root cause

And another one: java.lang.NullPointerException: null

I tried to make sure there are getters and setters on every field but it seems that's not the reason.

OrderController::createOrder method, after inserting the error occurs.

@PostMapping("/create")
    public void createOrder(@RequestBody OrderCreateDto orderDto){
        Set<Position> positions = new HashSet<>();
        Order order = new Order();
        for(PositionCreateDto positionDto: orderDto.positions){
            Position position =
                    new Position(itemRepository.findById(positionDto.itemId).get(), positionDto.weight);
            position.setOrder(order);
            positions.add(position);
        }
        order.setPersonName(orderDto.personName);
        order.setAddress(orderDto.address);
        order.setPhoneNumber(orderDto.phoneNumber);
        order.setPositions(positions);
        orderRepository.saveAndFlush(order);
    }
@Entity(name = "positions")
public class Position {

    @EmbeddedId
    private PositionId id;

    @JsonIgnore
    @NotNull
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "order_id")
    @MapsId("orderId")
    private Order order;

    @NotNull
    @OneToOne(cascade = CascadeType.ALL)
    @MapsId("itemId")
    private Item item;

    @NotEmpty
    private Double weight;

    public Position() {
    }

    public Position(@NotNull Item item, @NotEmpty Double weight) {
        this.item = item;
        this.weight = weight;
    }

    public PositionId getId() {
        return id;
    }

    public void setId(PositionId id) {
        this.id = id;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    public Order getOrder() {
        return order;
    }

    public Item getItem() {
        return item;
    }

    public void setItem(Item item) {
        this.item = item;
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }

    @JsonProperty("subtotal")
    public Double calculateSubtotal(){
        return item.getPrice() * weight;
    }

}
@Embeddable
public class PositionId implements Serializable {

    private Long orderId;

    private Long itemId;

    public PositionId(){
    }

    public PositionId(Long orderId, Long itemId) {
        this.orderId = orderId;
        this.itemId = itemId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PositionId that = (PositionId) o;
        return orderId.equals(that.orderId) &&
                itemId.equals(that.itemId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(orderId, itemId);
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getItemId() {
        return itemId;
    }

    public void setItemId(Long itemId) {
        this.itemId = itemId;
    }
}

Could anyone guide me how to get through the error? Also is the createOrder method implemented correctly?

Upvotes: 0

Views: 1229

Answers (2)

Damian
Damian

Reputation: 11

Finally, I've solved the issue! The trick was to instantiate PositionId when creating a Position.

public Position(Order order, Item item, Double weight) {
        this.order = order;
        this.item = item;
        this.weight = weight;
        this.id = new PositionId(order.getId(), item.getId());
}

I was almost sure, that it is being done for me under the hood. Apparently I was wrong.

Upvotes: 1

Hesham Osman
Hesham Osman

Reputation: 111

It seems that hibernate could not map itemId column,so I guess that the itemId column will be "item_id", so try to modify your PositionId class to be like that

@Embeddable
public class PositionId implements Serializable {
@Column(name = "order_id")
private Long ordered;

@Column(name = "item_id")
private Long itemId;

public PositionId(){
}

public PositionId(Long orderId, Long itemId) {
    this.orderId = orderId;
    this.itemId = itemId;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    PositionId that = (PositionId) o;
    return orderId.equals(that.orderId) &&
            itemId.equals(that.itemId);
}

@Override
public int hashCode() {
    return Objects.hash(orderId, itemId);
}

public Long getOrderId() {
    return orderId;
}

public void setOrderId(Long orderId) {
    this.orderId = orderId;
}

public Long getItemId() {
    return itemId;
}

public void setItemId(Long itemId) {
    this.itemId = itemId;
}
}

Upvotes: 0

Related Questions