Search code examples
jpaeclipselink

EclipseLink creates additional table from Embeddable class when using a composite primal key


I have a table with a composite primary key (2 columns) and modelled it as an entity and an embeddable in JPA. Code compiles and runs. However, when starting the server, EclipseLink creates a new table containing exactly these 2 columns. This makes me think that for some reason he does not understand that these are columns of the table represented by the entity, or he interpretes it as a join. I annotated the embeddable as @Embeddable and the composite key in the entity as @Embedded, thinking that should do. Maybe, there's some other annotation necessary?

My table's name is TABLE_PKSTAT_DATA_STATUS, the newly created one is denoted TABLE_PKSTAT_DATA_STATUS_TABLE_PKSTAT_DATA_STATUS, i.e. twice the original name with a connecting underscore. The PK columns' names are period and company_number.

I am using Java 17, Eclipse version is 2023-03 (4.27.0), EclipseLink JPA Support Version is 3.4.101.v202301061019. The table already existed in an Oracle DB and was not created by EclipseLink.

This is an excerpt of my entity:

@Entity
@EntityListeners(PkstatStatusChangeListener.class)
@Table(name = "table_pkstat_data_status")
@NamedQuery(name = "getPkstatDataStatus", query = "SELECT statusData "
        + "FROM PkstatDataStatus statusData "
        + "LEFT JOIN FETCH statusData.freezeUser "
        + "LEFT JOIN FETCH statusData.distributionRunUser "
        + "LEFT JOIN FETCH statusData.manualDistributionUser "
        + "WHERE statusData.statusId.companyNr = :companyNr")
@EqualsAndHashCode
@NoArgsConstructor
@Getter
@Setter
public class PkstatDataStatus implements Serializable {

    private static final long serialVersionUID = 1L;

    private static Logger logger = LogManager.getLogger();

    private List<PkstatDataStatus> data;

    @EmbeddedId
    private PkstatDataStatusPK statusId;

    @Column(name = "status")
    @Enumerated(EnumType.ORDINAL)
    private StatusOfPeriod status = StatusOfPeriod.STATUS_INVALID;

    @Column(name = "freeze_date")
    @Temporal(value = TemporalType.DATE)
    @Setter(value = AccessLevel.NONE)
    private Date freezeDate;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @Setter(value = AccessLevel.NONE)
    @PrimaryKeyJoinColumn(name = "freeze_user", referencedColumnName = "id")
    private UmUser freezeUser;

    @Column(name = "distribution_run_date")
    @Temporal(value = TemporalType.DATE)
    @Setter(value = AccessLevel.NONE)
    private Date distributionRunDate;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @Setter(value = AccessLevel.NONE)
    @PrimaryKeyJoinColumn(name = "distribution_run_user", referencedColumnName = "id")
    private UmUser distributionRunUser;

    @Column(name = "manual_distribution_date")
    @Temporal(value = TemporalType.DATE)
    @Setter(value = AccessLevel.NONE)
    private Date manualDistributionDate;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @PrimaryKeyJoinColumn(name = "manual_distribution_user", referencedColumnName = "id")
    private UmUser manualDistributionUser;

    @Column(name = "mega_import_date")
    @Temporal(value = TemporalType.DATE)
    @Setter(value = AccessLevel.NONE)
    private Date megaImportDate;

    [...]

And here's the embeddable:

@Getter
@Setter
@EqualsAndHashCode
@Embeddable
public class PkstatDataStatusPK implements Serializable {

    private static final long serialVersionUID = 1L;

    @Column(name = "period", nullable = false)
    @Setter(value = AccessLevel.PACKAGE)
    @Temporal(value = TemporalType.DATE)
    private Date period;

    @Column(name = "company_number", nullable = false)
    @Setter(value = AccessLevel.PACKAGE)
    private int companyNr;

    public PkstatDataStatusPK() {
    }

    public PkstatDataStatusPK(final Date period, final int companyNr) {
        this.period = period;
        this.companyNr = companyNr;
    }

}

This is the table creation as dumped out to console:

DEBUG 2023-08-22 09:56:35,957 JPA [pkstatkore][] - CREATE TABLE table_pkstat_data_status_table_pkstat_data_status (period DATE NOT NULL, company_number NUMBER(10) NOT NULL, PRIMARY KEY (period, company_number))
DEBUG 2023-08-22 09:56:35,999 JPA [pkstatkore][] - default_tables_created [table_pkstat_data_status_table_pkstat_data_status]
DEBUG 2023-08-22 09:56:36,001 JPA [pkstatkore][] - ALTER TABLE table_pkstat_data_status ADD CONSTRAINT tblpksttdtastatusdstrbtnrnuser FOREIGN KEY (distribution_run_user) REFERENCES dwh_um_user (id)
DEBUG 2023-08-22 09:56:36,053 JPA [pkstatkore][] - SELECT 1 FROM DUAL

And, few lines below, a constraint is added to the new table as well:

DEBUG 2023-08-22 09:56:36,434 JPA [pkstatkore][] - ALTER TABLE table_pkstat_data_status_table_pkstat_data_status ADD CONSTRAINT tblpksttdtsttstblpksttdtsttprd FOREIGN KEY (period, company_number) REFERENCES table_pkstat_data_status (period, company_number)

Additional info: As you can see, I am using a 3-fold join to UmUser table which I am not too happy with. However, this does not seem to cause the current problem as it still existed when I changed the 3 UmUser fields to simple int fields and removed the joins for testing purposes.

BTW: I also changed the model to using @IdClass rather than @Enbedded/@Embeddable. This resulted in the same effect.

Does anybody have an idea why Eclipselink keeps creating this new table?


Solution

  • table_pkstat_data_status_table_pkstat_data_status is the default JPA requires for a default unidirectional OneToMany or ManyToMany join table from 'table_pkstat_data_status' to PkstatDataStatus/'table_pkstat_data_status'. This is occurring because you have:

    private List<PkstatDataStatus> data;
    

    within your entity, which defaults to a ManyToMany and requires the relational table. I suspect there are only two fields setup because EclipseLink is confused on your primary key - or it is just having problems with the self-reference, though I believe this should work.

    If you don't intend this collection to be mapped, you would need to mark it as @Transient.

    Also note, your use of @PrimaryKeyJoinColumn in your entity doesn't seem to match your primary key. This annotation indicates that the entity you are using it on (PkstatDataStatus in this case) is using the foreign key within the annotation as its Primary key as well. It is a special case of JoinColumn, and since you don't have the 'freeze_user', 'distribution_run_user' and other columns in your embeddedId, I think you've misused it when you really should just be using @JoinColumn to indicate these columns are foreign keys to the respective targeted Entity's Id columns. ie:

      @ManyToOne(fetch = FetchType.EAGER, optional = false)
      @Setter(value = AccessLevel.NONE)
      @JoinColumn(name = "freeze_user", referencedColumnName = "id")
      private UmUser freezeUser;
    

    Also note that Lombok's @EqualsAndHashCode implementation leave quite a bit to be desired in JPA entities; Two entities really should be considered the 'same' data instance if they share the same ID (assuming it is immutable, which it isn't when an entity is first created). Any sort of hash/equals calculation that requires checking relationships may also have issues with recursion, as they have to go through the object graph. IMO it is better to just use the Java object implementation unless you have a strong need for a specific implementation.