Статьи

Учебник Grails для начинающих — HQL Queries (executeQuery и executeUpdate)

Этот урок Grails научит основам использования HQL. Grails поддерживает динамические искатели, что делает удобным выполнение простых запросов к базе данных. Но для более сложных случаев Grails предоставляет как Criteria API, так и HQL. Этот урок будет сосредоточен на последнем.

Вступление

Хорошо известно, что Grails находится поверх Spring и Hibernate — двух самых популярных сред Java. Hibernate используется в качестве базовой технологии для объектно-реляционного отображения Grails (GORM).

Hibernate не зависит от базы данных. Это означает, что поскольку Grails основан на нем, мы могли бы писать приложения, совместимые с большинством популярных баз данных. Нам не нужно писать разные запросы для каждой возможной базы данных.

Самый простой способ выполнить запросы к базе данных — через динамические искатели. Это просто и очень интуитивно понятно. Проверьте мой предыдущий пост на учебник по этой теме. Динамические искатели, однако, очень ограничены. Это может не подходить для сложных требований и случаев, когда разработчику требуется более низкий уровень контроля. HQL — очень хорошая альтернатива, поскольку она очень похожа на SQL.

HQL полностью объектно-ориентирован и понимает наследование, полиморфизм и ассоциацию. Его использование обеспечит очень мощный и гибкий API, сохраняя при этом ваше приложение независимым от базы данных. В Grails есть два доменных метода для вызова HQL

  • executeQuery — выполняет запросы HQL (операции SELECT)
  • executeUpdate — обновляет базу данных с помощью операций в стиле DML (UPDATE и DELETE).

ExecuteQuery

Вот пример класса домена, из которого мы будем запрашивать:

1
2
3
4
5
6
7
8
package asia.grails.test
class Person {
    String firstName
    String lastName
    int age
    static constraints = {
    }
}

Получить все объекты домена

Это код для извлечения всех объектов Person из базы данных:

1
def listOfAllPerson = Person.executeQuery("from Person")

Заметь:

  • executeQuery — это метод класса Domain, используемый для получения информации (операторы SELECT)
  • Как и в SQL, требуется идентификатор from
  • Вместо указания таблицы мы указываем класс домена сразу после ключевого слова from . Мы могли бы также написать запрос так
    1
    def listOfAllPerson = Person.executeQuery("from asia.grails.test.Person")
  • Действительно, не указывать предложение select . По умолчанию он возвращает экземпляры объектов указанного класса Domain. В этом примере он вернет список всех объектов Person.

Вот пример кода того, как мы могли бы использовать результат

1
2
3
4
5
listOfAllPerson.each { person ->
    println "First Name = ${person.firstName}"
    println "Last Name = ${person.lastName}"
    println "Age = ${person.age}"
}

Поскольку listOfAllPerson является списком экземпляров Person, мы можем выполнить итерацию по нему и распечатать детали.

Выберите пункт

Когда предложение select используется явно, HQL не возвращает список объектов домена. Вместо этого он вернет двумерный список. Вот пример, предполагающий, что по крайней мере 1 запись находится в базе данных:

1
2
3
4
5
6
def list = Person.executeQuery("select firstName, lastName from Person")
def firstPerson = list[0]
def firstName = firstPerson[0]
def lastName = firstPerson[1]
println "First Name = ${firstName}"
println "Last Name = ${lastName}"

Переменному списку будет присвоен список элементов. Каждый элемент представляет собой список, который соответствует значению, указанному в предложении select.

Код также может быть написан так, чтобы помочь визуализировать структуру данных:

1
2
3
4
5
def list = Person.executeQuery("select firstName, lastName from Person")
def firstName = list[0][0]
def lastName = list[0][1]
println "First Name = ${firstName}"
println "Last Name = ${lastName}"

Где пункт

Как и в SQL, мы можем фильтровать результаты запроса, используя предложение where. Вот некоторые примеры:

Люди с фамилией Доу

1
def peopleWithSurnameDoe = Person.executeQuery("from Person where lastName = 'Doe'")

Люди, которым исполнилось 18 лет

1
def adults = Person.executeQuery("from Person where age >= 18")

Люди, чье имя содержит Джон

1
def peopleWithFirstNameLikeJohn = Person.executeQuery("from Person where firstName like '%John%'")

Групповое предложение

Групповое предложение также допускается. Поведение похоже на SQL. Вот пример:

1
2
3
4
5
6
def list = Person.executeQuery("select age, count(*) from Person group by age")
list.each { item ->
    def age = item[0]
    def count = item[1]
    println "There are ${count} people with age ${age} years old"
}

Это напечатает все возрасты, найденные в таблице, и сколько людей имеют этот возраст.

Имея пункт

Имеющее предложение полезно отфильтровать результат группы по. Вот пример:

1
2
3
4
5
6
7
def list = Person.executeQuery(
    "select age, count(*) from Person group by age having count(*) > 1")
    list.each { item ->
        def age = item[0]
        def count = item[1]
    println "There are ${count} people with age ${age} years old"
}

Это напечатает все возрасты, найденные в таблице, и сколько людей имеют этот возраст, при условии, что в возрастной группе более 1 человека.

пагинация

Производительность не позволяет одновременно извлечь все записи в таблице. Это более эффективно, чтобы страница результатов. Например, получить 10 записей одновременно. Вот пример кода о том, как это сделать:

1
2
3
def listPage1 = Person.executeQuery("from Person order by id", [offset:0, max:10])
def listPage2 = Person.executeQuery("from Person order by id", [offset:10, max:10])
def listPage3 = Person.executeQuery("from Person order by id", [offset:20, max:10])

Параметр max информирует GORM о необходимости выбрать максимум 10 записей. Смещение означает, сколько записей пропустить, прежде чем читать первый результат.

  • На странице 1 мы не пропускаем ни одной записи и получаем первые 10 результатов
  • На странице 2 мы пропускаем первые 10 записей и получаем с 11 по 20 записи.
  • На странице 3 мы пропускаем первые 20 записей и получаем 21-30 записи.

GORM / Hibernate переведет информацию о подкачке в правильный синтаксис SQL в зависимости от базы данных.

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

Список параметров

У операторов HQL могут быть параметры. Вот пример:

1
2
def result = Person.executeQuery(
    "from Person where firstName = ? and lastName = ?", ['John', 'Doe'])

Параметры могут быть переданы в виде списка. Первый параметр (John) используется в первом вопросительном знаке, второй параметр (Doe) используется во втором вопросительном знаке и так далее.

Результаты также могут быть разбиты на страницы

1
2
def result = Person.executeQuery(
    "from Person where firstName = ? and lastName = ?", ['John', 'Doe'], [offset:0, max:5])

Именованные параметры

Предоставление параметров списка обычно трудно читать и подвержено ошибкам. Проще использовать именованные параметры. Например:

1
2
3
def result = Person.executeQuery(
    "from Person where firstName = :searchFirstName and lastName = :searchLastName",
    [searchFirstName:'John', searchLastName:'Doe'])

Двоеточие означает именованную переменную параметра. Затем значения могут быть переданы как карта значений.

Результаты также могут быть разбиты на страницы:

1
2
3
def result = Person.executeQuery(
    "from Person where firstName = :searchFirstName and lastName = :searchLastName",
    [searchFirstName:'John', searchLastName:'Doe'], [offset:0, max:5])

Вот более короткая версия:

1
2
3
def result = Person.executeQuery(
    "from Person where firstName = :searchFirstName and lastName = :searchLastName",
    [searchFirstName:'John', searchLastName:'Doe'], [offset:0, max:5])

Как выполнять СОЕДИНЕНИЯ

Вот пример классов домена отношения один ко многим:

1
2
3
4
5
6
7
package asia.grails.test
class Purchase {
    static hasMany = [items:PurchaseItem]
    String customer
    Date dateOfPurchase
    double price
}
1
2
3
4
5
6
7
8
package asia.grails.test
class PurchaseItem {
    static belongsTo = Purchase
    Purchase parentPurchase
    String product
    double price
    int quantity
}

Вот пример кода, который объединяет две таблицы:

1
2
def customerWhoBoughtPencils = Purchase.executeQuery(
    "select p.customer from Purchase p join p.items i where i.product = 'Pencil' ")

Это возвращает всех клиентов, которые купили карандаши

executeUpdate

Мы можем обновить или удалить записи, используя executeUpdate. Это иногда более эффективно, особенно при работе с большими наборами записей.

Удалить

Вот несколько примеров того, как удалять записи, используя executeUpdated.

Удалить все записи человека в базе данных

1
Purchase.executeUpdate("delete Person")

Вот разные способы удаления людей с именем Джон

1
2
3
Person.executeUpdate("delete Person where firstName = 'John'")
Person.executeUpdate("delete Person where firstName = ? ", ['John'])
Person.executeUpdate("delete Person where firstName = :firstNameToDelete ", [firstNameToDelete:'John'])

Обновить

Вот несколько примеров того, как удалять записи, используя executeUpdated.

Вот разные способы, как заставить всех людей иметь возраст 15

1
2
3
Person.executeUpdate("update Person set age = 15")
Person.executeUpdate("update Person set age = ?", [15])
Person.executeUpdate("update Person set age = :newAge", [newAge:15])

Вот несколько способов установить возраст Джона Доу на 15 лет.

1
2
3
4
5
6
7
Person.executeUpdate(
    "update Person set age = 15 where firstName = 'John' and lastName = 'Doe'")
Person.executeUpdate(
    "update Person set age = ? where firstName = ? and lastName = ?", [15, 'John', 'Doe'])
Person.executeUpdate(
    "update Person set age = :newAge where firstName = :firstNameToSearch and lastName = :lastNameToSearch",
    [newAge:15, firstNameToSearch:'John', lastNameToSearch:'Doe'])