简体   繁体   中英

JPA/Hibernate Composite Primary Foriegn Key ManyToOne Mapping

*** Solved - Updated w/ the Resolution* **

I have two tables:

notification - composite key made of UserId + NotificationDetail

notification_detail - single primary key

I want to use JPA to persist and read these objects but cannot get the ManyToOne mapping to work.

Many notification map to one notification_detail .

Tables:

create table notification(
notification_user_id        int             not null,
notification_detail_id     int              not null,
notification_status_cd      int             not null,
primary key(notification_user_id, notification_detail_id),
constraint pkey_notification_detail foreign key(notification_detail_id) references notification_detail(notification_detail_id),
constraint pkey_notification_status_cd foreign key(notification_status_cd) references lk_notification_status(notification_status_id)
);

create table notification_detail (
notification_detail_id  int             auto_increment,
notification_type_cd    int             not null,
notification_message    varchar(50)     not null,
primary key(notification_detail_id),
constraint pkey_notification_type_cd foreign key(notification_type_cd) references lk_notification_type(notification_type_id)
);

*** Resolved and Correct JPA Setup: * **

class NotificationId implements Serializable {
    private static final long serialVersionUID = 1L;
    Integer notificationUserId;
    Integer details;
    public Integer getNotificationUserId() {
        return notificationUserId;
    }
    public void setNotificationUserId(Integer notificationUserId) {
        this.notificationUserId = notificationUserId;
    }
    public Integer getDetails() {
        return details;
    }
    public void setDetails(Integer details) {
        this.details = details;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((details == null) ? 0 : details.hashCode());
        result = prime * result + ((notificationUserId == null) ? 0 : notificationUserId.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NotificationId other = (NotificationId) obj;
        if (details == null) {
            if (other.details != null)
                return false;
        } else if (!details.equals(other.details))
            return false;
        if (notificationUserId == null) {
            if (other.notificationUserId != null)
                return false;
        } else if (!notificationUserId.equals(other.notificationUserId))
            return false;
        return true;
    }
}

@Entity
@Table(name="notification")
@IdClass(NotificationId.class)
public class Notification implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @NotNull
    private Integer notificationUserId;

    @Id
    @NotNull
    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="notification_detail_id")
    private NotificationDetail details;

    @NotNull
    private Integer notificationStatusCd;

    @NotNull
    private Integer updateUserId;

    public Integer getNotificationUserId() {
        return notificationUserId;
    }

    public void setNotificationUserId(Integer notificationUserId) {
        this.notificationUserId = notificationUserId;
    }

    public NotificationDetail getDetails() {
        return details;
    }

    public void setDetails(NotificationDetail details) {
        this.details = details;
    }

    public Integer getNotificationStatusCd() {
        return notificationStatusCd;
    }

    public void setNotificationStatusCd(Integer notificationStatusCd) {
        this.notificationStatusCd = notificationStatusCd;
    }

    public Integer getUpdateUserId() {
        return updateUserId;
    }

    public void setUpdateUserId(Integer updateUserId) {
        this.updateUserId = updateUserId;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((details == null) ? 0 : details.hashCode());
        result = prime * result + ((notificationStatusCd == null) ? 0 : notificationStatusCd.hashCode());
        result = prime * result + ((notificationUserId == null) ? 0 : notificationUserId.hashCode());
        result = prime * result + ((updateUserId == null) ? 0 : updateUserId.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Notification other = (Notification) obj;
        if (details == null) {
            if (other.details != null)
                return false;
        } else if (!details.equals(other.details))
            return false;
        if (notificationStatusCd == null) {
            if (other.notificationStatusCd != null)
                return false;
        } else if (!notificationStatusCd.equals(other.notificationStatusCd))
            return false;
        if (notificationUserId == null) {
            if (other.notificationUserId != null)
                return false;
        } else if (!notificationUserId.equals(other.notificationUserId))
            return false;
        if (updateUserId == null) {
            if (other.updateUserId != null)
                return false;
        } else if (!updateUserId.equals(other.updateUserId))
            return false;
        return true;
    }

}

@Entity
@Table(name="notification_detail")
public class NotificationDetail implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer notificationDetailId;

    @NotNull
    private Integer notificationTypeCd;

    @NotNull
    private String notificationMessage;

    @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="details")
    private Set<Notification> notifications;

    @NotNull
    private Integer updateUserId;

    public Integer getNotificationDetailId() {
        return notificationDetailId;
    }

    public void setNotificationDetailId(Integer notificationDetailId) {
        this.notificationDetailId = notificationDetailId;
    }

    public Integer getNotificationTypeCd() {
        return notificationTypeCd;
    }

    public void setNotificationTypeCd(Integer notificationTypeCd) {
        this.notificationTypeCd = notificationTypeCd;
    }

    public String getNotificationMessage() {
        return notificationMessage;
    }

    public void setNotificationMessage(String notificationMessage) {
        this.notificationMessage = notificationMessage;
    }

    public Set<Notification> getNotifications() {
        return notifications;
    }

    public void setNotifications(Set<Notification> notifications) {
        this.notifications = notifications;
    }

    public void addNotification(Notification notification){
        if(notifications == null)
            notifications = new HashSet<Notification>();
        notifications.add(notification);
    }

    public Integer getUpdateUserId() {
        return updateUserId;
    }

    public void setUpdateUserId(Integer updateUserId) {
        this.updateUserId = updateUserId;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((notificationDetailId == null) ? 0 : notificationDetailId.hashCode());
        result = prime * result + ((notificationMessage == null) ? 0 : notificationMessage.hashCode());
        result = prime * result + ((notificationTypeCd == null) ? 0 : notificationTypeCd.hashCode());
        result = prime * result + ((notifications == null) ? 0 : notifications.hashCode());
        result = prime * result + ((updateUserId == null) ? 0 : updateUserId.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NotificationDetail other = (NotificationDetail) obj;
        if (notificationDetailId == null) {
            if (other.notificationDetailId != null)
                return false;
        } else if (!notificationDetailId.equals(other.notificationDetailId))
            return false;
        if (notificationMessage == null) {
            if (other.notificationMessage != null)
                return false;
        } else if (!notificationMessage.equals(other.notificationMessage))
            return false;
        if (notificationTypeCd == null) {
            if (other.notificationTypeCd != null)
                return false;
        } else if (!notificationTypeCd.equals(other.notificationTypeCd))
            return false;
        if (notifications == null) {
            if (other.notifications != null)
                return false;
        } else if (!notifications.equals(other.notifications))
            return false;
        if (updateUserId == null) {
            if (other.updateUserId != null)
                return false;
        } else if (!updateUserId.equals(other.updateUserId))
            return false;
        return true;
    }

}

JPA Usage:

NotificationDetail details = new NotificationDetail();
details.setMessage("Hello");
details.setTypeCd(NotificationTypeEnum.INFO);
detail.setUpdateUserId(userId);

Notification notif = new Notification();
notif.setNotifiedUserId(userId);
notif.setStatusCd(NotificationStatusEnum.UNREAD);
notif.setDetails(details);

notificationRepository.save(notif);

*** This error is now resolved * **:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'notificati0_.details_notificationDetailId' in 'field list'

*** Original Problem: * **

I've tried all types of different configurations. The best I can come up with is that my situation is complicated because the parent object "notification" has a composite key "notification_detail_id" that is the primary key of the child as well. This makes it's very difficult for Hibernate to persist the child first, then take the key and save it for the parent.

I realize my annotations are incorrect somewhere but just can't figure it out.

It looks like you are dealing with a "derived identity". Change your classes to look like this:

class NotificationId implements Serializable {
    private static final long serialVersionUID = 1L;
    Integer notifiedUserId;
    Integer details;
    ...
}


@Entity
@IdClass(NotificationId.class)
@Table(name="notification")
public class Notification implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @NotNull
    @Column(name="notification_user_id")
    private Integer notifiedUserId;

    @Id
    @NotNull
    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="notification_detail_id")
    private NotificationDetail details;

    @NotNull
    @Column(name="notification_status_cd")
    private Integer statusCd;

    @Column(name="update_timestamp")
    private Date timestamp;

    ...
}

NB:

  • NotificationId.notificationDetailId has been renamed to details to match the corresponding field in Notification
  • Notification.notificationDetailId has been removed
  • Notification.details has been marked as an @Id

Your field model seems to imply you should do the same thing with the relation between Notification and User .

Derived identities are discussed in the JPA 2.1 spec, section 2.4.1.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM