Charlee Chitsuk
Charlee Chitsuk

Reputation: 9069

JPA2: Nested Primary Key class cannot persist as binding null

I'm using JPA 2.x with EclipseLink 2.5.1 with nested primary key class as the following: -

Note: The behind the scene environment

1. OS: Windows 7 64 Bits
2. JDK: 1.7.0_65 64 Bits
3. Maven: 3.2.2
4. Arquillian: 1.1.5.Final
5. Container: Glassfish 4 embedded
6. Database: Derby 10.10.2.0 embedded

The Master PK

@Embeddable
public class MasterPk {
    @Column(
        name   = "MASTER_ID",
        length = 5
    )
    private String masterId;

    //----> Setter/Getter is omitted
}

The Master

@Entity
@Table(name = "MASTER_TAB")
public class Master {

    @EmbeddedId
    private MaskerPk id;

    @Column(
        name   = "MASTER_NAME",
        length = 40
    )
    private String masterName;

    @OneToMany(
        mappedBy = "master"
    )
    private List<Detail> details;

    //----> Setter/Getter is omitted
}

The Detail PK

@Embeddable
public class DetailPk {

    //----> THE NESTED IS HERE.
    @Embedded
    private MasterPk masterId;

    @Column(
        name   = "DETAIL_ID",
        length = 5
    )
    private String detailId;

    //----> Setter/Getter is omitted
}

The Detail

@Entity
@Table(name = "DETAIL_TAB")
public class Detail {

    @EmbeddedId
    private DetailPk id;

    @Column(
        name   = "DETAIL_NAME",
        length = 40
    )
    private String detailName;

    @MapsId("masterId")
    @ManyToOne
    @JoinColumns({
        @JoinColumn(
            name                 = "MASTER_ID",
            referencedColumnName = "MASTER_ID",
            nullable             = false
        )
    })
    private Master master;

    //----> Setter/Getter is omitted
}

The coding for persisting the Detail

MasterPk masterPk = new MasterPk();
masterPk.setId("001"); //there is a 001 existed in the db

DetailPk detailPk = new DetailPk();
detailPk.setMasterId(masterPk);
detailPk.setDetailId("001"); //new detail to persist

Detail detail = new Detail();
detail.id(detailPk);
detail.setDetailName("detail-name");

em.persist(detail);

The persistence XML

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence 
                   http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="unittest" transaction-type="JTA">

        <jta-data-source>jdbc/unittest</jta-data-source>

        <class>com.test.MasterPk</class>
        <class>com.test.Master</class>
        <class>com.test.DetiailPk</class>
        <class>com.test.Detail</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>
            <property name="eclipselink.ddl-generation" 
                      value="create-tables" />                     
            <property name="eclipselink.ddl-generation.output-mode"
                      value="both"/>
            <property name="eclipselink.application-location" 
                      value="target"/>                     
            <property name="eclipselink.create-ddl-jdbc-file-name" 
                      value="createDDL_ddlGeneration.jdbc"/>
            <property name="eclipselink.drop-ddl-jdbc-file-name" 
                      value="dropDDL_ddlGeneration.jdbc"/>
        </properties>

    </persistence-unit>
</persistence>

When the project starts, all tables are created correctly, especially the primary key and foreign key. But when persist with the coding., there are some trouble about the Detail as the Detail.masterId.id is null. The EclipseLink shows me as

Column 'MASTER_ID' cannot accept a NULL value.

INSERT INTO DETAIL_TAB (DETAILE_NAME, DETAIL_ID, MASTER_ID)
VALUES (?, ?, ?)
bind => [detail-name, 001, null]

//
//----> We may be noticed that the masterId is not null. It is printed as 001.
//
Query: InsertObjectQuery(Detail(detailName=detail-name,
                                id=DetailPk(detailId=001,
                                            masterId=(MasterPk(masterId=001))
                                            )
                                )
                         )

I double check by printing all field in this object-graph and can confirm that the masterId is not null as well. I'm not sure if doing something wrong/misunderstanding. Could you please help to advise how to solve this issue?

Edit 1

  1. Fix the @ManyToOne at the Detail.java. It is a typo when copying and pasting to this question
  2. Try to add the @Embedded to the masterId at the class DetailPk.

    Edit 1 result still faces the same issue.

Edit 2

  1. Add the coding for create the detail and persist
  2. I create the MasterPk directly without finding the Master.

    Further question, Should I need to find the Mater entity for getting its id and assigning it to the DetailPk instead of create it directly via new MasterPk();?

Upvotes: 1

Views: 867

Answers (1)

wypieprz
wypieprz

Reputation: 8219

As MasterPK is an embeddable it should be annotated with @Embedded:

@Embeddable
public class DetailPk {

    @Embedded
    private MasterPk masterId;
    ...
}

I guess Detail entity is also missing @ManyToOne (or it's just intentional?)

@Entity
@Table(name = "DETAIL_TAB")
public class Detail {
    ...
    @MapsId("masterId")
    @ManyToOne
    @JoinColumns({
        @JoinColumn(
            name                 = "MASTER_ID",
            referencedColumnName = "MASTER_ID",
            nullable             = false
        )
    })
    private Master master;
}

Ad Edit 2

The following statement

INSERT INTO DETAIL_TAB (DETAILE_NAME, DETAIL_ID, MASTER_ID)
VALUES (?, ?, ?)
bind => [detail-name, 001, null]

means that the owning side of the master-detail relationship (represented by @ManyToOne association) is null. From JPA point of view this looks as follows:

  • MASTER_ID column is related to master field in Detail entity
  • Master entity uses MasterPK as the primary key
  • Detail entity references Master with master field
  • because Detail entity acts as the owner of the relationship it would be good to specify cascading on it, i.e. @ManyToOne(cascade = CascadeType.PERSIST)

Based on the above, to make thing work we would need to set up the owning side, for example:

Master master = new Master();
master.id(masterPk);    

Detail detail = new Detail();
detail.id(detailPk);
detail.setMaster(master); // the owning side of the master-detail relationship

master.setDetail(detail); // mandatory if CascadeType.PERSIST is not defined
em.persist(detail);

This will result in:

INSERT INTO DETAIL_TAB (DETAIL_NAME, DETAIL_ID, MASTER_ID)
VALUES (?, ?, ?)
bind => [detail-name, 001, 001]

Upvotes: 1

Related Questions