1. Знайомство

Ще одна корисна річ, про яку хотілося б розповісти — це NativeQuery. Як ти знаєш, за допомогою NativeQuery можна писати запити на рідному SQL. Однак ще цікавішим є те, що ти можеш не використовувати мапінг класів під час отримання результату запиту.

Я краще покажу тобі приклад:

List<Object[]> люди = session.createNativeQuery("SELECT * FROM Person" ).list();

У цьому прикладі ми не передаємо клас, який відповідає рядкам результату запиту — натомість використовується просто масив об'єктів Object.

Це може бути корисним, якщо ти хочеш зробити вибірку лише пари колонок таблиці. Приклад:


List<Object[]> люди = session.createNativeQuery("SELECT id, name FROM Person").list();
 
for(Object[] person : persons) {
    Number id = (Number) person[0];
    String name = (String) person[1];
}

Це чимось схоже на підхід JDBC, коли ти отримуєш об'єкт ResultSet і читаєш дані з його рядків.

Однак Hibernate пропонує різні способи зробити цю справу надійнішою. Наприклад, ти можеш вказати тип колонок, які хочеш віднімати. Приклад:


Query<Object[]> query = session.createNativeQuery("SELECT id, name FROM Person");
query.addScalar("id", StandardBasicTypes.LONG< /span>);
query.addScalar("name", StandardBasicTypes.STRING< /span>);
List<Object[]> люди = query.list();
 
for(Object[] person : persons) {
    Long id = (Long) person[0];
    String name = (String) person[1];
}

2. Entity мапінг

Також ти можеш явно вказати клас, який Hibernate має використовувати під час парсингу результату NativeQuery. Зробити це можна у різний спосіб.


Query<Person> query = session.createNativeQuery("SELECT * FROM Person" )
    .addEntity(Person.class);
    .list();

І звісно, старий-добрий відомий тобі формат:


Query<Person> query = session.createNativeQuery("SELECT * FROM Person" , Person.class).list();

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

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


List<Phone> results = session.createNativeQuery(
    "SELECT {ph.*}, {pr.*}" +
    "FROM Phone ph" +
    "JOIN Person pr ON ph.person_id = pr.id")
.addEntity("ph", Phone.class)
.addJoin("pr", "ph.person")
.list();
 
for (Phone. phone : results) {
           assertNotNull( phone.getPerson().getName() );
}

Такий підхід за допомогою NativeQuery можна використовувати для прискорення вибірки даних із бази. Якщо ти знаєш, що якісь стовпчики тобі не потрібні, ти можеш їх не вказувати у запиті.

Також ти можеш одразу завантажити всі дочірні сутності, навіть якщо Hibernate хоче використовувати Cache або механізм LazyLoading. До того ж, у твоїх дочірніх сутностей може бути багато колонок у базі, і ти можеш відібрати лише деякі з них.

3. DTO-мапінг

Також Hibernate дозволяє використовувати для мапінгу результату не Entity-класів. Класи, які не мають жодних анотацій, і які не замаплені на будь-які таблиці.

Приклад:


public class PersonSummaryDTO {
    private Number id;
    private String name;
 
    public Number getId() {
    return id;
    }
 
    public void setId(Number id) {
    this.id = id;
    }
 
    public String getName() {
    return name;
    }
 
    public void setName(String name) {
    this.name = name;
}
}

List<PersonSummaryDTO> dtos = session.createNativeQuery(
    "SELECT p.id as \"id\", p.name as \"name\" FROM Person p")
.setResultTransformer(Transformers.aliasToBean(< span class="text-brown">PersonSummaryDTO.class) )
.list();

Оскільки анотацій у класу PersonSummaryDTO немає, імена колонок у SQL-запиті повинні точно збігатися з іменами полів класу PersonSummaryDTO.

Таке буває дуже корисно, якщо ти читаєш дані із зовнішньої бази даних, до якої твоя програма підключена тільки в режимі read-only. Тобто тобі дали доступ до таблиць, у яких по 50+ колонок, вони зберігають дані в денормалізованому вигляді для прискорення вибірки.

Або припустимо, що хтось вирішив зберігати ієрархію класів в одній таблиці, а за п'ять років ця таблиця так розрослася, що чорт ногу зломить. Тобі потрібно вибрати з цієї таблиці пару колонок (Id та ім'я користувача) та віддати їх клієнту.

Думаю, все зрозуміло, але якщо хочеш поринути у цю тему глибше, то докладніше можна почитати за посиланням:

Native SQL Queries