JDBC с Groovy

JDBC с Groovy

1. Вступление

В этой статье мы рассмотрим, как запрашивать реляционные базы данных с помощьюJDBC, используя идиоматический Groovy.

JDBC, хотя и является относительно низким уровнем, является основой большинства ORM и других высокоуровневых библиотек доступа к данным в JVM. И мы можем использовать JDBC непосредственно в Groovy, конечно; однако, у него довольно громоздкий API.

К счастью для нас, стандартная библиотека Groovy основана на JDBC и представляет интерфейс, который является простым, простым, но мощным. Итак, мы исследуем модуль Groovy SQL.

Мы собираемся взглянуть на JDBC на простом Groovy, не принимая во внимание какой-либо фреймворк, например Spring, для которого у нас естьother guides.

2. Настройка JDBC и Groovy

Мы должны включить модуль ssqlgroovy-в наши зависимости:


    org.codehaus.groovy
    groovy
    2.4.13


    org.codehaus.groovy
    groovy-sql
    2.4.13

Нет необходимости указывать это явно, если мы используем groovy-all:


    org.codehaus.groovy
    groovy-all
    2.4.13

Мы можем найти последнюю версиюgroovy, groovy-sql иgroovy-all на Maven Central.

3. Подключение к базе данных

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

Давайте представим классgroovy.sql.Sql, который мы будем использовать для всех операций с базой данных с модулем Groovy SQL.

ЭкземплярSql представляет базу данных, с которой мы хотим работать.

Однакоan instance of Sqlisn’t a single database connection. О связях поговорим позже, не будем о них сейчас беспокоиться; давайте просто предположим, что все работает волшебным образом.

3.1. Указание параметров подключения

В этой статье мы будем использовать базу данных HSQL, которая представляет собой легкую реляционную базу данных, которая в основном используется в тестах.

Для соединения с базой данных требуется URL, драйвер и учетные данные для доступа:

Map dbConnParams = [
  url: 'jdbc:hsqldb:mem:testDB',
  user: 'sa',
  password: '',
  driver: 'org.hsqldb.jdbc.JDBCDriver']

Здесь мы решили указать те, которые используютMap, хотя это не единственный возможный выбор.

Затем мы можем получить соединение из классаSql:

def sql = Sql.newInstance(dbConnParams)

Мы увидим, как его использовать, в следующих разделах.

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

sql.close()

3.2. ИспользуяDataSource

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

Кроме того, когда мы хотим объединить соединения или использовать JNDI, источник данных является наиболее естественным вариантом.

Класс GroovySql отлично принимает источники данных:

def sql = Sql.newInstance(datasource)

3.3. Автоматическое управление ресурсами

Не забывать вызыватьclose(), когда мы закончили с экземпляромSql, утомительно; В конце концов, машины запоминают вещи намного лучше, чем мы.

С помощьюSql мы можем заключить наш код в закрытие и заставить Groovy автоматически вызыватьclose(), когда элемент управления покидает его, даже в случае исключений:

Sql.withInstance(dbConnParams) {
    Sql sql -> haveFunWith(sql)
}

4. Выдача выписок по базе данных

Теперь мы можем перейти к интересным вещам.

Самый простой и неспециализированный способ выдать запрос к базе данных - это методexecute:

sql.execute "create table PROJECT (id integer not null, name varchar(50), url varchar(100))"

Теоретически это работает как для операторов DDL / DML, так и для запросов; тем не менее, простая форма выше не предлагает способ получить результаты запроса. Оставим вопросы на потом.

Методexecute имеет несколько перегруженных версий, но, опять же, мы рассмотрим более сложные варианты использования этого и других методов в следующих разделах.

4.1. Вставка данных

Для вставки данных в небольших количествах и в простых сценариях прекрасно подходит методexecute, описанный ранее.

Однако для случаев, когда мы сгенерировали столбцы (например, с последовательностями или автоинкрементом) и хотим узнать сгенерированные значения, существует специальный метод:executeInsert.

Что касаетсяexecute, то теперь мы рассмотрим самую простую доступную перегрузку метода, оставив более сложные варианты для следующего раздела.

Итак, предположим, что у нас есть таблица с автоинкрементным первичным ключом (идентичность на языке HSQLDB):

sql.execute "create table PROJECT (ID IDENTITY, NAME VARCHAR (50), URL VARCHAR (100))"

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

def ids = sql.executeInsert """
  INSERT INTO PROJECT (NAME, URL) VALUES ('tutorials', 'github.com/eugenp/tutorials')
"""

executeInsert ведет себя точно так же, какexecute, но что он возвращает?

Оказывается, возвращаемое значение является матрицей: его строки - это вставленные строки (помните, что один оператор может привести к вставке нескольких строк), а его столбцы - сгенерированные значения.

Звучит сложно, но в нашем случае, который является наиболее распространенным, есть одна строка и одно сгенерированное значение:

assertEquals(0, ids[0][0])

Последующая вставка вернет сгенерированное значение 1:

ids = sql.executeInsert """
  INSERT INTO PROJECT (NAME, URL)
  VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
"""

assertEquals(1, ids[0][0])

4.2. Обновление и удаление данных

Точно так же существует специальный метод изменения и удаления данных:executeUpdate.

Опять же, это отличается отexecute только своим возвращаемым значением, и мы рассмотрим только его простейшую форму.

Возвращаемое значение, в этом случае, является целым числом, числом затронутых строк:

def count = sql.executeUpdate("UPDATE PROJECT SET URL = 'https://' + URL")

assertEquals(2, count)

5. Запросы к базе данных

Вещи начинают становиться Groovy, когда мы запрашиваем базу данных.

Работать с классом JDBCResultSet не совсем весело. К счастью для нас, Groovy предлагает хорошую абстракцию над всем этим.

5.1. Итерация результатов запроса

Хотя петли - это такой старый стиль ... в настоящее время мы все занимаемся закрытием.

И Groovy здесь, чтобы удовлетворить наши вкусы:

sql.eachRow("SELECT * FROM PROJECT") { GroovyResultSet rs ->
    haveFunWith(rs)
}

МетодeachRow отправляет наш запрос к базе данных и вызывает закрытие для каждой строки.

Как мы видим,a row is represented by an instance of GroovyResultSet, который является расширением простого старогоResultSet с несколькими добавленными вкусностями. Читайте дальше, чтобы узнать больше об этом.

5.2. Доступ к наборам результатов

В дополнение ко всем методамResultSet,GroovyResultSet предлагает несколько удобных утилит.

В основном, он предоставляет именованные свойства, соответствующие именам столбцов:

sql.eachRow("SELECT * FROM PROJECT") { rs ->
    assertNotNull(rs.name)
    assertNotNull(rs.URL)
}

Обратите внимание, что имена свойств не чувствительны к регистру.

GroovyResultSet также предлагает доступ к столбцам с использованием индекса с отсчетом от нуля:

sql.eachRow("SELECT * FROM PROJECT") { rs ->
    assertNotNull(rs[0])
    assertNotNull(rs[1])
    assertNotNull(rs[2])
}

5.3. пагинация

Мы можем легко отобразить результаты, то есть загрузить только подмножество, начиная с некоторого смещения до некоторого максимального количества строк. Например, это общая проблема в веб-приложениях.

eachRow и связанные методы имеют перегрузки, принимающие смещение и максимальное количество возвращаемых строк:

def offset = 1
def maxResults = 1
def rows = sql.rows('SELECT * FROM PROJECT ORDER BY NAME', offset, maxResults)

assertEquals(1, rows.size())
assertEquals('REST with Spring', rows[0].name)

Здесь методrows возвращает список строк, а не выполняет итерацию по ним, какeachRow.

6. Параметризованные запросы и утверждения

Чаще всего запросы и операторы не полностью фиксируются во время компиляции; они обычно имеют статическую часть и динамическую часть в форме параметров.

Если вы думаете о конкатенации строк, остановитесь и прочтите о SQL-инъекции!

Ранее мы упоминали, что методы, которые мы видели в предыдущих разделах, имеют много перегрузок для различных сценариев.

Давайте представим те перегрузки, которые имеют дело с параметрами в запросах и операторах SQL.

6.1. Строки с заполнителями

В стиле, подобном простому JDBC, мы можем использовать позиционные параметры:

sql.execute(
    'INSERT INTO PROJECT (NAME, URL) VALUES (?, ?)',
    'tutorials', 'github.com/eugenp/tutorials')

или мы можем использовать именованные параметры с картой:

sql.execute(
    'INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)',
    [name: 'REST with Spring', url: 'github.com/eugenp/REST-With-Spring'])

Это работает дляexecute,executeUpdate,rows иeachRow. executeInsert тоже поддерживает параметры, но его сигнатура немного отличается и сложнее.

6.2. Groovy Strings

Мы также можем выбрать стиль Groovier, используя GStrings с заполнителями.

Все методы, которые мы видели, не заменяют заполнители в GStrings обычным образом; скорее, они вставляют их как параметры JDBC, обеспечивая правильное сохранение синтаксиса SQL, без необходимости заключать в кавычки или экранировать что-либо и, следовательно, без риска внедрения.

Это прекрасно, безопасно и Groovy:

def name = 'REST with Spring'
def url = 'github.com/eugenp/REST-With-Spring'
sql.execute "INSERT INTO PROJECT (NAME, URL) VALUES (${name}, ${url})"

7. Транзакции и связи

Пока что мы пропустили очень важную проблему: транзакции.

Фактически, мы вообще не говорили о том, как GroovySql управляет подключениями.

7.1. Короткоживущие соединения

В примерах, представленных до сих пор,each and every query or statement was sent to the database using a new, dedicated connection.Sql закрывает соединение, как только операция завершается.

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

Тем не менее,if we want to issue multiple DML statements and queries as a single, atomic operation, нам нужна транзакция.

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

7.2. Транзакции с кэшированным подключением

Groovy SQL не позволяет нам создавать или получать доступ к транзакциям в явном виде.

Вместо этого мы используем методwithTransaction с закрытием:

sql.withTransaction {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
    """
}

Внутри замыкания одно соединение с базой данных используется для всех запросов и операторов.

Кроме того, транзакция автоматически фиксируется, когда закрытие завершается, если только оно не завершается досрочно из-за исключения.

Однако мы также можем вручную зафиксировать или откатить текущую транзакцию с помощью методов классаSql:

sql.withTransaction {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    sql.commit()
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
    """
    sql.rollback()
}

7.3. Кэшированные соединения без транзакции

Наконец, чтобы повторно использовать соединение с базой данных без описанной выше семантики транзакции, мы используемcacheConnection:

sql.cacheConnection {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    throw new Exception('This does not roll back')
}

8. Выводы и дальнейшее чтение

В этой статье мы рассмотрели модуль Groovy SQL и то, как он улучшает и упрощает JDBC с помощью замыканий и строк Groovy.

Затем мы можем с уверенностью заключить, что старый добрый JDBC выглядит немного более современным с небольшим количеством Groovy!

Мы не обсудили все функции Groovy SQL; например, мы не учлиbatch processing, хранимые процедуры, метаданные и другие вещи.

Для получения дополнительной информации см.the Groovy documentation.

Реализацию всех этих примеров и фрагментов кода можно найти вthe GitHub project - это проект Maven, поэтому его легко импортировать и запускать как есть.