JavaRush/Java блог/Random UA/JPA Entities and DB Relationships
Nikita Koliadin
40 рівень

JPA Entities and DB Relationships

Стаття з групи Random UA
учасників

JPA Entities && DB Relationships

Доброго часу доби, колеги!
JPA Entities and DB Relationships - 1
Даний матеріал розрахований на тих, хто вже має уявлення про організацію баз даних (далі просто DB - "Database"), мінімальні знання про те, як працює Object-Relational Mapping (далі просто ORM ), та його реалізація, такі як Hibernate / JPA . Якщо ви не знайомі з цим, раджу почати з JDBC і лише потім переходити до ORM-моделі. Я попередив, і відповідальність за вашу психіку після прочитання цієї статті без належної підготовки не несу! :) Почнемо розбиратися з усім по порядку. По-перше, ми трохи копнемо в теорію, зовсім чуйна. По-друге, ми розберемося як цей shit зробити у всіх улюблених Java. Також ми напишемо з вами проект-шпаргалку, який закріпить наше розуміння теми і послужить шаблоном ЯКтреба робити mapping . Отже, Let's do it!

What is Entity?

Сутність ( Entity ) - це якийсь об'єкт із реального життя (наприклад, машина), який має атрибути (двері, КОЛЕСА , двигун). DB Entity: у цьому випадку наша сутність зберігається у DB, все просто. Навіщо і як ми засунули машину в базу даних - розглянемо пізніше.

What is DB Relationships?

Давним давно, у тридев'ятому королівстві була створена реляційна DB . У цьому DB дані представлялися як табличок. Але й ослу із Шрека було зрозуміло, що треба було зробити механізм взаємозв'язку даних таблиць. У результаті з'явилося 4 DB відносини :
  1. One-to-One
  2. One-to-Many
  3. Many-to-One
  4. Many-to-Many
Якщо ви все це бачите вперше, попереджаю ще раз — далі буде гірше: подумайте про те, щоб піти погуляти. Всі ці відносини ми розберемо на прикладі і зрозуміємо різницю між ними.

Horror Example

У нас буде один проект у якого буде 5 branches: master, де буде опис проекту, і по 1 branch на кожну DB relationship. У кожній branch'e будуть SQL скрипти створення DB та її заповнення тестовими даними, плюс Entity клас з annotation mapping'ом. Також буде для кожної branch'і Hibernate config файл. Я буду використовувати H2 embeded DB для проекту, щоб не відволікатися на окремі моменти хмарних DB або зовнішніх DB. Перейшовши за посиланням, встановіть H2 DB собі на пилосос. Я опишу кожен крок на 1 branch'e, решта – лише ключові моменти. Наприкінці ми підіб'ємо підсумки. Поїхали. Це посилання на master branch мого проекту.

One-to-One Relationship

Посилання на branch тут .
  1. Потрібно підключити H2 DB до нашого проекту. Тут необхідно підкреслити те, що нам необхідна Ultimate IDEA для зручної роботи з DB та іншими речами. Якщо вона вже у вас є тоді йдемо безпосередньо до підключення DB. Заходимо в tab Database і робимо як на скрині:

    JPA Entities and DB Relationships - 2

    Далі переходимо до налаштувань DB. Ви можете ввести свої дані, і навіть свою СУБД, повторююсь, H2 DB я використовую для простоти.

    JPA Entities and DB Relationships - 3

    Далі налаштуємо схему. Цей крок не обов'язковий, але бажаний, якщо у вас кілька схем в DB.

    JPA Entities and DB Relationships - 4

    Застосовуємо налаштування, і в результаті у нас має вийти щось на кшталт цього:

    JPA Entities and DB Relationships - 5
  2. Базу даних ми створабо і налаштували доступ до неї з IDEA. Тепер потрібно створити таблички в ній та заповнити якимись даними. Наприклад я візьму дві сутності: Author і Book. У книги може бути автор, може бути кілька авторів, а може й не бути. На цьому прикладі ми створимо усі види зв'язків. Але в цьому пункті - One-to-One relationship. Створимо відповідний скрипт, який створює DB Tables :

    DROP TABLE IF EXISTS PUBLIC.BOOK;
    
    CREATE TABLE PUBLIC.BOOK (
      ID         INTEGER      NOT NULL AUTO_INCREMENT,
      NAME       VARCHAR(255) NOT NULL,
      PRINT_YEAR INTEGER(4)   NOT NULL,
      CONSTRAINT BOOK_PRIMARY_KEY PRIMARY KEY (ID)
    );
    
    DROP TABLE IF EXISTS PUBLIC.AUTHOR;
    
    CREATE TABLE PUBLIC.AUTHOR (
      ID          INTEGER      NOT NULL AUTO_INCREMENT,
      FIRST_NAME  VARCHAR(255) NOT NULL,
      SECOND_NAME VARCHAR(255) NOT NULL,
      BOOK_ID     INTEGER      NOT NULL UNIQUE,
      CONSTRAINT AUTHOR_PRIMARY_KEY PRIMARY KEY (ID),
      CONSTRAINT BOOK_FOREIGN_KEY FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID)
    );

    І виконаємо його:

    JPA Entities and DB Relationships - 6

    Результат виконання в консолі:

    JPA Entities and DB Relationships - 7

    Результат у DB:

    JPA Entities and DB Relationships - 8
  3. Погляньмо на діаграму наших таблиць. Для цього ПКМ на нашу DB:

    JPA Entities and DB Relationships - 9

    Результат:

    JPA Entities and DB Relationships - 10

    На UML діаграмі ми можемо бачити всі primary keys і foreign keys, також бачимо зв'язок наших таблиць.

  4. Напишемо скрипт який заповнює нашу DB тестовими даними:

    INSERT INTO PUBLIC.BOOK (NAME, PRINT_YEAR)
    VALUES ('First book', 2010),
           ('Second book', 2011),
           ('Third book', 2012);
    
    INSERT INTO PUBLIC.AUTHOR (FIRST_NAME, SECOND_NAME, BOOK_ID)
    VALUES ('Pablo', 'Lambado', 1),
           ('Pazo', 'Zopa', 2),
           ('Lika', 'Vika', 3);

    Тобто, що виходить? One-to-One relationship потрібна тоді, коли сутність однієї таблиці пов'язана з однією сутністю іншої (або взагалі не пов'язана якщо NOT NULL прибрати у BOOK_ID). У нашому прикладі в однієї книжки ПОВИНЕН бути один автор. Ніяк інакше.

  5. Тепер найцікавіше, як зв'язати Java клас із DB сутностями? Дуже просто. Створимо два класи Book та Author. На прикладі я розберу 1 клас і ключові поля зв'язку. Візьму за приклад Author клас:

    @Data
    @Entity
    @DynamicInsert
    @DynamicUpdate
    @Table(name = "AUTHOR")
    public class Author {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "ID", nullable = false)
        private Long id;
    
        @Column(name = "FIRST_NAME", nullable = false)
        private String firstName;
    
        @Column(name = "SECOND_NAME", nullable = false)
        private String secondName;
    
        @OneToOne
        @JoinColumn(name = "BOOK_ID", unique = true, nullable = false)
        private Book book;
    }
Розберемося по порядку:
  1. Усі поля у класі повторюють атрибути DB сутності.
  2. @DataLombok'a ) каже, що для кожного поля буде створено геттер і сеттер, буде перевизначено equals, hashcode, і згенерований доString метод.
  3. @Entity каже, що цей клас - сутність і пов'язаний із сутністю DB.
  4. @DynamicInsert і @DynamicUpdate кажуть, що будуть виконуватися динамічні вставки та оновлення в DB. Це більш глибокі налаштування Hibernate, які стануть вам у нагоді, щоб у вас був ПРАВИЛЬНИЙ батчинг.
  5. @Table (name = "AUTHOR") пов'язує клас Book із таблицею DB AUTHOR.
  6. @Id каже, що це поле - primary key.
  7. @GeneratedValue (strategy = GenerationType.IDENTITY) - стратегія генерації primary key.
  8. @Column (name = "ID", nullable = false) пов'язує поле з атрибутом DB, і каже, що це поле DB може бути null. Це також корисно при генерації таблиць із сутностей. Зворотний процес, тому що ми зараз створюємо наш проект, це потрібно в тестових DB для Unit тестів.
  9. @OneToOne каже, що це поле є полем відносини One-to-One.
  10. @JoinColumn (name = "BOOK_ID", unique = true, nullable = false) - буде створено стовпчик BOOK_ID, який є унікальним і not null.
На звороті (у класі Book ) нам також потрібно зробити зв'язок One-to-One і вказати поле, по якому відбувається mapping. @OneToOne(mappedBy = "book") - в даному прикладі це поле book класу Author. JPA сам їх зв'яже. З першого погляду може здатися що тут каша з анотацій, але насправді це дуже зручно і з досвідом ви автоматично їх ставитимете, навіть не замислюючись.
  1. Тепер налаштуємо Hibernate. Для цього створимо файл hibernate.cfg.xml :

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
            <property name="hibernate.connection.driver_class">org.h2.Driver</property>
    
            <property name="hibernate.connection.url">jdbc:h2:~/db/onetoone</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password"/>
    
            <property name="hibernate.hbm2ddl.auto">update</property>
    
            <property name="hibernate.show_sql">true</property>
            <property name="hibernate.format_sql">true</property>
            <property name="hibernate.use_sql_comments">true</property>
    
            <property name="hibernate.generate_statistics">true</property>
    
            <property name="hibernate.jdbc.batch_size">50</property>
            <property name="hibernate.jdbc.fetch_size">50</property>
    
            <property name="hibernate.order_inserts">true</property>
            <property name="hibernate.order_updates">true</property>
            <property name="hibernate.jdbc.batch_versioned_data">true</property>
    
            <mapping class="com.qthegamep.forcodegympublication2.entity.Book"/>
            <mapping class="com.qthegamep.forcodegympublication2.entity.Author"/>
        </session-factory>
    </hibernate-configuration>
Опис властивостей :
  1. hibernate.dialect — діалект СУБД, яку ми вибрали.
  2. hibernate.connection.driver_class - Driver клас нашої DB.
  3. hibernate.connection.url - utl нашої DB. Можна взяти з першого пункту, де налаштовували DB.
  4. hibernate.connection.username - ім'я користувача DB.
  5. hibernate.connection.password - пароль користувача DB.
  6. hibernate.hbm2ddl.auto - налаштування генерації таблиць. Якщо update, то не генерує, якщо вона вже створена, а лише оновлює її.
  7. hibernate.show_sql – чи показувати запити DB.
  8. hibernate.format_sql — чи форматувати запити DB. Якщо ні то вони будуть все в один рядок. Рекомендую вмикати.
  9. hibernate.use_sql_comments - Коментує запити DB. Якщо це Insert пише над запитом коментар що запит типу Insert.
  10. hibernate.generate_statistics – генерує логи. Рекомендую і рекомендую налаштувати логування по максимуму. Читання логів збільшить ваші шанси правильної роботи з ORM.
  11. hibernate.jdbc.batch_size - Максимальний розмір батча.
  12. hibernate.jdbc.fetch_size - Максимальний розмір фетчу.
  13. hibernate.order_inserts - дозволяє динамічні вставки.
  14. hibernate.order_updates - дозволяє динамічні оновлення.
  15. hibernate.jdbc.batch_versioned_data - дозволяє батчинг. Дивіться своєю СУБД: не всі це підтримують.
  16. mapping class – класи, які є нашими сутностями. Перелічувати треба все.
  1. Тепер у нас сутність має визначитися. Можемо це перевірити у persistence tab'і:

    JPA Entities and DB Relationships - 11

    Результат:

    JPA Entities and DB Relationships - 12
  2. Також нам потрібно налаштувати assign data:

    JPA Entities and DB Relationships - 13 JPA Entities and DB Relationships - 14

    Підсумки: Ми зробабо One-to-One mapping. Матеріал є ознайомлювальним, деталі - у повідомленні.

One-to-Many Relationship

Посилання на branch тут . Код я більше не викладатиму в статті, тому що вона і так вже занадто велика. Весь код дивимось на GitHub'e.
  1. В результаті виконання скрипту, що ініціалізує, у нас вийде наступне:

    JPA Entities and DB Relationships - 15

    Чи відчуваєте різницю з попередньою таблицею?

  2. Діаграма:

    JPA Entities and DB Relationships - 16

    One-to-Many Relationship - у нас в одного автора може бути кілька книг. Лівою сутності відповідає одна чи кілька правої.

  3. Відмінність у mapping'e буде в анотаціях та полях:

    В Author класу з'являється поле:

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    private Set<Book> books;

    Воно вже є сетом, оскільки ми можемо мати кілька книг. @OneToMany говорить про тип відносини. FetchType.Lazy каже, що не потрібно нам підвантажувати весь список книг, якщо це не зазначено в запиті. Також слід сказати, що це поле НЕ МОЖНА додавати в toString, інакше підемо курити StackOverflowError. Про це у мене дбає мій улюблений Lombok:

    @ToString(exclude = "books")

    У класі Book ми робимо зворотний зв'язок (Many-to-One):

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "AUTHOR_ID", nullable = false)
    private Author author;

    Тут ми робимо висновок, що One-to-Many є дзеркальним відображенням Many-to-One і навпаки. Слід наголосити, що Hibernate нічого не знає про двонаправлені зв'язки. Для нього це два різні зв'язки: один в один бік, інший — у протилежний.

  4. У hibernate.cfg.xml особливо нічого не змінилося.

  5. Persistence:

    JPA Entities and DB Relationships - 17

Many-to-One Relationship

Оскільки Many-to-One є дзеркальним відображенням One-to-Many, відмінностей буде небагато. Посилання на branch тут .
  1. В результаті виконання скрипту, що ініціалізує, отримаємо результат:

    JPA Entities and DB Relationships - 18
  2. Діаграма:

    JPA Entities and DB Relationships - 19
  3. Відмінність у mapping'e буде в анотаціях та полях:

    У класі Author більше немає сета, оскільки він перемістився до Book класу.

  4. hibernate.cfg.xml

  5. Persistence:

    JPA Entities and DB Relationships - 20

Many-to-Many Relationship

Перейдемо до найцікавішого відношення. Це відношення за всіма правилами пристойності та непристойності створюється через додаткову таблицю. Але ця таблиця не є сутністю. Цікаво, так? Погляньмо на цей shit. Посилання на branch тут .
  1. Подивіться на ініціалізуючий скрипт , тут з'являється додаткова таблиця HAS. У нас виходить щось на зразок author-has-book.

    В результаті виконання скрипту ми отримаємо такі таблиці:

    JPA Entities and DB Relationships - 21
  2. Діаграма:

    JPA Entities and DB Relationships - 22

    У нашому прикладі виходить, що книга може бути багато автором, і автор може мати багато книг. Вони можуть перетинатися.

  3. У класах mapping'a будуть присутні мережі в класах. Але, як я вже сказав, таблиця HAS це не сутність.

    Клас Author :

    @ManyToMany
    @JoinTable(name = "HAS",
            joinColumns = @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID"),
            inverseJoinColumns = @JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
    )
    private Set<Book> books;

    @ ManyToMany - вид відносини.

    @JoinTable - саме і буде пов'язувати атрибут з додатковою таблицею HAS. У ній ми вказуємо два атрибути, які вказуватимуть на primary keys двох сутностей.

    Клас Book :

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "books")
    private Set<Author> authors;

    Тут вказуємо FetchType і поле, яким будемо мапитися.

  4. Наш hibernate.cfg.xml знову-таки не зазнав змін (я не враховую те, що ми до кожної branch створювали нову DB).

  5. Persistence:

    JPA Entities and DB Relationships - 23

Розбір польотів

Отже, ми поверхово розглянули види DB relationships і розібралися, як їх реалізувати в ORM моделі. Ми написали тестовий проект, який демонструє всі зв'язки, і розібралися, як конфігувати hibernate/jpa. Фух.

Корисні посилання

Мої попередні статті: PS Можуть бути помилки, ВІДЧИП'ЯТКИ в тексті. PPS Автор курив щось дивне під час написання цієї статті. Дякую за увагу!
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.