The best way to map a @OneToOne relationship with JPA and Hibernate

Introduction

There are many ways you can map a one-to-one relationship with Hibernate. In this post, I’m going to demonstrate which mapping is the most efficient one from a database perspective.

Domain Model

For the following examples, I’m going to use the following Post and PostDetails classes:

OneToOne

The Post entity is the parent, while the PostDetails is the child association because the Foreign Key is located in the post_details database table.

Typical mapping

Most often, this relationship is mapped as follows:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

More, even the Post entity can have a PostDetails mapping as well:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToOne(mappedBy = "post", cascade = CascadeType.ALL, 
              fetch = FetchType.LAZY, optional = false)
    private PostDetails details;

    //Getters and setters omitted for brevity

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

However, this mapping is not the most efficient, as further demonstrated.

The post_details table contains a Primary Key (PK) column (e.g. id) and a Foreign Key (FK) column (e.g. post_id).

one-to-one

However, there can be only one post_details row associated with a post, so it makes more sense to have the post_details PK mirroring the post PK.

one-to-one-shared-pk

This way, the post_details PK is also a FK, and the two tables are sharing their PKs as well.

PK and FK columns are most often indexed, so sharing the PK can reduce the index footprint by half, which is desirable since you want to store all your indexes into memory to speed up index scanning.

While the unidirectional @OneToOne association can be fetched lazily, the parent-side of a bidirectional @OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad.

This can be easily demonstrated by simply fetching the Post entity:

Post post = entityManager.find(Post.class, 1L);

Hibernate fetches the child entity as well, so, instead of only one query, Hibernate requires two select statements:

SELECT p.id AS id1_0_0_, p.title AS title2_0_0_
FROM   post p
WHERE  p.id = 1

SELECT pd.post_id AS post_id3_1_0_, pd.created_by AS created_1_1_0_,
       pd.created_on AS created_2_1_0_
FROM   post_details pd
WHERE  pd.post_id = 1

Even if the FK is NOT NULL and the parent-side is aware about its non-nullability through the optional attribute (e.g. @OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)), Hibernate still generates a secondary select statement.

For every managed entity, the Persistence Context requires both the entity type and the identifier,
so the child identifier must be known when loading the parent entity, and the only way to find the associated post_details primary key is to execute a secondary query.

Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with @LazyToOne(LazyToOneOption.NO_PROXY) and the child side is not using @MapsId.

The most efficient mapping

The best way to map a @OneToOne relationship is to use @MapsId. This way, you don’t even need a bidirectional association since you can always fetch the PostDetails entity by using the Post entity identifier.

The mapping looks like this:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

This way, the id column serves as both Primary Key and FK. You’ll notice that the @Id column no longer uses a @GeneratedValue annotation since the identifier is populated with the identifier of the post association.

The PostDetails entity can be persisted as follows:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);
    PostDetails details = new PostDetails("John Doe");
    details.setPost(post);
    entityManager.persist(details);
});

And we can even fetch the PostDetails using the Post entity identifier, so there is no need for a bidirectional association:

PostDetails details = entityManager.find(
    PostDetails.class, 
    post.getId()
);

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Knowing how to map entity relationships efficiently can make a lot of difference when it comes to application performance. For @OneToOne associations, you should always share the Primary Key with the parent table, and you should avoid the bidirectional association if you don’t plan to use bytecode enhancement.

Code available on GitHub.

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

56 thoughts on “The best way to map a @OneToOne relationship with JPA and Hibernate

    1. For bidirectional @OneToOne associations, you can avoid the N+1 query issue using JOIN FETCH. Nevertheless, the @MapsId approach is just better. It allows you to reduce the index size, while simplifying the Domain Model to match the underlying relational model as well.

    2. My workaround for the n+1 select issue on @OneToOne relations (optional ones, in my case) was to actually annotate it as a @OneToMany relation inside the bean (with subselect fetching) and then use the getter / setter to present it externally as a regular field rather than a set.

      This is a little gross but the grossness is contained, you can comment it, and external code doesn’t need to be aware.

  1. Another question, is it possible to persist PostDetails without setting Post reference? Something like postDetails.setId(99)?

      1. Cool! I’ve never used @MapId so I never got this kind of issue. Thanks for the explanation!

  2. One variation I’ve been troubled by and couldn’t find a clear solution to (probably because I am a Hibernate / JPA noob) was when the parent entity / table had a natural key (i.e. @Embedded). That’s just to give you an eventual idea for a future post 😉

  3. Thank you for this article.

    One question tough:
    What does using “@LazyToOne(LazyToOneOption.NO_PROXY)” exactly do?
    So if I fetch Post only, do I get a Post where the Post.postDetails ist just a proxy?

    What SQLs are executed?

    1. That’s just a legacy mapping that will probably be removed in some future release. It’s used to instruct Hibernate to avoid generating a Proxy since the bytecode enhancement takes care of the laziness part.

      As for the executed queries, if you enable bytecode enhancement, you’ll no longer get the mapped-by side fetched eagerly. Otherwise, you always get the parent-side fetched.

    1. It is valid, but, if you read it carefully, you’ll see that I advise you using the child side only.

      That Jira issue affects the parent side, which you don’t really need in most use cases anyway.

      1. Thanks for the clarification.

        My bad, I didn’t understand it correctly. I just downloaded the source & I get it now.

        Dint realise earlier that the advice involves removing child reference from parent & always get child explicitly using entity manager.

        AFAIK, The only side effect is writing explicit join in JPA if any complex requires the use of fields from child aswell. But I think that can easily be managed.

        I really enjoy reading your articles. Thanks for the great stuff you write.

  4. Not sure what happened to the formatting within my last comment. Positing this comment again with a slightly different format

    I just realised that there are few more issues with this approach.

    (1) If the relationship is optional, when the parent is fetched, we will not whether the child is present for this parent unless the query is performed against the child.

    (2) The cascades should be done manually. (can live with this pain for performance)

    w.r.t (1), is there a way to delay this child fetch to a point in future as in my usecase, I need to provide search functionality for parents & I have a lot of children who are optional.

    Thanks again

    1. (1) applies to parent-side @OneToOne unless you use Bytecode Enhancement.
      (2) That’s not really a big issue. Instead of one entity state transition, you need to operations (persist the parent, and persist the child as well).

  5. Hi, you recommend to use bytecode enhancement for Optional OneToOne bidirectional relations.
    We are in the process of going from Hibernate4 to Hibernate5 and, to support this kind of relations, we used to use bytecode enhancement (ANT InstrumentTask) of the non owner classes (with LazyToOneOption.NO_PROXY.) but we have found some regression problems. (n+1 selects)

    @Entity
    public class Foo {
    @Id
    private Integer id;

        private String dummyFoo;
    
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "foo")
    @LazyToOne(value = LazyToOneOption.NO_PROXY)
    private Bar bar;
    

    }

    @Entity
    public class Bar {
    @Id
    private Integer id;

        private String dummyBar;
    
    @OneToOne(fetch = FetchType.LAZY)
    private Foo foo;
    

    }

    import.sql:

    Hibernate: insert into Foo (dummyFoo, id) values (‘DUMMYFOO’, 1)
    Hibernate: insert into Bar (dummyBar, foo_id, id) values (‘DUMMYBAR’, 1, 1)

    With 4.3.11.Final:

    em.find(Foo.class, 1): OK

    Hibernate:
    select
    foo0_.id as id1_1_0_,
    foo0_.dummyFoo as dummyFoo2_1_0_
    from
    Foo foo0_
    where
    foo0_.id=?

    em.find(Bar.class,1): OK

    Hibernate:
    select
    bar0_.id as id1_0_0_,
    bar0_.dummyBar as dummyBar2_0_0_,
    bar0_.foo_id as foo_id3_0_0_
    from
    Bar bar0_
    where
    bar0_.id=?

    With 5.2.10.Final:

    em.find(Foo.class, 1): OK

    Hibernate:
    select
    foo0_.id as id1_1_0_,
    foo0_.dummyFoo as dummyFoo2_1_0_
    from
    Foo foo0_
    where
    foo0_.id=?

    em.find(Bar.class,1): NO OK

    Hibernate:
    select
    bar0_.id as id1_0_0_,
    bar0_.dummyBar as dummyBar2_0_0_,
    bar0_.foo_id as foo_id3_0_0_
    from
    Bar bar0_
    where
    bar0_.id=?
    Hibernate:
    select
    foo0_.id as id1_1_0_,
    foo0_.dummyFoo as dummyFoo2_1_0_
    from
    Foo foo0_
    where
    foo0_.id=?

    Why this second select is executed? It is marked as lazy and the FK is known to have a value by Bar!!

    As the InstrumentTask is deprecated we tested with the EnhancementTask with the same wrong results.

    And, just in case, with the org.hibernate.orm.tooling.hibernate-enhance-maven-plugin, but then:

    both classes are enhanced
    (I don´t know how to apply a filter to the plugin for a selective enhancement and we have > 850 Entities)
    the results are the same with N+1 selects when you read from the child side

    Do you have any idea of what is wrong or if Is this a known problem?

    Regards

    1. It might be a bug. Anyway, as I explained in this article, there is no need for the parent side @OneToOne which is suboptimal anayway. Just use @MapsId and locate the parent by the child identifier.

  6. Hi! What would be the equivalent of using @Mapsid in XML mapping files? Im trying to solve eager loading for one-to-one relationships.
    Thanks!

    1. @MapsId does not prevent EAGER loading of the parent-side if that’s your issue. The only way to fix it is to remove the parent-side association since you don’t really need it. You can always fetch the Child knowing the parent identifier anyway.

      1. I’m trying to solve the load of the child when I load the load the parent. Is that what you mean?
        Let’s say I have Client and Address, one-to-one relationship. I want to avoid that every time I load Client to not load Address.
        Thank you very much for your quick response!

      2. Well, this article already offers a solution. If you want a more detailed explanation to this as well as how you can use ByteCode enhancement to address this limitation, then you should definitely read my book.

      1. As I explained in this article, you don’t really need parent-side @OneToOne, hence you don’t need the optional attribute as well. So, if you decide to use it and it doe snot works as specified by the JPA spec, feel free to supply a replicating test case and open a Jira issue.

  7. Nice article.
    I just wonder why in the last example, there is no an annotation “@JoinColumn(name=”id”)” with the Post property in the PostDetails class?

  8. Thanks for the post. Regarding

    (2) The cascades should be done manually. (can live with this pain for performance)

    What did you mean by

    (2) That’s not really a big issue, and you can always tweak your queries to overcome this limitation.

    ? – write native sql?

      1. Thanks for your reply. I wanted the child to be available from the parent, so used

        @OneToOne(mappedBy = "postDetails")
        @JoinColumn(name = "id")
        @MapsId
        private Post post;
        

        And

        @OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
        @PrimaryKeyJoinColumn
        private PostDetails postDetails;

        So when I use (Spring Data) postRepository.delete(post) it cascades to delete postDetails as well. I think this works, but haven’t tested it extensively.

      2. That’s wrong because you combine mappedBy with non-mappedBy semantics. And, you don’t need the child to be available in the parent because you can always fetch the child using the parent identifier. You can use DTOs to aggregate bothh the parent and the child if you really need them further up the stack.

  9. Thanks for the reply.

    I guess I was just trying to use cascade delete instead of explicit method calls, like calling postDetailsRepository.delete(post.getId()) and so on.

    It’s not the end of the world, and I guess it makes the Post entity cleaner, if it doesn’t have to know about everything it is related to.

      1. OK, good idea. I have your book and will give it a read!

        I used the soft-delete mechanism detailed elsewhere in your blog, so not sure if that will generate the database event to cascade. I suppose it could work in a stored procedure.

      2. For Hibernate, the soft delete works just like any other delete entity state transition, so the cascade can propagate from parent to child entities. It’s just that the SQL statement is substituted.

  10. Hi!

    I got your book 🙂 It’s great!

    However, I guess it’s pretty simple but still. Why should we manage relation on the child-side even for @OneToOne? I see it’s reasonable for @ManyToOne. But, what if we will put post_details_id into post table? What are the disadvantages?

    Thanks a lot!

    1. Thanks, you are going to love it.

      As for your question, it’s not a good practice to place the FK in the parent table to map a one-to-many association. Otherwise, what if you have a post_details row that does not belong to any post. Technically, this would be possible in your DB schema, although it makes no sense in reality.

      The @OneToOne makes sense on the child side only, that’s how FK work on the DB as well.

      1. Got it!

        Thanks again for the book. You did a great work. It’s pretty big and heavy 🙂 but it’s very interesting. For me, it’s kind of a fresh view of the JPA, and Hibernate.

  11. Hi

    can you tell me if I have Parent object and two relations to Childs (ChildA, ChildB) all with OneToOne relations, how will I persist them?
    Do I need to persist them in two steps or there are some other ‘one step’ solution?

      1. I’m trying that approach but it means that I should have Repository class for ChildA and for ChildB (which means in large hierarchy I’ll have many of them).
        public interface ChildARepository extends JpaRepository
        public interface ChildBRepository extends JpaRepository

        Am I on wrong track or I really should do it that way?

      2. You don’t need any extra Repository. Just declare a custom saveParent method in the ParentRepository that takes the Parent and all Child and save them using the injected EntityManager.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s