Tag: hibernate

  • Understanding One-to-One Relationships: Database vs Java (Hibernate)

    Understanding One-to-One Relationships: Database vs Java (Hibernate)

    When modeling One-to-One relationships, you can approach it in several ways depending on the ownership, lifecycle, and data modeling constraints. While Java and Hibernate offer flexibility in how this relationship is implemented at the code level, the root of it lies in database design. So let’s start there.


    One-to-One Relationship Strategies in Database Engineering

    In relational databases, a One-to-One relationship means each row in Table A corresponds to exactly one row in Table B, and vice versa. But there are multiple ways to implement this, depending on the direction and ownership of the relationship.

    1. Foreign Key in Parent Table (Shared Primary Key strategy – reversed)

    • How it works: The parent table (e.g., Person) holds a foreign key to the child table (Passport.id).
    • Pros:
      • Easy to query from parent.
      • Clear ownership.
    • Cons:
      • Implies that the parent cannot exist without the child — or needs NULL values.
      • Foreign key can be NULL if optional.
    CREATE TABLE person (
        id BIGINT PRIMARY KEY,
        name VARCHAR(100),
        passport_id BIGINT UNIQUE,
        FOREIGN KEY (passport_id) REFERENCES passport(id)
    );
    

    2. Child Table Uses Parent’s Primary Key (Shared Primary Key strategy)

    • How it works: The child table (e.g., Passport) uses the same ID as the parent (Person) — usually both primary key and foreign key.
    • Pros:
      • Enforces strict 1:1 relationship.
      • Cleaner data integrity — Passport cannot exist without Person.
    • Cons:
      • Less flexible if the child becomes optional.
      • Harder to manage outside ORMs.
    CREATE TABLE person (
        id BIGINT PRIMARY KEY,
        name VARCHAR(100)
    );
    
    CREATE TABLE passport (
        id BIGINT PRIMARY KEY,
        number VARCHAR(50),
        FOREIGN KEY (id) REFERENCES person(id)
    );
    

    3. Join Table Strategy (Less common for One-to-One)

    • How it works: A third table (e.g., person_passport) holds person_id and passport_id with a unique constraint.
    • Pros:
      • Very flexible and decoupled.
      • If the relation has a tendency to become 1xN or Nx1 or NxN then it’s a good choice because it will make the transition easier.
    • Cons:
      • Overkill unless mapping polymorphic or cross-entity relations.
      • High complexity for something that can be simple.

    Entity Mapping in Hibernate & Java

    Now let’s translate this to Java and Hibernate.

    Shared Primary Key Strategy (Child uses parent’s ID)

    @Entity
    public class Person {
        @Id
        private Long id;
    
        @OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
        private Passport passport;
    }
    
    @Entity
    public class Passport {
        @Id
        private Long id;
    
        @OneToOne
        @MapsId
        @JoinColumn(name = "id")
        private Person person;
    
        private String number;
    }
    
    • @MapsId indicates that Passport shares the same primary key as Person.
    • Very tight coupling — used when lifecycle is tightly bound.

    Foreign Key in Parent Table

    @Entity
    public class Person {
        @Id
        private Long id;
    
        @OneToOne
        @JoinColumn(name = "passport_id")
        private Passport passport;
    }
    
    @Entity
    public class Passport {
        @Id
        private Long id;
        private String number;
    }
    
    • More flexible: Person can exist without Passport.
    • You control fetch/lazy, cascade, optionality more easily.
    • More flexible: Person can exist without Passport.
    • You control fetch/lazy, cascade, optionality more easily.

    What about property with JoinTables?

    Sometimes, your model may evolve and you’d rather keep the base entity clean and extend when needed. For example, instead of putting a Passport inside Person, you could have:

    @Entity
    public class Passport  {
    
        @OneToOne
        @JoinTable(
            name = "person_passport",
            joinColumns = @JoinColumn(name = "person_id"),
            inverseJoinColumns = @JoinColumn(name = "passport_id")
        )
        private Passport passport;
    }
    

    This approach uses:

    • Join Table in JPA.
    • @JoinTable allows modeling more flexible or optional relationships.
    • Useful when only some persons have passport.
    • A smart choice when there’s a possibility that the relationship’s cardinality may change.

    It’s especially powerful when you want to modularize behavior and avoid cluttering the base Person class but makes the entity mapping very complex.


    What About Inheritance with Joined Strategy?

    Sometimes you just want a easier way to navigate the entity properties and treat the two tables as a single entity. For example, instead of putting a Passport inside Person, you could extend it:

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public class Person {
        @Column
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    }
    
    @Entity
    public class Passport extends Person {
    
       @Column
       private String number;
    }
    

    Summary

    • InheritanceType.JOINED allows you to split the data into separate tables for each class in the inheritance hierarchy.
    • The child class has its own table and its joined with the parent class, so the child entity has all properties from the parent as its own.
    • Hibernate will automatically handle the joins when querying entities from the inheritance hierarchy.

    This strategy is useful when you want to follow database normalization principles but be mindful of the performance overhead due to the necessary joins when querying data.


    Final Thoughts: Choosing the Right Strategy

    Your choice of strategy in the database layer directly impacts your entity mapping in Java:

    • Use Shared Primary Keys when the two entities are tightly bound and always created/removed together.
    • Use a Foreign Key in Parent when the child is optional or loosely coupled.
    • Use Join Tables or Inheritance when dealing with complex relationships, partial behaviors, or domain subtypes.

    Choosing the right one-to-one mapping isn’t just about the ORM — it’s about data consistency, flexibility, and how your domain actually behaves.