JPA composite primary key with null value
Asked Answered
D

1

16

I have a table containing customer data in an oracle database. Here is a simplified definition:

CUSTOMER (CUSTOMER_ID NUMBER NOT NULL,
          SOURCE_SYSTEM VARCHAR2(30),
          FULL_NAME VARCHAR2(360),
          PHONE_NUMBER VARCHAR2(240)
          )

The primary key for this table is (CUSTOMER_ID, SOURCE_SYSTEM).

The table has numerous rows for which SOURCE_SYSTEM is null. At the database level, there is no issue, however when I try to access any of these rows via JPA Entity, it causes a number of issues:

1: Using em.find() to fetch a row with a null SOURCE_SYSTEM always results in a null being returned.

2: Using em.merge() to upsert a row with a null SOURCE_SYSTEM succeeds if the record does not exist in the table, but fails on subsequent updates because the merge ALWAYS results in an insert being run.

3: Using em.createQuery() to explicitly query for a row with a null causes the following exception:

Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.1.v20111018-r10243):
   org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
CUSTOMER.CUSTOMER_ID => 1
CUSTOMER.FULL_NAME => GUY PERSON
CUSTOMER.PHONE_NUMBER => 555-555-1234
CUSTOMER.SOURCE_SYSTEM => null)] during the execution of the query was detected to be null.
   Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Customer sql="SELECT CUSTOMER_ID, FULL_NAME, PHONE_NUMBER, SOURCE_SYSTEM FROM CUSTOMER WHERE ((CUSTOMER_ID = ?) AND (SOURCE_SYSTEM IS NULL))")

Unfortunately, "Primary keys must not contain null" seems pretty final. I was unable to find too much information on workarounds for this error, which makes it seem like there is no solution.

THE QUESTION: I would like to know if anyone has any Java code-based solution that don't involve making changes to the database. My current workaround is to use ROW_ID as the @Id of the table, but this means I can no longer use em.merge() or em.find().

Here are my Java classes:

Customer.java:

@Entity
@Table(name = "CUSTOMER")
public class Customer implements Serializable {
  private static final long serialVersionUID = 1L;

  @EmbeddedId
  private Customer_Id key;

  @Column(name = "CUSTOMER_ID", nullable = false, insertable = false, updatable = false)
  private Long customerId;
  @Column(name = "SOURCE_SYSTEM", length = 30, insertable = false, updatable = false)
  private String sourceSystem;

  @Column(name = "FULL_NAME", length = 360)
  private String fullName;
  @Column(name = "PHONE_NUMBER", length = 240)
  private String phoneNumber;

  //Setters, Getters, etc
  ...
}

Customer_Id.java

@Embeddable
public class Customer_Id implements Serializable {
  private static final long serialVersionUID = 1L;

  @Column(name = "CUSTOMER_ID", nullable = false)
  private Long customerId;
  @Column(name = "SOURCE_SYSTEM", length = 30)
  private String sourceSystem;

  //Setters, Getters, etc
  ...
}
Dilly answered 28/3, 2013 at 17:1 Comment(6)
Why are you using null for customer_id? It looks like something that should use sequencing, and if it is not null, should allow the source_system field to be null. You can try specifying the validation to be used as described here eclipse.org/eclipselink/documentation/2.4/jpa/extensions/… but I'm not sure why what you have wouldn't work. You are mapping the customer_id field twice though so make sure you are setting both correctlyVeronica
Also turn logging on to see if there are problems with your mappings, or the SQL that is generated. Queries for null fields generally must be slightly different which might not be occurring since its a pk check. wiki.eclipse.org/EclipseLink/Examples/JPA/Logging describes Eclipselink loggingVeronica
CUSTOMER_ID will never be null, and is marked as nullable = false in the class as well as NOT NULL in the database, although it is not based on a sequence. It is very likely that the sql for em.find() is being generated with SOURCE_SYSTEM = '' instead of SOURCE_SYSTEM IS NULL, which is why the record is not being returned properly.Dilly
I have attempted to implement the entity class using the EclipseLink specific @PrimaryKey annotation with validation = IdValidation.NONE, but the end behavior was exactly the same as with the @EmbeddedId annotation shown above.Dilly
You really should implements equals and hashCode to take into account the nullable field.. also see #674604Grishilda
Why are you not having source_system as nullable = false? Please make it nullable = false too and then from your program always set some value for it. Even if you assign "" string the composite primary key will be calculated by using both fields and if that composite is unique the queries would be fine. But if you are assigning null each time to this 'source_system' then i am afraid you will not succeed.Guerdon
R
1

Primary keys cannot contain null (in JPA or in databases). Use a different value such as "" or " ".

Is the customer_id unique? if so then just remove the sourceSystem from the Id.

Otherwise, you could try logging a bug to have support for null ids added.

Relevant answered 2/4, 2013 at 13:29 Comment(2)
CUSTOMER_ID is not unique, and I cannot make changes to the table as it is already being used by other applications. Unfortunately, Oracle views the empty string to be a null value, and, I can't change the existing null values in the table to suit my own needs since it would affect any other applications which currently expect a null in SOURCE_SYSTEM.Dilly
Could you define a secondary index which replaces the empty string with a suitable placeholder?Camfort

© 2022 - 2024 — McMap. All rights reserved.