Search code examples
hibernatejpaeclipselinkjpa-2.0

OneToOne PrimaryKeyJoinColumn Cascade


I am struggling a little bit to make Eclipselink to cascade persist a relationship:

@Entity
class Notification {
    @Id
    @UuidGenerator(name="UUID_GEN")
    @GeneratedValue(generator="UUID_GEN")
    @Column(name = "NOTIFICATION_ID")
    private UUID id;


    @OneToOne(cascade = ALL, fetch = FetchType.EAGER)
    @JoinFetch(JoinFetchType.INNER)
    @PrimaryKeyJoinColumn(name="NOTIFICATION_ID", referencedColumnName="NOTIFICATION_ID")
    private Notificator notificator;

   ...
}

@Entity
class Notificator {

    @Id
    @Column(name="NOTIFICATION_ID")
    private UUID id;

    ...
}

So when i tried to persist a Notification object, Eclipselink failed to persist the enclosing Notificator object, because the Notificator.id was not set, so a constraint failure has happened.

In a second attempt, i tried to use @MapId annotation:

@Entity
class Notification {
    @Id
    @UuidGenerator(name="UUID_GEN")
    @GeneratedValue(generator="UUID_GEN")
    @Column(name = "NOTIFICATION_ID")
    private UUID id;


    @OneToOne
    @MapsId
    private Notificator notificator;

   ...
}

@Entity
class Notificator {

    @Id
    @Column(name="NOTIFICATION_ID")
    private UUID id;

    @OneToOne
    PrimaryKeyJoinColumn(name="NOTIFICATION_ID", referencedColumnName="NOTIFICATION_ID")
    private Notification notification; // didn't need to have it here, but fine.
    ...
}

This way, i couldn't even bring EntityManager. It throws this error:

Exception Description: A non-read-only mapping must be defined for the sequence number field.

Is there a way to make this cascade to work so i don't need to persist the two entities separately? I am trying to avoid this, since there are a few other entities sharing this same ID, so it would be kind of a dirty work as it stands.

Thank you


Solution

  • With your first attempt, you have the sequencing annotations on the field you are attempting to use as a foreign key, with nothing to set the Notificator.id field. If you want this to work, you would create a mapping from Notificator to Notification so that its 'ID' field is used as a foreign key.

    Unfortunately you are also incorrectly using the @PrimaryKeyJoinColumn annotation in your second attempt. The @PrimaryKeyJoinColumn is used to specify that the database field specified in the mapping is controlled and set by the field marked with @Id, essentially making the mapping insertable/updatable=false.

    To match the structure you seem to be after, you should try:

    @Entity
    class Notification {
        @Id
        @UuidGenerator(name="UUID_GEN")
        @GeneratedValue(generator="UUID_GEN")
        @Column(name = "NOTIFICATION_ID")
        private UUID id;
    
    
        @OneToOne(mappedby="notification", cascade = ALL, fetch = FetchType.EAGER)
        @JoinFetch(JoinFetchType.INNER)
        private Notificator notificator;
    
       ...
    }
    
    @Entity
    class Notificator {
    
        @Id
        @Column(name="NOTIFICATION_ID")
        private UUID id;
    
        @OneToOne
        @JoinColumn(name="NOTIFICATION_ID", referencedColumnName="NOTIFICATION_ID", insertable=false, updatable=false)
        private Notification notification; 
        ...
    }
    

    This is not exactly as you want as the above example requires you to set the Notificator.id value yourself once the value in the referenced Notification.id is available (post persist/flush).

    If you are using JPA 2.0, you can use the @MapsId in the Notificator class instead of specifying the join column to have JPA automatically set the Notificator.id field value for you in managed entities when your object graph is persisted:

    @Entity
    class Notificator {
    
        @Id
        private UUID id;
    
        @OneToOne
        @MapsId
        @JoinColumn(name="NOTIFICATION_ID", referencedColumnName="NOTIFICATION_ID")
        private Notification notification; 
        ...
    }
    

    As you pointed out, you don't really need both the Notificator.notification and Notificator.id value in your model, but you do need at least Notificator.notification if you want the value for Notificator."NOTIFICATION_ID" field to come from the notification. JPA 2.0 allows you to remove the Notificator.id and just use the Notificator.notification relationship as the entity id using:

    @Entity
    class Notificator {
    
        @Id
        @OneToOne
        @JoinColumn(name="NOTIFICATION_ID", referencedColumnName="NOTIFICATION_ID")
        private Notification notification; 
        ...
    }
    

    This allows you to still use the UUID value for identity lookups. FetchJoins etc can ensure that the relationships is always fetched, but a draw back is that JPQL that wishes to use the ID value would need to use notificator.notification.id to access it.