usmanwalana
usmanwalana

Reputation: 1062

Updating List items(preserving primary keys) in an entity using JpaRepository

Its better to ask using an example so...

An entity PurchaseOrder has a list of PurchaseOrderDetail. OneToMany Relation. Entity and list items cannot be directly saved used JPA methods as there is some calculations involved. When we perform an update on PurchaseOrder we have to preserve the primary key so we can update like this

PurchaseOrder purchaseOrder = purchaseOrderRepository.getOne(purchaseOrderRequest.getId());
purchaseOrder.setSupplier(purchaseOrderRequest.getSupplier());
purchaseOrder.setDollarRate(purchaseOrderRequest.getDollarRate());
purchaseOrder.setShippingCost(purchaseOrderRequest.getShippingCost());
return purchaseOrderRepository.save(purchaseOrder);

But how to Beautifullyupdate the List<PurchaseOrderDetail> purchaseOrderDetails ?

I have the following code which will delete existing list items and will insert new items losing the primary keys. But what is the JPA way to do so that during update following happens

1. If some items were deleted they should be deleted from DB.
2. If some items were updated they should be updated preserving the primary keys.
3. If some new items are added they should be created having new primary keys.

Code

purchaseOrder.setPurchaseOrderDetails(null);

        for (PurchaseOrderDetailRequest purchaseOrderDetailRequest : purchaseOrderRequest.getPurchaseOrderDetails())
        {
            PurchaseOrderDetail purchaseOrderDetail = new PurchaseOrderDetail();
            purchaseOrderDetail.setPartNumber(purchaseOrderDetailRequest.getPartNumber());
            purchaseOrderDetail.setDescription(purchaseOrderDetailRequest.getDescription());
            purchaseOrderDetail.setQuantity(purchaseOrderDetailRequest.getQuantity());
            purchaseOrderDetail.setUnitPriceInDollars(purchaseOrderDetailRequest.getUnitPriceInDollars());
            purchaseOrderDetail.setTotalPriceInDollars(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetailRequest.getUnitPriceInDollars());
            purchaseOrderDetail.setUnitPriceInSAR(purchaseOrder.getDollarRate() * purchaseOrderDetailRequest.getUnitPriceInDollars());
            purchaseOrderDetail.setTotalPriceInSAR(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetail.getUnitPriceInSAR());
            purchaseOrderDetail.setUnitCost(purchaseOrderDetail.getUnitPriceInSAR() + shippingFactor);
            purchaseOrder.addPurchaseOrderDetail(purchaseOrderDetail);
        }

PurchaseOrder

@Entity
@Table(name = "purchaseOrders")
public class PurchaseOrder extends UserDateAudit
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 140)
    private String supplier;

    @NotNull
    private Float dollarRate;

    @NotNull
    private Float amount;

    @NotNull
    private Float shippingCost;

    @OneToMany(
            mappedBy = "purchaseOrder",
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER,
            orphanRemoval = true
    )
    @Fetch(FetchMode.SELECT)
    @BatchSize(size = 150)
    private List<PurchaseOrderDetail> purchaseOrderDetails = new ArrayList<>();
}

PurchaseOrderDetail

@Entity
@Table(name = "purchaseOrderDetails")
public class PurchaseOrderDetail
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 50)
    private String partNumber;

    @Size(max = 256)
    private String description;

    @NotNull
    private Integer quantity;

    @NotNull
    private Float unitPriceInDollars;

    @NotNull
    private Float totalPriceInDollars;

    @NotNull
    private Float unitPriceInSAR;

    @NotNull
    private Float totalPriceInSAR;

    @NotNull
    private  Float unitCost;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "purchaseOrder_id", nullable = false)
    private PurchaseOrder purchaseOrder;
}

Upvotes: 2

Views: 2679

Answers (1)

davidxxx
davidxxx

Reputation: 131326

JpaRepository.save() does em.persist() or em.merge() according to the entity state.
And you also configured the mapping of the @OneToMany relationship with cascade = CascadeType.ALL and orphanRemoval = true, so you are in the right way.
What you have to do in your code is updating the PurchaseOrderDetail elements of the List defined in PurchaseOrder instead of creating new instances for them.
You could add the existing elements in a Map to fast up the lookup processing if required.

// collect existing details into a map to fast up
Map<Long, PurchaseOrderDetail> purchaseOrderDetailByIdMap = 
purchaseOrder.getPurchaseOrderDetails()
             .stream()
             .collect(toMap(PurchaseOrderDetail::getId, p -> p);

//collect new details into a list
List<PurchaseOrderDetail> updatedDetails = 
purchaseOrderRequest.getPurchaseOrderDetails()
                    .stream()
                    .map(detailReq -> mapDetailRequestToDetail(detailReq, 
                                      purchaseOrderDetailByIdMap))
                    .collect(toList());

// overwrite the relationship with the updatedDetails var
purchaseOrder.setPurchaseOrderDetails(updatedDetails);

// ...      

// mapping function extracted to be clearer
private PurchaseOrderDetail mapDetailRequestToDetail(PurchaseOrderDetailRequest purchaseOrderDetailRequest, 
                         Map<Long, PurchaseOrderDetail> purchaseOrderDetailByIdMap) {             

    // Here you get the existing element with the defined id or you create a new one            
    PurchaseOrderDetail purchaseOrderDetail = 
    purchaseOrderDetailByIdMap.computeIfAbsent(purchaseOrderDetailRequest.getId(), k-> new PurchaseOrderDetail());

    // set fields
    purchaseOrderDetail.setPartNumber(purchaseOrderDetailRequest.getPartNumber());
    purchaseOrderDetail.setDescription(purchaseOrderDetailRequest.getDescription());
    purchaseOrderDetail.setQuantity(purchaseOrderDetailRequest.getQuantity());
    purchaseOrderDetail.setUnitPriceInDollars(purchaseOrderDetailRequest.getUnitPriceInDollars());
    purchaseOrderDetail.setTotalPriceInDollars(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetailRequest.getUnitPriceInDollars());
    purchaseOrderDetail.setUnitPriceInSAR(purchaseOrder.getDollarRate() * purchaseOrderDetailRequest.getUnitPriceInDollars());
    purchaseOrderDetail.setTotalPriceInSAR(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetail.getUnitPriceInSAR());
    purchaseOrderDetail.setUnitCost(purchaseOrderDetail.getUnitPriceInSAR() + shippingFactor);

    return purchaseOrderDetail;         
}

Upvotes: 1

Related Questions