Друкарня від WE.UA

Я Java розробник і в вільний час займаюсь розробкою власного застосунку про який думаю, ще напишу окремий довгочит. В цьому ж довгочиті я хотів би поділитись проблемою пов’язаною з наслідуванням в сутностях із якою я стикнувся під час розробки.


Як виглядає наслідування в сутностях і що це таке ?

Насамперед розповім про те, як взагалі виглядає наслідування в сутностях в Hibernate. Для прикладу побудуємо класичну ієрархію з фігурами.

Схемка з сутностями фігур

Ну і звичайно приклад коду створення цієї ієрархії:

@Entity(name = "shapes")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "shape_type", 
        discriminatorType = DiscriminatorType.STRING)
public class Shape {
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    private Long id;
}
@Entity
@DiscriminatorValue("rectangle")
public class Rectangle extends Shape {
    private Double length;
    private Double width;
}
@Entity
@DiscriminatorValue("circle")
public class Circle extends Shape {
    private Double radius;
}

В базі даних за цим приладом створиться табличка:

Створена табличка “shapes” в бд

Існує, ще кілька варіантів організації цих сутностей в базі даних окрім однієї таблиці (об’єднані таблиці, окрема таблиця на сутність). Детальніше можна почитати тут - ТИЦЬ.

Почнемо експериментувати

На такому простому прикладі проблем немає і якщо ми спробуємо дістати всі фігури ми будемо мати лише один запит до бази даних.

Hibernate: select s1_0.id,s1_0.shape_type,s1_0.radius,s1_0.length,s1_0.width from shapes s1_0

Якщо навіть будемо використовувати інші схеми організації таблиць все одно буде один запит до бази даних але з об’єднанням.

Hibernate: select s1_0.id,s1_0.clazz_,s1_0.radius,s1_0.length,s1_0.width from ( 
      select id, null::float as radius, null::float as length, null::float as width, 0 as clazz_ from shapes 
union all 
      select id, radius, null::float as length, null::float as width, 1 as clazz_ from circle 
union all 
      select id, null::float as radius, length, width, 2 as clazz_ from rectangle ) s1_0

Проблеми починаються пізніше, якщо ми хочемо додати зв’язки з іншими таблицями в похідні сутності. Для прикладу створимо сутність з кольором і додамо звязок із прямокутником (для зручнішого перегляду sql запитів буду використовувати збереження сутностей в одній таблиці). А тепер глянемо, які запити виконаються, якщо спробувати дістати всі фігури:

Hibernate: select s1_0.id,s1_0.shape_type,s1_0.radius,s1_0.color_id,s1_0.length,s1_0.width from shapes s1_0
Hibernate: select c1_0.id,c1_0.name from colors c1_0 where c1_0.id=?
Hibernate: select c1_0.id,c1_0.name from colors c1_0 where c1_0.id=?
Hibernate: select c1_0.id,c1_0.name from colors c1_0 where c1_0.id=?
Hibernate: select c1_0.id,c1_0.name from colors c1_0 where c1_0.id=?
Hibernate: select c1_0.id,c1_0.name from colors c1_0 where c1_0.id=?

Ми бачимо класичний приклад N + 1 проблеми. Ми маємо один загальний запит і багато окремих запитів на те, щоб дістати з бази даних кольори для кожного з прямокутників.

Вирішення N + 1 проблеми

Ті хто використовують Hibernate мабуть знають, що є кілька основних способів щоб вирішити цю проблему.

  • @Fetch(FetchMode.JOIN) анотація, детальніше тут - ТИЦЬ.

  • Entity Graphs, детальніше тут - ТИЦЬ.

Обидва з цих методів не працюють якщо в вас є ієрархія в сутностях. Єдиний спосіб, що в мене працював це прописувати join fetch в анотаціях @Query в репозиторіях, наприклад:

public interface ShapeRepo extends JpaRepository<Shape, Long> {

    @Query("select s from shapes s left join fetch s.color")
    List<Shape> findAll();
}

Такий спосіб працює і в ми маємо лише один запит до бази даних з джойном:

select s1_0.id,s1_0.shape_type,s1_0.radius,c1_0.id,c1_0.name,s1_0.length,s1_0.width from shapes s1_0 left join colors c1_0 on c1_0.id=s1_0.color_id

Нова проблема

Але все не так просто, як здається здавалось би додаємо fetch до запитів і все працює добре, але ні… Давайте створимо сутність наприклад Картинка і зробимо звязок багато до багатьох з Колом. А зв’язок між Кольором і Прямокутником також змінимо на багато до багатьох.

Але якщо ми спробуємо за попередньою схемою зробити запит:

@Query("select s from shapes s left join fetch s.colors left join fetch s.images")
List<Shape> findAll();

То отримаємо помилку:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.taraskovaliv.demo.entities.Circle.images, com.taraskovaliv.demo.entities.Rectangle.colors]

Рішення цієї проблеми для унаслідуваних сутностей я так і не знайшов.

Висновок

Підсумовуючи все вищескразане скажу що краще уникати наслідування в сутностях, якщо потрібно якось розділити бізнес логіку switch працює добре. Наслідування в сутностях викликає багато проблем з оптимізацією кількостів запитів до бази даних, що може привести до проблем якщо ваші дочірні сутності мають зв’язки з іншими базами даних.

P.S.

Підписуйтесь в подальшому планую писати, ще про всякі цікаві речі що зустрінуться мені під час розробки власного застосунку і про сам застосунок).

Статті про вітчизняний бізнес та цікавих людей:

  • Як побудована програма Meest China Academy

    Курс про товарний бізнес охоплює різні етапи роботи: від пошуку ідеї до перевірки товару, логістики та масштабування. Програма Meest China Academy містить 17 модулів, які послідовно розкривають ці теми без зведення всього навчання до однієї універсальної поради.

    Теми цього довгочиту:

    Meest
  • Які технології використані в Айфон 17

    Айфон 17 поєднує OLED-дисплей із частотою до 120 Гц, чип A19, дві камери Fusion 48 Мп і швидке заряджання через USB-C. У COMFY можна купити Айфон 17 з накопичувачем на 256 або 512 ГБ, вибравши конфігурацію відповідно до обсягу фото, відео та застосунків

    Теми цього довгочиту:

    Iphone 17
  • Як вибрати вживаний iPhone: коротке керівництво для розумної покупки

    Вторинний ринок смартфонів Apple активно зростає, і питання, як вибрати вживаний iPhone, стає актуальним для дедалі більшої кількості користувачів. Правильний підхід дозволяє отримати бу iPhone з актуальною iOS і доброю камерою за помітно нижчу ціну, ніж у нових пристроїв.

    Теми цього довгочиту:

    Iphone
  • Як подолати жіночу безплідність за допомогою донорських яйцеклітин

    Діагноз "безпліддя" рідко звучить як вирок одразу. Для багатьох жінок саме в цей момент вперше звучить словосполучення "донорські яйцеклітини" — і це часто не кінець шляху до материнства, а якраз новий, реальний шанс.

    Теми цього довгочиту:

    Донорство
  • Як заспокоїти зубний біль і чому самолікування може призвести до операції?

    Раптовий біль у зубі може призвести до порушення сну та ускладненого вживання їжі. Люди часто приймають таблетки, роблять полоскання або лікуються народними засобами. В таких випадках не варто відкладати візит до стоматолога, адже можуть з’явитись ускладнення.

    Теми цього довгочиту:

    Стоматологія
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Taras Kovaliv
Taras Kovaliv@taras_kovaliv

Java розробник з ціллю

3Довгочити
316Перегляди
22Підписники
Підтримати
На Друкарні з 15 квітня 2023

Більше від автора

Це також може зацікавити:

Коментарі (4)

А навищо вам наслідування для Entities?

Це також може зацікавити: