Hibernate Many-To-One Relationship without Primary Key or Join Table
Asked Answered
I

2

21

Problem

I would like to start by saying that I realize the database structure is atrocious, but I cannot change it at this point in time.

That being said, I have the need to create a one-to-many, bi-directional relationship in Hibernate (4.2.1) which involves no primary keys (only a unique key on the "parent" side of the relationship) and no join tables. The foreign key representing this relationship is a backpointer from the "child" to the "parent" (see below). I have searched and tried various different annotation configurations with no luck. Is what I'm asking for possible?

Database

GLOBAL_PART

CREATE TABLE "GLOBAL_PART" (    
    "GLOBAL_PART_ID" NUMBER NOT NULL,
    "RELEASES" NUMBER,
    CONSTRAINT "GLOBAL_PART_PK" PRIMARY KEY ("GLOBAL_PART_ID"),
    CONSTRAINT "GLOBAL_PART_RELEASES_UK" UNIQUE ("RELEASES")
);

PART_RELEASE

CREATE TABLE "PART_RELEASE" (
    "PART_RELEASE_ID" NUMBER NOT NULL,
    "COLLECTION_ID" NUMBER,
    CONSTRAINT "PART_RELEASE_PK" PRIMARY KEY ("PART_RELEASE_ID"),
    CONSTRAINT "GLOBAL_PART_RELEASE_FK" FOREIGN KEY ("COLLECTION_ID")
        REFERENCES "GLOBAL_PART" ("RELEASES") ON DELETE CASCADE ENABLE
);

Reference:

PART_RELEASE                 GLOBAL_PART
-------------------          ------------
PART_RELEASE_ID (PK)         GLOBAL_PART_ID (PK)
COLLECTION_ID -------------> RELEASES (UK)

Java

GlobalPart.java

@Entity
@Table(name = "GLOBAL_PART")
@SequenceGenerator(name = "SEQUENCE_GENERATOR", sequenceName = "GLOBAL_PART_SEQ")
public class GlobalPart extends ModelBase implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "GLOBAL_PART_ID", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQUENCE_GENERATOR")
    private Long globalPartId;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "COLLECTIONGID")
    private Set<PartRelease> releases;

    public Long getGlobalPartId() {
        return globalPartId;
    }

    public void setGlobalPartId(Long globalPartId) {
        this.globalPartId = globalPartId;
    }

    public Set<PartRelease> getReleases() {
        return releases;
    }

    public void setReleases(Set<PartRelease> releases) {
        this.releases = releases;
    }

}

PartRelease.java

@Entity
@Table(name = "PART_RELEASE")
@SequenceGenerator(name = "SEQUENCE_GENERATOR", sequenceName = "PART_RELEASE_SEQ")
public class PartRelease extends ModelBase implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "PART_RELEASE_ID", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQUENCE_GENERATOR")
    private String partReleaseId;

    @ManyToOne
    @JoinColumn(name = "RELEASES")
    private GlobalPart globalPart;

    public String getPartReleaseId() {
        return partReleaseId;
    }

    public void setPartReleaseId(String partReleaseId) {
        this.partReleaseId = partReleaseId;
    }

    public GlobalPart getGlobalPart() {
        return globalPart;
    }

    public void setGlobalPart(GlobalPart globalPart) {
        this.globalPart = globalPart;
    }

}

Any help would be greatly appreciated!!

Issacissachar answered 7/6, 2013 at 17:25 Comment(0)
S
42

First of all, in a bidirectional association, there is always an owner side, which defines the mapping, and an inverse side, marked by the presence of the mappedBy attribute.

In a OneToMany association, the owner side is always the many side (PartRelease in your case).

Moreover, a join column, by default, references the ID of the entity it references. Since it's not what you want, you have to specify the referenced column name.

And of course, the RELEASES column must be mapped:

public class GlobalPart extends ModelBase implements Serializable {

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "globalPart")
    private Set<PartRelease> partReleases;

    @Column(name = "RELEASES")
    private Long releasesNumber;
}

public class PartRelease extends ModelBase implements Serializable {
    @ManyToOne
    @JoinColumn(name = "COLLECTION_ID", referencedColumnName = "RELEASES")
    private GlobalPart globalPart;

}

Associations are well described in the documentation. You should read it.

Selfness answered 7/6, 2013 at 21:37 Comment(1)
Perfect! Thank you so much! I had originally gone this route, but I was missing the releaseNumber property mapped to the RELEASES column. Eventually I convinced myself I was doing it wrong (by misreading MappingException) and took the approach mentioned in the question. Thanks, again!Issacissachar
B
3

Whenever you need to map a @ManyToOne using a non-Primary Key column on the parent-side, you have to use the referencedColumnName attribute of the @JoinColumn annotation.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
    name = "RELEASES", 
    referencedColumnName = "COLLECTIONGID"
)

I used FetchType.LAZY for the @ManyToOne because, by default, FetchType.EAGER fetching is used, which is very bad for performance.

Berners answered 15/8, 2017 at 13:37 Comment(2)
please suppose I used best practices in a bi-directional onetomany association. and used the non primary key join column as you said. now I want to fetch a simple ManyToOne entity by having an id. but hibernate logging show an extra select on another side entity with where on referencedColumnName. why? while when I use primary key join column, this extra select does not exist.Tympanites
@GhasemSadeghi I am also facing the same issue. Did you find any solution to thisChader

© 2022 - 2024 — McMap. All rights reserved.