Search code examples
javahibernatejpaormbidirectional

Hibernate JPA one to one bidirectional mapping with shared primary key foreign key field is null (hb can't set it automatically)


So I was trying to get some unidirectional one-to-one mapping the last day and after some testing I decided to try out the other approach. Is stumbled upon a problem when I tried to persist my entities. I can't find a way to force hibernate to populate my foreign key field with reference back and forth at the same time. It sets them only one way and not the other:

Item ---> ItemInfo (I populate info field manually in constructor)

Item <-x- ItemInfo (hibernate doesn't set item field on persist)

so when I try to persist Item entity I get exception from @MapsId : attempted to assign id from null one-to-one property [by.test.hm.ItemInfo.item]

My tables:

enter image description here

t_item_info(item_id) references t_item(id) field. Only t_item(id) column is auto incremented

my classes:

@Entity
@Table(name = "t_item")
class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(mappedBy = "item", cascade = CascadeType.ALL, 
              fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
    private ItemInfo info;

    private String name;

}

and the other one:

@Entity
@Table(name = "t_item_info")
class ItemInfo {

    @Id
    private Long itemId;

    @MapsId
    @OneToOne
    @JoinColumn(name = "item_id", referencedColumnName = "id")
    private Item item;


    private String descr;

}

How I create and persist objects:

Item item = new Item(
    null,
    new ItemInfo(null, "Somde description"),
    "Some name"
);
itemRepo.save(item);

Exception:

org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [by.test.hm.ItemInfo.item] at org.hibernate.id.ForeignGenerator.generate(ForeignGenerator.java:87) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:115) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:113)

I tried a lot of things. Switching side where I declare mappedBy from Item to ItemInfo in which case everything just stops working because ItemInfo tries to get persisted first. I tried all possible combinations of optional = false\true and nullable = false\true and nothing seems to work =(

I know I can set the references manually an exception will be gone, and I know I can use @PostPersist to set it but i'm looking for answer when hibernate does this for me automatically

PS: I posted similar question regarding unidirectional mapping and I'm new around here so I hope I did nothing wrong when created a new question for similar issue


Solution

  • @Entity
    @Table(name = "t_item")
    class Item {
            
         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)
         private Long id;
         @OneToOne(mappedBy = "item", cascade = CascadeType.ALL, 
                          fetch = FetchType.LAZY, orphanRemoval = true, optional=false)
         private ItemInfo info;
         
         @Column(name="name)
         private String name;
                
         // Getters and setters omitted for brevity 
               
         private void setItemInfo(ItemInfo info) {
            if (info == null) {
               if (this.info != null) {
                  this.info.setInfo(null);
               }
            } else {
                 info.setInfo(this);
            }
         this.info = info;  
    }
    
    @Entity
    @Table(name = "t_item_info")
    class ItemInfo {
    
        @Id
        private Long itemId;
    
        @OneToOne
        @MapsId
        @JoinColumn(name="item_id)
        private Item item;
    
        @Column(name="description")
        private String description;
    
        // Getters and setters omitted for brevity
    
    }
    

    Out of topic, but your classes have same name.

    Try the above code ..............