1. Обзор
В этой статье будет представлен объектно – ориентированный запрос Jooq – Jooq-и простой способ его настройки в сотрудничестве с Spring Framework.
Большинство приложений Java имеют некоторую сохраняемость SQL и получают доступ к этому уровню с помощью инструментов более высокого уровня, таких как JPA. И хотя это полезно, в некоторых случаях вам действительно нужен более тонкий, более тонкий инструмент, чтобы получить доступ к вашим данным или на самом деле воспользоваться всем, что может предложить базовая БД.
Jooq избегает некоторых типичных шаблонов ORM и генерирует код, который позволяет нам создавать типобезопасные запросы и получать полный контроль над сгенерированным SQL с помощью чистого и мощного fluent API.
Эта статья посвящена весеннему MVC. В нашей статье поддержка Spring Boot для jOOQ описывается, как использовать jOOQ в Spring Boot.
2. Зависимости Maven
Для выполнения кода в этом руководстве необходимы следующие зависимости.
2.1. jOOQ
org.jooq jooq 3.2.14
2.2. Весна
Для нашего примера требуется несколько зависимостей Spring; однако, чтобы упростить задачу, нам просто нужно явно включить две из них в файл POM:
org.springframework spring-context 5.2.2.RELEASE org.springframework spring-jdbc 5.2.2.RELEASE
2.3. База данных
Чтобы упростить наш пример, мы будем использовать встроенную базу данных H2:
com.h2database h2 1.4.191
3. Генерация кода
3.1. Структура базы данных
Давайте представим структуру базы данных, с которой мы будем работать на протяжении всей этой статьи. Предположим, что нам нужно создать базу данных для издателя, чтобы хранить информацию о книгах и авторах, которыми они управляют, где автор может написать много книг, а книга может быть написана в соавторстве со многими авторами.
Чтобы упростить задачу, мы создадим только три таблицы: book для книг, author для авторов и другую таблицу под названием author_book для представления отношений “многие ко многим” между авторами и книгами. Таблица author содержит три столбца: id , first_name и last_name. Таблица book содержит только столбец title и первичный ключ id//.
Следующие SQL-запросы, хранящиеся в файле intro_schema.sql resource, будут выполняться в базе данных, которую мы уже настроили ранее, чтобы создать необходимые таблицы и заполнить их образцами данных:
DROP TABLE IF EXISTS author_book, author, book; CREATE TABLE author ( id INT NOT NULL PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50) NOT NULL ); CREATE TABLE book ( id INT NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL ); CREATE TABLE author_book ( author_id INT NOT NULL, book_id INT NOT NULL, PRIMARY KEY (author_id, book_id), CONSTRAINT fk_ab_author FOREIGN KEY (author_id) REFERENCES author (id) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT fk_ab_book FOREIGN KEY (book_id) REFERENCES book (id) ); INSERT INTO author VALUES (1, 'Kathy', 'Sierra'), (2, 'Bert', 'Bates'), (3, 'Bryan', 'Basham'); INSERT INTO book VALUES (1, 'Head First Java'), (2, 'Head First Servlets and JSP'), (3, 'OCA/OCP Java SE 7 Programmer'); INSERT INTO author_book VALUES (1, 1), (1, 3), (2, 1);
3.2. Свойства плагина Maven
Мы будем использовать три различных плагина Maven для создания кода Jooq. Первым из них является плагин Properties Maven.
Этот плагин используется для чтения данных конфигурации из файла ресурсов. Это не требуется, так как данные могут быть добавлены непосредственно в POM, но рекомендуется управлять свойствами извне.
В этом разделе мы определим свойства подключений к базе данных, включая класс драйвера JDBC, URL-адрес базы данных, имя пользователя и пароль, в файле с именем intro_config.properties . Экстернализация этих свойств позволяет легко переключать базу данных или просто изменять данные конфигурации.
Цель read-project-properties этого плагина должна быть привязана к ранней фазе, чтобы данные конфигурации могли быть подготовлены для использования другими плагинами. В этом случае он привязан к фазе инициализация :
org.codehaus.mojo properties-maven-plugin 1.0.0 initialize read-project-properties src/main/resources/intro_config.properties
3.3. Плагин SQL Maven
Плагин SQL Maven используется для выполнения инструкций SQL для создания и заполнения таблиц базы данных. Он будет использовать свойства, извлеченные из файла intro_config.properties плагином Properties Maven, и возьмет инструкции SQL из ресурса intro_schema.sql .
Плагин SQL Maven настроен следующим образом:
org.codehaus.mojo sql-maven-plugin 1.5 initialize execute ${db.driver} ${db.url} ${db.username} ${db.password} src/main/resources/intro_schema.sql com.h2database h2 1.4.191
Обратите внимание, что этот плагин должен быть размещен позже, чем плагин свойств Maven в файле POM, так как их цели выполнения привязаны к одной и той же фазе, и Maven выполнит их в том порядке, в котором они перечислены.
3.4. Плагин jOOQ Codegen
Плагин Jooq Codegen генерирует Java-код из структуры таблицы базы данных. Его цель generate должна быть привязана к фазе generate-sources , чтобы обеспечить правильный порядок выполнения. Метаданные плагина выглядят следующим образом:
org.jooq jooq-codegen-maven ${org.jooq.version} generate-sources generate ${db.driver} ${db.url} ${db.username} ${db.password} com.baeldung.jooq.introduction.db src/main/java
3.5. Генерация кода
Чтобы завершить процесс генерации исходного кода, нам нужно запустить фазу Maven generate-sources . В Eclipse мы можем сделать это, щелкнув правой кнопкой мыши на проекте и выбрав Run As – > Maven generate – sources . После выполнения команды создаются исходные файлы, соответствующие таблицам author , book , author_book (и нескольким другим для вспомогательных классов).
Давайте углубимся в классы таблиц, чтобы увидеть, что произвел Jooq. Каждый класс имеет статическое поле с тем же именем, что и класс, за исключением того, что все буквы в имени заглавные. Ниже приведены фрагменты кода, взятые из определений сгенерированных классов:
Класс Автор :
public class Author extends TableImpl{ public static final Author AUTHOR = new Author(); // other class members }
Книга класс:
public class Book extends TableImpl{ public static final Book BOOK = new Book(); // other class members }
Авторская книга класс:
public class AuthorBook extends TableImpl{ public static final AuthorBook AUTHOR_BOOK = new AuthorBook(); // other class members }
Экземпляры, на которые ссылаются эти статические поля, будут служить объектами доступа к данным для представления соответствующих таблиц при работе с другими слоями в проекте.
4. Конфигурация пружины
4.1. Перевод исключений jOOQ в Spring
Чтобы исключения, создаваемые при выполнении Jooq, соответствовали поддержке Spring для доступа к базе данных, нам нужно перевести их в подтипы класса DataAccessException .
Давайте определим реализацию интерфейса ExecuteListener для преобразования исключений:
public class ExceptionTranslator extends DefaultExecuteListener { public void exception(ExecuteContext context) { SQLDialect dialect = context.configuration().dialect(); SQLExceptionTranslator translator = new SQLErrorCodeSQLExceptionTranslator(dialect.name()); context.exception(translator .translate("Access database using Jooq", context.sql(), context.sqlException())); } }
Этот класс будет использоваться контекстом приложения Spring.
4.2. Настройка пружины
В этом разделе будут рассмотрены шаги по определению контекста персистентности , содержащего метаданные и компоненты, которые будут использоваться в контексте приложения Spring.
Давайте начнем с применения необходимых аннотаций к классу:
- @Configuration : Сделайте класс распознаваемым в качестве контейнера для бобов
- @ComponentScan : Настройка директив сканирования, включая параметр value для объявления массива имен пакетов для поиска компонентов. В этом учебном пособии пакет для поиска-это пакет, созданный плагином Jooq Codegen Maven
- @EnableTransactionManagement : Включить управление транзакциями с помощью Spring
- @PropertySource : Укажите расположение файлов свойств, которые будут загружены. Значение в этой статье указывает на файл, содержащий данные конфигурации и диалект базы данных, который является тем же файлом, упомянутым в подразделе 4.1.
@Configuration @ComponentScan({"com.baeldung.Jooq.introduction.db.public_.tables"}) @EnableTransactionManagement @PropertySource("classpath:intro_config.properties") public class PersistenceContext { // Other declarations }
Затем используйте объект Environment для получения данных конфигурации, которые затем используются для настройки компонента DataSource :
@Autowired private Environment environment; @Bean public DataSource dataSource() { JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setUrl(environment.getRequiredProperty("db.url")); dataSource.setUser(environment.getRequiredProperty("db.username")); dataSource.setPassword(environment.getRequiredProperty("db.password"));
return dataSource; }
Теперь мы определяем несколько компонентов для работы с операциями доступа к базе данных:
@Bean public TransactionAwareDataSourceProxy transactionAwareDataSource() { return new TransactionAwareDataSourceProxy(dataSource()); } @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public DataSourceConnectionProvider connectionProvider() { return new DataSourceConnectionProvider(transactionAwareDataSource()); } @Bean public ExceptionTranslator exceptionTransformer() { return new ExceptionTranslator(); } @Bean public DefaultDSLContext dsl() { return new DefaultDSLContext(configuration()); }
Наконец, мы предоставляем реализацию Jooq Configuration и объявляем ее как компонент Spring, который будет использоваться классом DSLContext :
@Bean public DefaultConfiguration configuration() { DefaultConfiguration JooqConfiguration = new DefaultConfiguration(); jooqConfiguration.set(connectionProvider()); jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer())); String sqlDialectName = environment.getRequiredProperty("jooq.sql.dialect"); SQLDialect dialect = SQLDialect.valueOf(sqlDialectName); jooqConfiguration.set(dialect); return jooqConfiguration; }
5. Использование jOOQ С Пружиной
В этом разделе демонстрируется использование Jooq в общих запросах доступа к базе данных. Существует два теста, один для фиксации и один для отката, для каждого типа операции “запись”, включая вставку, обновление и удаление данных. Использование операции “чтение” показано при выборе данных для проверки запросов “запись”.
Мы начнем с объявления объекта autowired Sslcontext и экземпляров классов, сгенерированных Jooq, которые будут использоваться всеми методами тестирования:
@Autowired private DSLContext dsl; Author author = Author.AUTHOR; Book book = Book.BOOK; AuthorBook authorBook = AuthorBook.AUTHOR_BOOK;
5.1. Вставка Данных
Первым шагом является вставка данных в таблицы:
dsl.insertInto(author) .set(author.ID, 4) .set(author.FIRST_NAME, "Herbert") .set(author.LAST_NAME, "Schildt") .execute(); dsl.insertInto(book) .set(book.ID, 4) .set(book.TITLE, "A Beginner's Guide") .execute(); dsl.insertInto(authorBook) .set(authorBook.AUTHOR_ID, 4) .set(authorBook.BOOK_ID, 4) .execute();
A ВЫБЕРИТЕ запрос для извлечения данных:
Result> result = dsl .select(author.ID, author.LAST_NAME, DSL.count()) .from(author) .join(authorBook) .on(author.ID.equal(authorBook.AUTHOR_ID)) .join(book) .on(authorBook.BOOK_ID.equal(book.ID)) .groupBy(author.LAST_NAME) .fetch();
Приведенный выше запрос выдает следующие выходные данные:
+----+---------+-----+ | ID|LAST_NAME|count| +----+---------+-----+ | 1|Sierra | 2| | 2|Bates | 1| | 4|Schildt | 1| +----+---------+-----+
Результат подтверждается API Assert :
assertEquals(3, result.size()); assertEquals("Sierra", result.getValue(0, author.LAST_NAME)); assertEquals(Integer.valueOf(2), result.getValue(0, DSL.count())); assertEquals("Schildt", result.getValue(2, author.LAST_NAME)); assertEquals(Integer.valueOf(1), result.getValue(2, DSL.count()));
При возникновении сбоя из-за недопустимого запроса создается исключение, и транзакция откатывается. В следующем примере запрос INSERT нарушает ограничение внешнего ключа, что приводит к исключению:
@Test(expected = DataAccessException.class) public void givenInvalidData_whenInserting_thenFail() { dsl.insertInto(authorBook) .set(authorBook.AUTHOR_ID, 4) .set(authorBook.BOOK_ID, 5) .execute(); }
5.2. Обновление Данных
Теперь давайте обновим существующие данные:
dsl.update(author) .set(author.LAST_NAME, "Baeldung") .where(author.ID.equal(3)) .execute(); dsl.update(book) .set(book.TITLE, "Building your REST API with Spring") .where(book.ID.equal(3)) .execute(); dsl.insertInto(authorBook) .set(authorBook.AUTHOR_ID, 3) .set(authorBook.BOOK_ID, 3) .execute();
Получите необходимые данные:
Result> result = dsl .select(author.ID, author.LAST_NAME, book.TITLE) .from(author) .join(authorBook) .on(author.ID.equal(authorBook.AUTHOR_ID)) .join(book) .on(authorBook.BOOK_ID.equal(book.ID)) .where(author.ID.equal(3)) .fetch();
Выход должен быть:
+----+---------+----------------------------------+ | ID|LAST_NAME|TITLE | +----+---------+----------------------------------+ | 3|Baeldung |Building your REST API with Spring| +----+---------+----------------------------------+
Следующий тест подтвердит, что Jooq работал должным образом:
assertEquals(1, result.size()); assertEquals(Integer.valueOf(3), result.getValue(0, author.ID)); assertEquals("Baeldung", result.getValue(0, author.LAST_NAME)); assertEquals("Building your REST API with Spring", result.getValue(0, book.TITLE));
В случае сбоя генерируется исключение и транзакция откатывается, что мы подтверждаем тестом:
@Test(expected = DataAccessException.class) public void givenInvalidData_whenUpdating_thenFail() { dsl.update(authorBook) .set(authorBook.AUTHOR_ID, 4) .set(authorBook.BOOK_ID, 5) .execute(); }
5.3. Удаление Данных
Следующий метод удаляет некоторые данные:
dsl.delete(author) .where(author.ID.lt(3)) .execute();
Вот запрос на чтение затронутой таблицы:
Result> result = dsl .select(author.ID, author.FIRST_NAME, author.LAST_NAME) .from(author) .fetch();
Вывод запроса:
+----+----------+---------+ | ID|FIRST_NAME|LAST_NAME| +----+----------+---------+ | 3|Bryan |Basham | +----+----------+---------+
Следующий тест проверяет удаление:
assertEquals(1, result.size()); assertEquals("Bryan", result.getValue(0, author.FIRST_NAME)); assertEquals("Basham", result.getValue(0, author.LAST_NAME));
С другой стороны, если запрос недействителен, он вызовет исключение, и транзакция откатится. Следующий тест докажет, что:
@Test(expected = DataAccessException.class) public void givenInvalidData_whenDeleting_thenFail() { dsl.delete(book) .where(book.ID.equal(1)) .execute(); }
6. Заключение
В этом учебном пособии представлены основы Jooq, библиотеки Java для работы с базами данных. В нем описывались шаги по созданию исходного кода из структуры базы данных и способы взаимодействия с этой базой данных с помощью вновь созданных классов.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub .