How to change the @OneToOne shared primary key column name with JPA and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, we are going to see how you can change the @OneToOne shared primary key column name when using JPA and Hibernate. This has been a recurrent theme when answering questions about Hibernate or during my High-Performance Java Persistence training.

As previously explained, the one-to-one database table relationship requires the Primary Key to be shared among the parent and the child tables.

Unfortunately, just adding the JPA @OneToOne annotation in the child entity does not render a true one-to-one table relationship since a separate Foreign Key column will be used. Only when adding the @MapsId annotation will the JPA one-to-one association map to a real one-to-one table relationship.

Domain Model

Let’s assume we are using the following Post and PostDetails entities:

OneToOne JPA relationship

The Post entity is the parent while the PostDetails is the child entity and its associated Primary Key is also a Foreign Key to the parent table Primary Key.

The Post entity is rather straightforward to map since it does not contain any association:

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

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    //Getters and setters omitted for brevity
}

The PostDetails can be mapped as follows:

@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
}

Notice that we are using FetchType.LAZY explicitly since, by default, JPA uses DetchType.EAGER for @OneToOne and @ManyToOne associations, and that’s very bad for performance.

When generating the schema with the hbm2ddl tool or if we create the schema manually and managing it with Flyway, Hibernate expects the following database table structure:

One-to-one table relationship with default Primary Key column name

Notice that the Primary Key column name is called post_id in the post_details table, and this is not very nice since the Primary Key column name is called id in the post table.

@JoinColumn to the rescue

To fix this issue, we just have to add a @JoinColumn annotation to the @OneToOne association in the PostDetails entity:

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

This way, Hibernate will either generate or expect the following database tables:

One-to-one table relationship with custom Primary Key column name

Awesome, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

It’s a little bit unfortunate that, by default, the @MapsId annotation does not use the entity identifier name to match the underlying table Primary Key column. However, using the @JoinColumn annotation a straightforward way to fix this issue.

Transactions and Concurrency Control eBook

12 Comments on “How to change the @OneToOne shared primary key column name with JPA and Hibernate

  1. I see, I am modeling a GET /parents/{id} endpoint and would like to return the parent details along with the (optional) child. If I mapped the @OneToOne association on both sides then saving the Parent and the Child in one single ‘repository.save(…)’ method would work (assuming the save happens using the Parent repository and setting the relationship on both sides) But if later I were to query the parent and the child like so:

    SELECT Parent p LEFT JOIN FETCH p.child WHERE p.id = :id

    HHH000327: Error performing load command
    SQL Error 42703
    column child0_.value does not exist

    Please note that Child is @Inheritance(strategy=SINGLE_TABLE)

    Using the Child repository to fetch the Parent (as you suggest?) would require me to use a RIGHT JOIN FETCH since it isn’t guaranteed the Parent I am looking for has one child.

    Also, If I didn’t map the relationship on both sides (as you suggest?), then I would have to save the Child with his own repository (not the parent’s), so to achieve what I want (POST /parents) I would have to save() both Parent and Child with their own repositories ?

    Thanks a lot for your support and great work with the Hypersistence Optimizer

    • Mapping the child association on the parent side is very problematic. Unless you are using bytecode enhancement, you will always load it.

      However, you could do it as in this GitHub test case:

      https://github.com/vladmihalcea/high-performance-java-persistence/blob/master/core/src/test/java/com/vladmihalcea/book/hpjp/hibernate/association/BidirectionalOneToOneNPlusOneTest.java#L130

      You can map two entities on the parent table:

      • A Post entity that has the child association with you JOIN FETCH for your use case
      • A PostSummary entity that maps the same table but without that child association

      When you don’t need the child, just use the PostSummary instead of the Post entity.

      More, just because Spring Data JPA offers the JpaRepository, it doesn’t mean you should only use its methods. In fact, the save method is an anti-pattern, as I described in this article:

      https://vladmihalcea.com/best-spring-data-jparepository/

      Whenever you need more control, you should use custom repositories, as explained in this article:

      https://vladmihalcea.com/custom-spring-data-repository/

      The custom repository gives you the flexibility of calling persist directly on the EntityManager for both the parent and the child. This way, you can get along with the fact that you didn;t map the child entity on the Post side.

      • I just downloaded dependency com.vladmihalcea:hibernate-types-55 and made all my repository classes implement HibernateRepository. I see that I will have to change my repository.save(…) methods to repository.persist(…) or repository.merge(…) and that’s fine.
        However, there are no built-in methods such as repository.findById() etc, and that means I will have to create my custom repository and its implementation so that I can use entityManager to create those methods. Does that mean I will have to manually map the results to an Entity or DTO/Projection using a resultTransformer everytime?

        Thanks again

      • If you don’t see the findById method is because you didn’t extend both the JpaRepository and the HibernateRepository, as shown in that article:

        @Repository
        public interface PostRepository
            extends JpaRepository<Post, Long>, HibernateRepository<Post> {
         
        }
        

        The HibernateRepository is not a substitute for the JpaRepository. It’s an enhancement that adds new methods and deprecates some of the existing ones.

      • Thanks, that worked.

        When using HibernateRepository persist(…) or merge(…) methods I am getting back.

        No EntityManager with actual transaction available for current thread – cannot reliably process ‘persist’ call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread – cannot reliably process ‘persist’ call

        My Service and Repository classes are annotated with @Transactional and JpaConfig has @EnableTransactionManagement. What could the problem be?

      • Check out if you set the basePackages attribute to com.vladmihalcea.spring.repository as explained in the docs:

        @Configuration
        @EnableJpaRepositories(
            basePackages = {
                "com.vladmihalcea.spring.repository",
                ...
            }
        )
        public class JpaConfiguration {
            ...
        }
        
  2. My goal is to have a foreign key called ‘post_id’ in the ‘post_details’ table. It sounds nice to me. Am I missing something?

    • If you want to have a separate FK besides the PK column, then you need to remove MapsId, but you’ll get a one-to-many relationship.

      • Thank you sir, that was very clear. If I have a @OneToOne mapping, PK will be shared among Parent and Child entity. The last issue I have is, why does the @OneToOne annotation needs to be put only in the Child entity class? My goal would be to retrieve the child using the Parent object.

      • A one-to-one table relationship is built by having the PK column in the child table have a FK constraint. That’s why the association is on the child entity because that’s where the FK constraint is.

        If you want to fetch both entities at once you can do it like this:

        select pd
        from PostDetails pd
        join fetch pd.post
        where pd.id = :postId
        

        The PostDetails will contain the parent Post as well.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.