Search code examples
javaspringhibernateone-to-onebidirectional

Hibernate @OneToOne with Shared Primary Key(bidirectional). Dependent entity not persisted in DB.


I have two entities PointOfInterest (referred as POI from here on) and its Address. I want to define a one to one bidirectional mapping with shared primary key between the two, with POI as owner entity. I am using postgreSQL DB, POI table has POIId as PK, generated by a sequence generator defined in DB. Address table has column POIId which is the PK of Address table and also a FK to POI table's POIId column, along with other columns of its own.

**PointOfInterest.java**

@Entity
@Table(name = "\"POI\"")
public class PointOfInterest  implements Serializable {
    private static final long serialVersionUID = -5406785879200149642L;

    @Id
    @Column(name="\"POIId\"", nullable=false)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="poi_seq_poiid_generator")
    @SequenceGenerator(name = "poi_seq_poiid_generator", sequenceName = "poi_seq_poiid", allocationSize=1)
    private Long poiId;

    @OneToOne(mappedBy = "poi",cascade = CascadeType.PERSIST)
    private Address address;

    //Other fields

-----------------------------------------------------------------
    **Address.java**
    @Entity
    @Table(name="\"Address\"")
    public class Address implements Serializable{

        private static final long serialVersionUID = -7146133528411371107L;

        @Id
        @GeneratedValue(generator="sharedPrimaryKeyGenerator")
        @GenericGenerator(name="sharedPrimaryKeyGenerator",strategy="foreign",parameters =  @Parameter(name="property", value="poi"))
        @Column(name="\"POIId\"")
        private Long poiId;

       @OneToOne
       @PrimaryKeyJoinColumn
       private PointOfInterest poi;

Before saving the POI object using session.saveOrUpdate(poi). I instantiate and set all other properties of POI object. Then, obtain Address object from a separate method in my logic and do something like.

PointOfInterest poi = methodCallToGetPOI();
Address address = methodCallToGetAddress();

poi.setAddress(address);
address.setPOI(poi);

//Send POI object to DB layer to an appropriate method which does:

session.saveOrUpdate(poi);
session.flush();

When I see the query generated, I see something like this:

   Hibernate: 
   select
        nextval ('poi_seq_poiid')

 Hibernate: 
     insert 
     into
         "POI"
         ("CreatedBy", "CreatedTime", "LocationGeographic", "ModifiedBy", "ModifiedTime", "Name", "POICode", "Radius", "POIType", "POIId") 
     values
         (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

So, clearly hibernate is not making an insert statement in Address Table. I have searched everywhere on the internet and compared my mappings, they seem to be correct. However, no row is being inserted in the address table. When I debug the flow, I found that the address object is being instantiated and populated. Please help me figure out why is this. The only reason I can think of this that I am using a sequence generator and in all the examples and code snippets on the internet everybody has used hibernate's auto generation strategy, so, is it because of this? I cannot use auto gen key, I have to use my DB sequence only. If these annotations won't work, kindly suggest some alternative.

I am using Spring and Hibernate with spring framework version 4.0.5.RELEASE and a session factory obtained from org.springframework.orm.hibernate4.LocalSessionFactoryBean and transaction manager obtained from org.springframework.orm.hibernate4.HibernateTransactionManager.


Solution

  • you don't want to generate the PK on Address too (even with an ad-hoc generator).

    Since these two entities share the PK, the generation may happen only once. Then the mapping have to be responsible to populate both POI_ID and ADDRESS_ID with the same value.

    Strange thing, @PrimaryKeyJoinColumn does not work anymore on Hibernate 5.2.2, but here is an equivalent mapping:

    @Entity
    @Table(name = "POI")
    public class PointOfInterest implements Serializable
    {
        private static final long serialVersionUID = 1L;
    
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "poi_seq_poiid_generator")
        @SequenceGenerator(name = "poi_seq_poiid_generator", sequenceName = "poi_seq_poiid", allocationSize = 1)
        @Column(name = "POI_ID")
        private Long id;
    
        @OneToOne(mappedBy = "poi", cascade = CascadeType.ALL, orphanRemoval = true)
        private Address address;
    
        @Column
        private String name;
    
        ...
    }
    
    @Entity
    @Table(name = "Address")
    public class Address implements Serializable
    {
        private static final long serialVersionUID = 1L;
    
        @Id
        @OneToOne
        @JoinColumn(name = "ADDRESS_ID")
        private PointOfInterest poi;
    
        @Column
        private String name;
    
        ...
    }
    

    Using Hibernate DDL generation, this is the resulting SQL:

    create table Address (name varchar(255), ADDRESS_ID bigint not null, primary key (ADDRESS_ID)) ENGINE=InnoDB
    create table POI (POI_ID bigint not null, name varchar(255), primary key (POI_ID)) ENGINE=InnoDB
    alter table Address add constraint FK_Address_ADDRESS_ID foreign key (ADDRESS_ID) references POI (POI_ID)