При связи один-к-одному каждая запись в одной таблице напрямую связана с отдельной записью в другой таблице. Для того, чтобы связать сущности отношением один-к-одному в Hibernate используется аннотация @OneToOne. В целом, может быть 3 варианта ее использования:
- связанные сущности используют одно и тоже значение первичного ключа;
- внешний ключ определяется полем одной из сущностей (это поле в БД должно быть уникальным для имитации отношения один-к-одному);
- используется таблица для хранения ссылки между двумя сущностями (ограничение уникальности должно быть установлено на каждом из полей для того, чтобы соответствовать кратности один-к-одному).
Связь один-к-одному с использованием общих первичных ключей
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * Пользователь системы */ @Entity @Table(name = "users") public class User { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; /** * Поля общие для всех пользователей */ }
import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; /** * Партнер системы */ @Entity @Table(name = "partners") public class Partner { @OneToOne(cascade = CascadeType.ALL) @PrimaryKeyJoinColumn private User user; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; /** * Поля данных партнеров системы */ }
Аннотация @PrimaryKeyJoinColumn
указывает на то, что первичный ключ сущности Partner
используется в качестве внешнего ключа для связи с сущностью User
.
Связь один-к-одному с использованием явного внешнего ключа
import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; /** * Пользователь системы */ @Entity @Table(name = "users") public class User { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name="passport_id") private Passport passport; }
Связь в БД между таблицами users
и passports
осуществляется посредством поля passport_id
в таблице users
. Связанное поле в User
объявлено с помощью аннотации @JoinColumn
, ее параметр обозначает поле в БД, которое будет использоваться для создания связи.
Связь один-к-одному может быть двунаправленной. В двунаправленных отношениях одна из сторон (и только одна) должна быть владельцем и нести ответственность за обновление связанных полей. В нашем случае владельцем выступает сущность User
. Для того, чтобы объявить сторону, которая не несет ответственности за отношения, используется атрибут mappedBy
. Он ссылается на имя свойства связи на стороне владельца (passport
).
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; /** * Паспорт пользователя */ @Entity @Table(name = "passports") public class Passport { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(mappedBy = "passport") private User user; }
Двунаправленное отношение не создает дополнительного внешнего ключа. Фактически, двунаправленная связь никак не влияет на то, как таблицы связаны друг с другом в БД. Просто она позволяет работать с сущностями в обоих направлениях, все также используя единственный внешний ключ. В случае, если на стороне владельца нет связанного поля @JoinColumn
, то выполнятся следующие умолчания: в таблице владельца будет создано поле для связи, имя которого собирается из имени связи на стороне владельца(«passport»), нижнего подчеркивания(«_») и имени уникального ключа на зависящей стороне(«id»).
Преимуществом однонаправленной связи является то, что ею легче управлять, потому что Вы должны поддерживать только одну сторону. Преимущество же двунаправленной связи заключается в возможности доступа между связанными сущностями в обоих направлениях. Но обычно это приводит к формированию лишних запросов к БД, поэтому используйте двунаправленные связи осторожно и контролируйте формируемые ими sql-запросы.
После объявления связи в сущности, использование ее внешнего ключа в чистом виде приведет к возникновению исключения org.hibernate.MappingException: Repeated column in mapping for entity: hibernate.objects.User column: passport_id (should be mapped with insert=»false» update=»false»). Для того, чтобы получить возможность доступа к значению внешнего ключа без загрузки связанного объекта, его можно объявить и использовать как не обновляемое поле:
@Column(insertable = false, updatable = false) private Long passport_id;
Связь один-к-одному с использованием таблицы отношений
import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToOne; import javax.persistence.Table; /** * Пользователь системы */ @Entity @Table(name = "users") public class User { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(cascade = CascadeType.ALL) @JoinTable(name = "user_passport", joinColumns = @JoinColumn(name="user_id"), inverseJoinColumns = @JoinColumn(name="passport_id") ) private Passport passport; }
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; /** * Паспорт пользователя */ @Entity @Table(name = "passports") public class Passport { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(mappedBy = "passport") private User user; }
В БД таблица users
связана с passports
с помощью таблицы отношений user_passport
. Эта таблица содержит внешний ключ user_id
, указывающий на таблицу users
и внешний ключ passport_id
, указывающий на passports
. @JoinTable
позволяет избежать создания отдельной сущности для таблицы отношений user_passport
и непосредственно связать сущности User
и Password
между собой. Связь может быть двунаправленной точно также, как в случае с использованием явного внешнего ключа.
Спасибо, жду новых статей.
Просто великолепно, спасибо огромное!