Связи many-to-one и оne-to-many
обратные зеркальные противоположности, если вы
знаете как описать одну из них, значит опишете и другую. Связь один ко многим встречается очень часто,
допустим, человек представлен сущностью Person и на протяжении жизни у него происходят важные события во
времени Event,
такие как рождение, дата свадьбы,
поступление в институт, первая любовь и т. д. Давайте начнем с примера однонаправленной связи one-to-many
через foreign-key, связь представлена двумя сущностями Person(one) -> Event(many) . Описывается эта связь через аннотацию @OneToMany() в
сущности Person, так же нам нужно установить связь foreign-key между двумя сущностями поможет нам в этом
аннотация @JoinColumn(), name
–
описывает поле связи в таблице EventsM, referencedColumnName – поле
связи в таблице PersonsM:
@Entity @Table(name = "PersonsM")
public class Person implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@OneToMany()
@JoinColumn(name =
"idF",referencedColumnName = "idP1")
private Set<Event>
event;
…
@Entity @Table(name="EventsM")
public class Event {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE")
private long idE1;
…
При этом генерируется следующая схема:
create table EventsM (idE bigint identity not null, birthDate datetime,
idF bigint, primary key (idE));
create table PersonsM (idP1 bigint identity not null, MyName
varchar(255), primary key (idP1));
alter table EventsM add constraint FK112CDA344D0C254D foreign key (idF)
references PersonsM;
Следующий пример, давайте реализуем связь
через таблицу (join table), для этого будем использовать аннотацию @JoinTable(), где атрибут name задает имя таблицы связи, атрибут joinColumns связывает поле таблицы Persons1M
с таблицей Persons1MEventsM, атрибут inverseJoinColumns
связывает поле таблицы EventsM с таблицей Persons1MEventsM, посмотрим на
листинг, как это все описывается:
@Entity @Table(name = "Persons1M")
public class Person1 implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@OneToMany()
@JoinTable(name="Persons1MEventsM",
joinColumns = @JoinColumn(name
= "idP1"),
inverseJoinColumns =
@JoinColumn(name = "idE") )
private Set<Event>
event;
…
@Entity @Table(name="EventsM")
public class Event {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE")
private long idE1;
…
При этом генерируется следующая схема:
create table EventsM (idE bigint identity not null, birthDate datetime,
primary key (idE));
create table Persons1M (idP1 bigint identity not null, MyName
varchar(255), primary key (idP1));
create table Persons1MEventsM (idP1 bigint not null, idE bigint not
null, primary key (idP1, idE), unique (idE));
alter table Persons1MEventsM add constraint FK690FE29A547EF857 foreign
key (idE) references EventsM;
alter table Persons1MEventsM add constraint FK690FE29A547885EB foreign key
(idP1) references Persons1M;
В следующем примере, давайте представим
обратную ситуацию, на первое сентября, на первый звонок, в школу пригласили учеников
(Person),
дата события к 9:00, первого сентября (Event) с цветами в школу. Связь Person(many) -> Event(one), давайте
реализуем этот пример через ассоциацию many-to-one, реализовав связь через foreign-key. Описывается
эта связь через аннотацию @ManyToOne(), и помогает связать нам поля сущностей,
знакомая аннотация @JoinColumn(), где атрибут name будет описывать поле таблицы PersonsM, а атрибут referencedColumnName – поле таблицы
EventsM. Давайте посмотрим на листинг, как все реализовано:
@Entity @Table(name = "PersonsM")
public class Person implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@ManyToOne()
@JoinColumn(name =
"idF",referencedColumnName = "idE")
private Event event;
…
@Entity @Table(name="EventsM")
public class Event {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE")
private long idE1;
…
При этом будет генерироваться следующая схема
в базе данных:
create table EventsM (idE bigint identity not null, birthDate datetime,
primary key (idE));
create table PersonsM (idP1 bigint identity not null, MyName
varchar(255), idF bigint, primary key (idP1));
alter table PersonsM add constraint FK1E448C6F7411D824 foreign key (idF)
references EventsM;
И осталось нам рассмотреть пример связь many-to-one через таблицу связи (join table),
используем аннотацию @JoinTable(), где атрибут name задает имя таблицы связи, joinColumns – связывает поле таблицы Persons2M,
с таблицей связи PersonsM2EventsM,
атрибут inverseJoinColumns связывает поле таблицы Events2M, с таблицей связи PersonsM2EventsM.
И так взглянем на листинг, посмотрим, как все описывается:
@Entity @Table(name = "Persons2M")
public class Person1 implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@ManyToOne() @JoinTable(name =
"PersonsM2EventsM",
joinColumns = @JoinColumn(name
= "idF")
,inverseJoinColumns =
@JoinColumn(name = "idE") )
private Event event;
…
@Entity @Table(name="Events2M")
public class Event {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE1")
private long idE1;
…
При этом генерируется следующая схема в базе
данных:
create table Events2M (idE1 bigint identity not null, birthDate
datetime, primary key (idE1));
create table Persons2M (idP1 bigint identity not null, MyName
varchar(255), primary key (idP1));
create table PersonsM2EventsM (idE bigint, idF bigint not null, primary
key (idF));
alter table PersonsM2EventsM add constraint FKABCC4B31DAA2F946 foreign
key (idF) references Persons2M;
alter table PersonsM2EventsM add constraint FKABCC4B317411D823 foreign
key (idE) references Events2M;
Следующий пример показывает, как выглядит двунаправленная связь (Bidirectional Association) у one-to-many
и many-to-one. Возьмем за основу наш пример, самый первый
пример one-to-many через foreign-key, и преобразуем его в двунаправленную связь.
Для этого нужно добавить аннотацию @ManyToOne() в сущность Event к полю person, и в атрибуте targetEntity прописать класс сущности, на что будет
ссылаться обратная связь в нашем случае Person.class, взглянем
на листинг:
@Entity @Table(name = "PersonsM5")
public class Person implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@OneToMany() @JoinColumn(name =
"idF",referencedColumnName = "idP1")
private Set<Event>
event;
…
@Entity @Table(name="EventsM5")
public class Event {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE")
private long idE1;
@ManyToOne(targetEntity =
Person.class)
private Person person;
….
Очень просто все преобразовывается, при этом
схема базы данных остается неизменной. Давайте
преобразуем второй пример в двунаправленную связь, напомню,
у второго примера связь построена
через таблицу (join table). Добавляем в
сущность Event наше поле
person и помечаем его аннотацией @ManyToOne(),
и все, наша bidirectional association построена,
смотрим листинг:
@Entity @Table(name = "PersonsM512")
public class Person1 implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@OneToMany()
@JoinTable(name =
"Person112toEvent",
joinColumns = @JoinColumn(name
= "idP"),
inverseJoinColumns =
@JoinColumn(name ="idE"))
private Set<Event1>
event;
…
@Entity @Table(name="EventsM512")
public class Event1 {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE1")
private long idE1;
@ManyToOne(targetEntity =
Event1.class)
private Person1 person;
…
Снова схема базы данных не меняется, остается
как у однонаправленной связи. Перейдем к рассмотрению примера многие ко многим (many-to-many), в
один день родилось(Event)
множество знаменитых людей(Person), или можно
сказать наоборот множество знаменитых людей имеют одинаковую дату
рождения. Это и есть ассоциация многие
ко многим, давайте преобразуем предыдущий пример в many-to-many ( Person(many) <-> Event(many)). Заменим в сущности Person1 аннотацию @OneToMany() на @ManyToMany(),
изменим тип поля на List
у поля event. Так
же поменяем аннотацию у поля person на @ManyToMany() и сменим тип поля на List,
все теперь мы имеем двунаправленную связь многие ко многим. Давайте взглянем на листинг:
@Entity @Table(name = "PersonsM513")
public class Person1 implements Serializable {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY) @Column(name = "idP1")
private Long idP;
@ManyToMany()
@JoinTable(name =
"Person113toEvent",
joinColumns = @JoinColumn(name
= "idP"),
inverseJoinColumns =
@JoinColumn(name ="idE"))
private List<Event1>
event;
…
@Entity @Table(name="EventsM513")
public class Event1 {
@Id @GeneratedValue(strategy =
GenerationType.IDENTITY)
@Column(name =
"idE1")
private long idE1;
@ManyToMany(targetEntity =
Event1.class)
private List<Person1>
person;
…
Вот так, все просто c
ассоциациями
сущностей… И так продолжим, внимательный
читатель, уже обратил внимание на то
что, при описании сущностей мы
использовали коллекции Set, List и задался вопросом, а зачем их использовать и в чем смысл их
использования. Hibernate использует при своей работе множество коллекций языка java, из пакета java.util.*. Hibernate
рекомендует,
в сущности где поля описаны как коллекции, пользоваться интерфейсами коллекций
при описании поля, а не самими классами коллекций, но при этом инициализировать
поле коллекцию нужным вам типом.
Взглянем на листинг, поле event описано как интерфейс Set
и инициализируется типом коллекции HashSet():
@Entity
public class Person implements Serializable {
@Id
private Long idP;
@OneToMany() @org.hibernate.annotations.OrderBy(clause =
"event asc")
private Set<Event> event = new HashSet< Event >();
Коллекция Set не может содержать
одинаковые значения, т.е. если у вас все поля
имеют уникальные значения, то
нужно им пользоваться, сортировка
значений не поддерживается, хотя если применить
при инициализации класс TreeSet() и
определить Comparator то можно получить отсортированную коллекцию
SortedSet.
Если вы хотите иметь одинаковые
значения, то нужно пользоваться коллекцией List, эта коллекция может пронумеровать значения, и вы сможете
обращаться к ним по порядку. Хотелось бы
отметить такой факт, что hibernate при работе с данными коллекции внутри
“движка” у себя пользуется собственными
внутренними представлениями коллекций. Для
примера, для Set используется внутреннее представление
org.hibernate.collection.internal.PersistentSet. Если вы откроете официальную документацию по Hibernate
и найдете главы Collection
Mapping, то вам представиться
возможность познакомиться с такими коллекциями как: <set>, <bag>,
<idbag>, <list>, <array>
и <map>. В принципе можно
обойтись в 99% случаев только
<set> и <list> которые мы
рассмотрели выше. Хотелось бы сказать
пару слов о сортировки коллекций, упорядочивать можно только коллекции <set>
и <map>. Существует два механизма сортировки, первое это в памяти JVM когда hibernate считал данные
из базы данных и сортирует их средствами коллекций и второй способ
сортировка средствами самой базы данных. Первый способ применим, когда в
коллекции мало элементов, второй применяется для сортировки “больших”
коллекций. При первом способе применяем
аннотацию @Sort(), в ней нужно указать атрибут type который
отвечает за способ сортировки коллекции. Второй способ использует аннотацию @OrderBy
(), где нужно указать атрибут clause
, в нем указать поле базы данных, по которой идет
сортировка и способ по возрастанию (asc) или убыванию (desc). Стоит
отметить еще одну особенность, все коллекции по умолчанию подгружаются
лениво, это нужно иметь в виду всегда при работе с коллекциями.
…
@OneToMany() @Sort (type=SortType.UNSORTED)
private Set<Event> event = new TreeSet<Event>();
…
Снова схема базы данных не меняется, остается как у однонаправленной связи. Перейдем к рассмотрению примера многие ко многим (many-to-many), в один день родилось(Event) множество знаменитых людей(Person), или можно сказать наоборот множество знаменитых людей имеют одинаковую дату рождения.
ОтветитьУдалить_________________________
Это разве ManytoMany?
А почему бы и нет, для простого примера сгодится? все примеры подбирал чем проще, тем лучше для понимания и отлаживал каждый пример, проверял теории как на самом деле поведет себя hibernate на практике ... с 1-ой по 15-ую статьи писал около 9 месяцев, все слова могу подтвердить практическим примером, сам иногда перечитываю свои же статьи стареть стал иногда забываю мелочи реализации... :)
УдалитьВ этом примере не множество дат к ко многим людям , а одна дата ко многим людям. ManytoOne, OnetoMany.
Удалить