Search code examples
spring-boothibernatespring-data-jpanhibernate-mapping

One To One Mapping Spring Data JPA


I've a question about One to One unidirectional Mapping in Spring Boot. I've a Customer class with a One to One unidirectional mapping to an Address class.

But when I try to associate a new customer with an existing Address, the database is updated. So two Customers are now associated with the one Address.

As I understand it only one Customer should be associated with one unique Address. Do I understand the concept correctly, or am I doing something wrong in Spring Boot/ Spring Data JPA/ Hibernate?

Customer

@Entity
public class Customer {
    @Id
    private Long cId;
    private String cName;
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="aid")
    private Address cAddr;
    :
}

Address

@Entity
public class Address {
    @Id
    private Long aid;
    private String town;
    private String county;
    :
}

data.sql

insert into address values (100, "New York", "NY");
insert into customer values (1, "John Smith", 100);

Application.java

@Override
public void run(String... args) throws Exception {
    Customer c1 = new Customer((long)5, "Mr. Men");
    Optional<Address> a100 = ar.findById((long)100);
    c1.setcAddr(a100.get());
    cr.save(c1);
}

Database Both Customers have aid 100


Solution

  • There are 2 options on how to make @OneToOne relation: unidirectional and bidirectional: see hibernate doc.

    When you scroll down a little bit you will find the following:

    When using a bidirectional @OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException

    It means that you'll have the exception only on fetching and when you have a bidirectional association. Because Hibernate will make an additional query to find the dependent entities, will find 2 of them, which doesn't fit @OneToOne relation and will have to throw an exception.

    One way to "fix" uniqueness for your entities, is to make cAddr unique:

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="aid", unique=true)
    private Address cAddr;
    

    If you create your db tables, by setting hbm2ddl property this will add a unique constraint to the aid column.

    I really recommend to read the following:

    1. @OneToOne javadoc itself provides examples of how to do everything correctly (for you Examples 1 and 2 are the most useful)
    2. Check Vlad's blog about @OneToOne. It must be the best you can find. At least jump to the chapter "The most efficient mapping" and implement it bidirectional and sharing the PK, using @MapsId.

    Also maybe you will come up to the idea to use @ManyToOne option (at least i can imagine that customer can have multiple addresses)