5

While creating an online shop application using play-1.2.4 ,I ran into some problems with jpa..I wanted to provide an admin area using the CRUD module in play.Here ,an admin user can create/edit or delete the entities in the application(like Customers,Orders,Items etc).

A Customer can create Orders.Each Order will have a Set of CartItems.When an Order is deleted,the corresponding CartItems must be deleted.When a Customer is deleted,all his Orders must be deleted as well.I thought I could get this by setting the cascade property in jpa annotation.

I modelled it like this

Customer.java

@Entity
public class Customer extends Model {
    @Email
    @Required
    public String email;
    ...
    @OneToMany(mappedBy="customer", cascade=CascadeType.ALL)
    public List<Order> orders;
    @OneToOne
    public PayMethod currentPayment;
    ...
}

Order.java

@Entity
public class Order extends Model {  
    @OneToMany( cascade=CascadeType.ALL,orphanRemoval=true,fetch=FetchType.EAGER)
    public Set<CartItem> cartItems;

    @ManyToOne
    public Customer customer;
    @ManyToOne
    public PayMethod paymentMethod;
    ...
}

CartItem.java

@Entity
public class CartItem extends Model implements Comparable<CartItem>{    
    @ManyToOne
    public Item item;
    public int quantity;
}

PayMethod.java

@Entity
public class PayMethod extends Model {
    @Required
    public String cardNumber;
    @ManyToOne
    public Customer customer;
    ...
}

The following database tables were created

customer table

id |  email      |   fullname    | currentpayment_id
---|-------------|---------------|-----------------
2  |jon@gmail.com| jon           |29 

order table

 id |customer_id | paymentmethod_id 
----+------------+-----------------
 25 |  2         |       29

cartitem table

id  | quantity | item_id 
----+----------+---------
 26 |        1 |      14

*order_cartitem table*

 order_id | cartitems_id 
----------+--------------
       25 |           26

In the Admin interface created using CRUD(I haven't implented any methods ,just using the provided CRUD module as is),I tried to delete a Customer,but then,I get this error,

ERROR: update or delete on table "cartitem" violates foreign key constraint "fk7ff437ad3e28aa91" on table "order_cartitem"
Detail: Key (id)=(26) is still referenced from table "order_cartitem".
08:03:03,031 ERROR ~ Could not synchronize database state with session

Is there something wrong with the way I modelled the Entities? I thought the delete on the Customer will be cascaded to Order and that in turn will cascade to its CartItems .

What do I have to do to get this cascading effect?Or do I have to manually remove each contained instances of CartItems?

EDIT: As per Seb's reply

class Order extends Model { 
    @OneToMany(mappedBy="order", cascade=CascadeType.ALL,orphanRemoval=true,fetch=FetchType.EAGER)
    public Set<CartItem> cartItems;
    ...
}

class CartItem extends Model implements Comparable<CartItem>{

    @ManyToOne
    public Item item;

    public int quantity;

    @ManyToOne
    public Order order;
...
}

static void addItemToCart(Long itemId,Long orderId,String quantity) {
    Item item = Item.findById(itemId);
   Order order = Order.findById(orderId);
   int qty = Integer.parseInt(quantity);
   CartItem cartItem = new CartItem(item,qty);
   cartItem.order=order;
   order.addItem(cartItem, qty);
   order.save();
...
}

This gets rid of order_cartitem table and adds a field order_id to cartitem table

cartitem table

     id | quantity | item_id | order_id 
    ----+----------+---------+----------
     26 |        1 |      14 |       25    
     27 |        1 |      20 |       25

The admin(CRUD) interface,lists the Customers and Orders.When a particular Customer(the one who created the Order) is selected and the delete button is clicked,it results in a JPA error

JPA error
A JPA error occurred (Cannot commit): collection owner not associated with session: models.Order.cartItems

here is the stacktrace

If someone can understand why this is happening,please tell me.

Interestingly,I can click on the delete button for a particular Order,and it successfully calls the following controller method,

Admin.java

public static void deleteOrder(Long id) {
    Order order = Order.findById(id);
    order.delete();     
    ...
}

which deletes the Order and all its CartItems successfully.. So,why does this not happen when a Customer is deleted?

Damon Julian
  • 3,829
  • 4
  • 29
  • 34

3 Answers3

2

Make your relation bidirectionnal by adding this to your cartitem class

@ManyToOne
public Order order;

and a mappedBy="order" in your cartItems @OneToMany

then hibernate will better handles your deletion. I am guessing that without this bidirectionnal link, hibernate tried to firstly set the column to null. You can also try to allow null values in your join table to see what happens if you don't want to enable the bidirectional relationship

Seb Cesbron
  • 3,823
  • 15
  • 18
  • thanks for the reply..I tried this,Now ,when I tried to remove a Customer through CRUD(Admin),I got this error: ERROR ~ an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session) org.hibernate.AssertionFailure: collection owner not associated with session: models.Order.cartItems – Damon Julian Mar 05 '12 at 10:18
  • 1
    cascade deletion in hibernate is a bit tricky. I must admit that I never really understand all the cases and do some empiric tests to find the solution. Orphan removal is very touchy because hibernate need a custom collection implementation. You can try without it. You may also try with eager fetching between customer and order but this may not be the best solution in terms of performance. – Seb Cesbron Mar 05 '12 at 15:54
0

I had a similar problem, but I was not using a List, I was using an entities' array.

The problem with my wrapping entity was that it did not initialized the array (new MyEntity[0]) as I usually do with my lists (new ArrayList(0)).

I realized (empirical method) that when trying to call "merge" to a "null" array associated by an "all-delete-orphan" relationship, the "org.hibernate.AssertionFailure: collection owner not associated with session:" arose.

So my piece of advice is: when using a "all-delete-orphans" relationship, initialize your collection (array included) with an empty instance.

Alejo Ceballos
  • 491
  • 5
  • 5
0

There is a problem with cascade properties of JPA. Try to use related cascade annotations from hibernate and remove all of cascades you used from JPA. Here is an example for your PayMethod model class:

// Be careful, import the realted annotations from hibernate (not JPA)
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

@Entity
public class PayMethod extends Model {
    @Required
    public String cardNumber;

    @ManyToOne
    @OnDelete(action = OnDeleteAction.CASCADE)
    public Customer customer;
    ...
}

Above method works for me with uni-directional mapping. I hope this solve your problem.