1.19.2015

9. Ассоциации сущностей. Many-to-one, оne-to-many, many-to-many. Hibernate



Связи 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>();

3 комментария:

  1. Снова схема базы данных не меняется, остается как у однонаправленной связи. Перейдем к рассмотрению примера многие ко многим (many-to-many), в один день родилось(Event) множество знаменитых людей(Person), или можно сказать наоборот множество знаменитых людей имеют одинаковую дату рождения.
    _________________________
    Это разве ManytoMany?

    ОтветитьУдалить
    Ответы
    1. А почему бы и нет, для простого примера сгодится? все примеры подбирал чем проще, тем лучше для понимания и отлаживал каждый пример, проверял теории как на самом деле поведет себя hibernate на практике ... с 1-ой по 15-ую статьи писал около 9 месяцев, все слова могу подтвердить практическим примером, сам иногда перечитываю свои же статьи стареть стал иногда забываю мелочи реализации... :)

      Удалить
    2. В этом примере не множество дат к ко многим людям , а одна дата ко многим людям. ManytoOne, OnetoMany.

      Удалить