The problem with JPA Composite primary keys and CascadeType.ALL

Following my previous blog here, I encountered another issue with the use of composite primary keys.

With JPA, we can specify the cascade of relationship to be CascadeType.ALL, so that when we persist the CUSTOMER, all of its PRODUCT are persisted too.

So, the code will be modified as follows:

CUSTOMER

package com.wordpress.dwuysan;

// imports omitted

@Entity
@Table(name = "customer")
public class Customer implements Serializable {
    @Id
    @Column(name = "customer_id")
    private String customerId;

    @OneToMany(
        fetch = FetchType.EAGER,
        mappedBy = "customer",
        cascade = CascadeType.ALL)
    private Set<Product> products = new HashSet<Product>();

    /* the rest of the code omitted */
}

During unit testing however, when executing the following lines:

    this.entityManager.persist(customer); // customer with child products here
    final Query query = this.entityManager.createQuery("FROM Customer cust");
    return query.getResultList(); // error here

And the error is:

javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
	at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:637)
	at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:74)
	at com.wordpress.dwuysan.CustomerServiceBean(CustomerServiceBean.java:59)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)

SO, for now, I think I will use another ID for the PRODUCT. Would need to look for the underlying reason.

Advertisements

@JoinColumn is part of the composite primary keys

Whilst it is not very common (hmmm … , come to think of it, it may be quite common), there are cases where one would require the use of composite primary keys in JPA, but at the same time, one of the key is actually a foreign key to another parent table.

Let’s just say for example there are table CUSTOMER and PRODUCT (Smith 2007), in this case the PRODUCT uses composite primary key of PRIMARY KEY (customer_id, product_id).

In JPA, they are mapped as follows:

CUSTOMER

package com.wordpress.dwuysan;

// imports omitted

@Entity
@Table(name = "customer")
public class Customer implements Serializable {
    @Id
    @Column(name = "customer_id")
    private String customerId;

    @OneToMany(
        fetch = FetchType.EAGER,
        mappedBy = "customer")
    private Set<Product> products = new HashSet<Product>();

    /* the rest of the code omitted */
}

PRODUCTPK

package com.wordpress.dwuysan;

// imports omitted

@Embeddable
public class ProductPK implements Serializable {
    private String productId;
    private String customerId;

    /* the rest of the code omitted */
}

PRODUCT

package com.wordpress.dwuysan;

// imports omitted

@Entity
@Table(name = "product")
public class Product implements Serializable {

    @AttributeOverrides({
        @AttributeOverride(
            name = "customerId",
            column = @Column(name = "customer_id")),
        @AttributeOverride(
            name = "productId",
            column = @Column(name = "productId"))
    })
    private ProductPK productPK;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

However, on compiling, I got the following error from JUnit:

Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.wordpress.dwuysan.Product column: customer_id (should be mapped with insert="false" update="false")
	at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:652)
	at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:674)
	at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:696)
	at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:450)
	at org.hibernate.mapping.RootClass.validate(RootClass.java:192)
	at org.hibernate.cfg.Configuration.validate(Configuration.java:1108)

So, to resolve this, we need to modify the Product class to:

PRODUCT

package com.wordpress.dwuysan;

// imports omitted

@Entity
@Table(name = "product")
public class Product implements Serializable {

    @AttributeOverrides({
        @AttributeOverride(
            name = "customerId",
            column = @Column(name = "customer_id")),
        @AttributeOverride(
            name = "productId",
            column = @Column(name = "productId"))
    })
    private ProductPK productPK;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "customer_id", insertable = false, updatable = false)
    private Customer customer;
}

Reference:
Smith, J, 2007, ‘Composite Primary Keys’, accessed 22 February 2012.