VB_
VB_

Reputation: 45722

Hibernate: saving one-2-one obligatory relationship

I have two entities: A and B. B use the same PK as A entity. A and B linked with one-2-one, optional = false assosiation.

What I do:

//in session scope
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
session.save(a); 
session.flush(); //EXCEPTION!

Exception:

org.postgresql.util.PSQLException: ERROR: insert or update on table "B" violates foreign key constraint "fk_A_pk" Detail: Key (id)=(5600) is not present in table "A"

What a problem:

Due to PS3 I can't save A with null B-property, because relationship is obligatory. But I can't save A with not-null property, because A haven't aID yet, and B can't imagine own id.

Question:

How could I save A & B entities?

PS1 B.PK is simultaneously a FK to A.PK field.

PS2 From the generated sql, I see that Hibernate try to proceed 'insert into B ...' query, before it proceed 'insert into A'.

PS3

A class:

@Entity
@Table(name = "A")
@Inheritance(strategy= InheritanceType.JOINED)
public class A{

    @Id
    @SequenceGenerator(name = "a_sequence", sequenceName = "sq_a")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "a_sequence")
    @Column(name = "id")
    private long aID;

    @JsonIgnore
    @OneToOne(mappedBy="a", cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
    private B b;
}

B class:

@Entity
@Table(name="b")
public class B {
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(generator="gen")
    @GenericGenerator(name="gen", strategy="foreign", parameters=@Parameter(name="property", value="a"))
    private long aID;

    @JsonIgnore
    @PrimaryKeyJoinColumn
    @OneToOne(cascade = CascadeType.ALL)
    private A a;
}

ADDITION (PostgreSQL DDL):

CREATE TABLE A
(
  id bigint NOT NULL,
  CONSTRAINT pk_file PRIMARY KEY (id )
)

CREATE TABLE B
(
  id integer NOT NULL,
  CONSTRAINT pk_file PRIMARY KEY (id ),
  CONSTRAINT fk_a_pk FOREIGN KEY (id)
      REFERENCES A (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

Upvotes: 2

Views: 1087

Answers (1)

Danilo Piazzalunga
Danilo Piazzalunga

Reputation: 7844

tl;dr: optional = false on A tells Hibernate to insert into B first.

Making the association unidirectional

The JPA JavaDoc provides an example of a one-to-one association that assumes both the source and target share the same primary key values, but it uses an unidirectional association:

  1. Remove the association from A:

    @Entity
    public class A {
        @Id
        @SequenceGenerator(...)
        @GeneratedValue(...)
        private long aId;
    }
    
  2. If you are using an Hibernate version which supports JPA 2.0 (recent versions do), specify the @MapsId attribute on B, removing the foreign generator:

    @Entity
    public class B {
        @Id
        private long aId;
    
        @OneToOne
        @MapsId
        private A a;
    }
    

Keeping the association bidirectional

You cannot put optional = false on the @OneToOne annotation in A. Since the foreign key (B.id) referencing A is mandatory, you should insert into A first anyway:

INSERT INTO A (id) VALUES (1);
-- there isn't a corresponding record in B,
-- until we perform a second insert into B:
INSERT INTO B (id) VALUES (1);

Knowing this, let's make the association bidirectional again:

@Entity
public class A {
    @Id
    @SequenceGenerator(...)
    @GeneratedValue(...)
    private long aId;

    // should work without 'optional = false'
    @OneToOne(mappedBy = "a",  cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private B b;
}

See also Primary Keys through OneToOne and ManyToOne Relationships

Upvotes: 1

Related Questions