Pengenalan Jooq dengan Spring

1. Gambaran keseluruhan

Artikel ini akan memperkenalkan Jooq Object Oriented Querying - Jooq - dan cara mudah untuk membuatnya dengan kerjasama Spring Framework.

Sebilangan besar aplikasi Java mempunyai semacam ketekunan SQL dan mengakses lapisan itu dengan bantuan alat peringkat tinggi seperti JPA. Dan walaupun itu berguna, dalam beberapa kes, anda benar-benar memerlukan alat yang lebih halus dan bernuansa untuk mendapatkan data anda atau untuk benar-benar memanfaatkan semua yang ditawarkan DB yang mendasari.

Jooq mengelakkan beberapa corak ORM khas dan menghasilkan kod yang membolehkan kita membuat pertanyaan jenis yang selamat, dan mendapatkan kawalan penuh terhadap SQL yang dihasilkan melalui API fasih yang bersih dan kuat.

Artikel ini memberi tumpuan kepada Spring MVC. Artikel kami Spring Boot Support untuk jOOQ menerangkan cara menggunakan jOOQ di Spring Boot.

2. Pergantungan Maven

Pergantungan berikut diperlukan untuk menjalankan kod dalam tutorial ini.

2.1. jOOQ

 org.jooq jooq 3.2.14 

2.2. Musim bunga

Terdapat beberapa pergantungan Spring yang diperlukan untuk contoh kita; namun, untuk mempermudah sesuatu, kami hanya perlu memasukkan dua daripadanya dalam fail POM:

 org.springframework spring-context 5.2.2.RELEASE   org.springframework spring-jdbc 5.2.2.RELEASE 

2.3. Pangkalan data

Untuk mempermudah contohnya, kami akan menggunakan pangkalan data tertanam H2:

 com.h2database h2 1.4.191 

3. Penjanaan Kod

3.1. Struktur Pangkalan Data

Mari memperkenalkan struktur pangkalan data yang akan kami bekerjasama sepanjang artikel ini. Anggaplah kita perlu membuat pangkalan data untuk penerbit untuk menyimpan maklumat buku dan pengarang yang mereka kelola, di mana pengarang boleh menulis banyak buku dan buku mungkin ditulis bersama oleh banyak pengarang.

Untuk menjadikannya mudah, kami akan menghasilkan hanya tiga jadual: buku untuk buku, pengarang untuk pengarang, dan satu lagi jadual yang disebut author_book untuk mewakili hubungan antara penulis dan buku. Yang penulis jadual mempunyai tiga tiang: id , first_name , dan last_name. Jadual buku hanya mengandungi lajur tajuk dan kunci utama id .

Pertanyaan SQL berikut, yang disimpan dalam fail sumber intro_schema.sql , akan dijalankan terhadap pangkalan data yang telah kami siapkan sebelumnya untuk membuat jadual yang diperlukan dan mengisi dengan data sampel:

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. Properties Maven Plugin

Kami akan menggunakan tiga plugin Maven yang berbeza untuk menghasilkan kod Jooq. Yang pertama adalah plugin Properties Maven.

Plugin ini digunakan untuk membaca data konfigurasi dari fail sumber. Ia tidak diperlukan kerana data dapat ditambahkan secara langsung ke POM, tetapi adalah baik untuk menguruskan harta secara luaran.

Di bahagian ini, kami akan menentukan sifat untuk sambungan pangkalan data, termasuk kelas pemacu JDBC, URL pangkalan data, nama pengguna, dan kata laluan, dalam fail bernama intro_config.properties . Mengecualikan sifat ini memudahkan menukar pangkalan data atau mengubah data konfigurasi.

Objektif baca-projek-sifat plugin ini harus terikat pada fasa awal sehingga data konfigurasi dapat disiapkan untuk digunakan oleh pemalam lain. Dalam kes ini, ia terikat pada fasa inisialisasi :

 org.codehaus.mojo properties-maven-plugin 1.0.0   initialize  read-project-properties    src/main/resources/intro_config.properties     

3.3. Pemalam SQL Maven

Plugin SQL Maven digunakan untuk melaksanakan pernyataan SQL untuk membuat dan mengisi jadual pangkalan data. Ia akan memanfaatkan sifat-sifat yang telah diekstrak dari fail intro_config.properties oleh plugin Properties Maven dan mengambil pernyataan SQL dari sumber intro_schema.sql .

Plugin SQL Maven dikonfigurasi seperti di bawah:

 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   

Perhatikan bahawa plugin ini mesti diletakkan lebih lama daripada plugin Properties Maven dalam fail POM kerana tujuan pelaksanaannya terikat pada fasa yang sama, dan Maven akan melaksanakannya mengikut urutan yang disenaraikan.

3.4. jOOQ Codegen Plugin

Jooq Codegen Plugin menghasilkan kod Java dari struktur jadual pangkalan data. Its menjana matlamat perlu terikat kepada menjana-sumber fasa untuk memastikan susunan yang betul pelaksanaan. Metadata pemalam kelihatan seperti berikut:

 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. Menjana Kod

Untuk menyelesaikan proses penghasilan kod sumber, kita perlu menjalankan fasa sumber penjanaan Maven . Di Eclipse, kita dapat melakukan ini dengan mengklik kanan projek dan memilih Run As -> Maven menghasilkan-sumber . Selepas perintah itu selesai, fail sumber yang sepadan dengan pengarang , buku , author_book jadual (dan beberapa orang yang lain untuk menyokong kelas) dihasilkan.

Mari kita gali kelas meja untuk melihat apa yang dihasilkan oleh Jooq. Setiap kelas mempunyai bidang statik dengan nama yang sama dengan kelas, kecuali semua huruf dalam huruf besarnya ditulis. Berikut adalah coretan kod yang diambil dari definisi kelas yang dihasilkan:

The Pengarang kelas:

public class Author extends TableImpl { public static final Author AUTHOR = new Author(); // other class members }

The Book class:

public class Book extends TableImpl { public static final Book BOOK = new Book(); // other class members }

The AuthorBook class:

public class AuthorBook extends TableImpl { public static final AuthorBook AUTHOR_BOOK = new AuthorBook(); // other class members }

The instances referenced by those static fields will serve as data access objects to represent the corresponding tables when working with other layers in a project.

4. Spring Configuration

4.1. Translating jOOQ Exceptions to Spring

In order to make exceptions thrown from Jooq execution consistent with Spring support for database access, we need to translate them into subtypes of the DataAccessException class.

Let's define an implementation of the ExecuteListener interface to convert exceptions:

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())); } }

This class will be used by the Spring application context.

4.2. Configuring Spring

This section will go through steps to define a PersistenceContext that contains metadata and beans to be used in the Spring application context.

Let's get started by applying necessary annotations to the class:

  • @Configuration: Make the class to be recognized as a container for beans
  • @ComponentScan: Configure scanning directives, including the value option to declare an array of package names to search for components. In this tutorial, the package to be searched is the one generated by the Jooq Codegen Maven plugin
  • @EnableTransactionManagement: Enable transactions to be managed by Spring
  • @PropertySource: Indicate the locations of the properties files to be loaded. The value in this article points to the file containing configuration data and dialect of the database, which happens to be the same file mentioned in subsection 4.1.
@Configuration @ComponentScan({"com.baeldung.Jooq.introduction.db.public_.tables"}) @EnableTransactionManagement @PropertySource("classpath:intro_config.properties") public class PersistenceContext { // Other declarations }

Next, use an Environment object to get the configuration data, which is then used to configure the DataSource bean:

@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; }

Now we define several beans to work with database access operations:

@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()); }

Finally, we provide a Jooq Configuration implementation and declare it as a Spring bean to be used by the DSLContext class:

@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. Using jOOQ With Spring

This section demonstrates the use of Jooq in common database access queries. There are two tests, one for commit and one for rollback, for each type of “write” operation, including inserting, updating, and deleting data. The use of “read” operation is illustrated when selecting data to verify the “write” queries.

We will begin by declaring an auto-wired DSLContext object and instances of Jooq generated classes to be used by all testing methods:

@Autowired private DSLContext dsl; Author author = Author.AUTHOR; Book book = Book.BOOK; AuthorBook authorBook = AuthorBook.AUTHOR_BOOK;

5.1. Inserting Data

The first step is to insert data into tables:

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 SELECT query to extract data:

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();
    

The above query produces the following output:

+----+---------+-----+ | ID|LAST_NAME|count| +----+---------+-----+ | 1|Sierra | 2| | 2|Bates | 1| | 4|Schildt | 1| +----+---------+-----+

The result is confirmed by the Assert API:

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()));

When a failure occurs due to an invalid query, an exception is thrown and the transaction rolls back. In the following example, the INSERT query violates a foreign key constraint, resulting in an exception:

@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. Updating Data

Now let's update the existing data:

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();

Get the necessary data:

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();
    

The output should be:

+----+---------+----------------------------------+ | ID|LAST_NAME|TITLE | +----+---------+----------------------------------+ | 3|Baeldung |Building your REST API with Spring| +----+---------+----------------------------------+

The following test will verify that Jooq worked as expected:

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));

In case of a failure, an exception is thrown and the transaction rolls back, which we confirm with a test:

@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. Deleting Data

The following method deletes some data:

dsl.delete(author) .where(author.ID.lt(3)) .execute();

Here is the query to read the affected table:

Result
    
      result = dsl .select(author.ID, author.FIRST_NAME, author.LAST_NAME) .from(author) .fetch();
    

The query output:

+----+----------+---------+ | ID|FIRST_NAME|LAST_NAME| +----+----------+---------+ | 3|Bryan |Basham | +----+----------+---------+

The following test verifies the deletion:

assertEquals(1, result.size()); assertEquals("Bryan", result.getValue(0, author.FIRST_NAME)); assertEquals("Basham", result.getValue(0, author.LAST_NAME));

On the other hand, if a query is invalid, it will throw an exception and the transaction rolls back. The following test will prove that:

@Test(expected = DataAccessException.class) public void givenInvalidData_whenDeleting_thenFail() { dsl.delete(book) .where(book.ID.equal(1)) .execute(); }

6. Conclusion

Tutorial ini memperkenalkan asas-asas Jooq, perpustakaan Java untuk bekerja dengan pangkalan data. Ini merangkumi langkah-langkah untuk menghasilkan kod sumber dari struktur pangkalan data dan bagaimana berinteraksi dengan pangkalan data itu menggunakan kelas yang baru dibuat.

Pelaksanaan semua contoh dan coretan kod ini terdapat dalam projek GitHub.