5

I have the follwoing entity:

@Entity
public class A {

  // id, etc..

  @OneToOne
  private B b;
}

The table a already exists and when i add the new field b to it, hibernate executes the following:

alter table a add column b_id int8
alter table a add constraint FKg76mxqt8whi8t8p4i7el95910 foreign key (b_id) references b

As you see, the foreign key column b_id is not unique. Why is that the case? Doesn´t the One-to-One relation imply that the foreign key has to be unique? That´s also what i found in the JPA specification for unidirectional One-to-One relatoins:

[...] The foreign key column has the same type as the primary key of table B and there is a unique key constraint on it.

To make it work i have to explicitally add the @JoinColumn(unique=true) annotation to the field. Why do i have to do that explicitally?

M.Dietz
  • 900
  • 10
  • 29
  • I suppose that it is not setting the foreign key as unique by default is because you are allowed to have multiple rows with that foreign key in the table, with is normally obvious, and the docs does not say that foreign key constraint is unique by default:https://javaee.github.io/javaee-spec/javadocs/javax/persistence/OneToOne.html – Markiian Benovskyi Feb 25 '20 at 14:36
  • But i should not be able to have the same foreign key in multiple rows in a one-to-one relation. One-to-One implies that the foreign key is only part of exactly one row. If its contained in multiple rows, its not a one-to-one relation. – M.Dietz Feb 25 '20 at 14:39
  • Which hibernate version are you using? – Markiian Benovskyi Feb 25 '20 at 14:41
  • I don't know if the B model has a `mappedBy` property. But in order to have the unique constraint you need to have the non-owner side of relationship to have a `mappedBy` property set. Without it you're modeling two different relations instead of one bi-directional relation; because of that nothing is stopping the current model from having two Childs pointing to the same Parent. Please include the code for model B relationship as well – Markiian Benovskyi Feb 25 '20 at 14:44
  • I´m having an unidirectional mapping. So `B` does not have a field `A`. – M.Dietz Feb 25 '20 at 14:53
  • As I wrote before, you're not modelling a bi-directional relation, and nothing is stopping the current model from having two Childs pointing to the same Parent. You need to create a single bi-directional relation with a Parent, and on Child set the `mappedBy` parameter, in order to have a unique relation. Without this your relation is one direction only, and this can have multiple rows – Markiian Benovskyi Feb 25 '20 at 14:56

2 Answers2

3

Bi-directional @OneToOne

In order to create a unique constraint you have to create a complete bi-directional OneToOne relation.

This means you have to add @OneToOne annotation on the Parent (Owning) entity, and add @OneToOne(mappedBy="...") on the Child entity.

This will create a unique constraint on your id column.

Otherwise, you're modeling two different relations instead of one bi-directional relation; because of that nothing is stopping the current model from having two Childs pointing to the same Parent.

The official JavaDoc for @OneToOne annotation has more information on additional parameters and advice on bi-directional relation.

UPD: Link to hibernate specification on how it is handling of @OneToOne relation:

  1. When using a bidirectional @OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side.
  2. A unidirectional association follows the relational database foreign key semantics, the client-side owning the relationship.

In your case

This means that on your B entity model you should add a field with your A entity and annotate it with @OneToOne(mappedBy="b") to make your relation bi-direactional and complete, restricting access to single Parent and creating a unique constraint.

Markiian Benovskyi
  • 2,137
  • 22
  • 29
  • Why "nothing is stopping the current model from having two Childs pointing to the same Parent"? It's clearly one-to-one, that contradicts your statement. – Andronicus Feb 25 '20 at 16:32
  • @Andronicus You can read more here: https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#associations-one-to-one. As told on the specification: `When using a bidirectional @OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side.` so when you have only unidirectional relation as you have here it says: `A unidirectional association follows the relational database foreign key semantics, the client-side owning the relationship.` - and this can be the reason your constraint is not unique – Markiian Benovskyi Feb 25 '20 at 16:53
  • @M.Dietz if you find that this answers your question, please mark it as accepted answer, thank you – Markiian Benovskyi Feb 25 '20 at 17:03
0

I came across this issue recently. My assumptions were @OneToOne must add a unique constraint on the foreign key. But hibernate doesn't do that.

See this section in the hibernate docs. Neither unidirectional nor bidirectional mappings add unique constraint (evident from the queries in the docs). That means hibernate won't throw an exception when adding rows with same foreign keys.

Although note that in case of bidirectional mapping, hibernate does a uniqueness check when fetching the parent entity (but not child). If there are multiple child (owning) entities refering to the same parent entity which is being fetched, hibernate throws a org.hibernate.exception.ConstraintViolationException.

The solution is to add @JoinColumn(unique = true, ...) on your child. This will also throws an error when inserting a row with a non-unique foreign key in the table.

Omkar Manjrekar
  • 103
  • 1
  • 11
  • That's incorrect, hibernate enforces unique constraint on a bidirectional one-to-one relation and throws an exception if the annotation is written correctly with parent, child and `mappedBy` properly set – Markiian Benovskyi Sep 06 '21 at 14:05