淺析Java數(shù)據(jù)庫操作工具包jOOQ的使用
jOOQ 是一個(gè)輕量級(jí)的 Java ORM(對(duì)象關(guān)系映射)框架,可用來構(gòu)建復(fù)雜的 SQL 查詢。jOOQ 可以根據(jù)數(shù)據(jù)庫表自動(dòng)生成對(duì)應(yīng)的 Java 類,且字段類型與數(shù)據(jù)庫一一對(duì)應(yīng),減少了 SQL 注入的風(fēng)險(xiǎn)。
本文即是對(duì) jOOQ 的初探,包括四個(gè)部分:準(zhǔn)備數(shù)據(jù)庫和測(cè)試數(shù)據(jù)、jOOQ Java 代碼生成、jOOQ 初步使用,以及 jOOQ 與 Spring Boot 的集成。
開始各個(gè)部分前,列出本文涉及的各軟件版本:
Java:20(BellSoft LibericaJDK)
Maven:3.9.2
MySQL:8.1.0
jOOQ:3.18.6
Spring Boot:3.1.3
1 準(zhǔn)備數(shù)據(jù)庫、表和測(cè)試數(shù)據(jù)
探索 jOOQ 的使用之前,需要有一個(gè)數(shù)據(jù)庫和幾張表。學(xué)生課程系統(tǒng)就是一個(gè)不錯(cuò)的業(yè)務(wù)場(chǎng)景,既接近實(shí)際又涉及連表等復(fù)雜查詢,很適合用來作演示學(xué)習(xí)。
本文為學(xué)生課程系統(tǒng)創(chuàng)建了一個(gè) school 數(shù)據(jù)庫,并在其下創(chuàng)建了三張表 student(學(xué)生表)、course(課程表)和 score(成績(jī)表)。
如下為完整的建庫、建表和數(shù)據(jù)插入語句:
-- 創(chuàng)建數(shù)據(jù)庫 school DROP DATABASE IF EXISTS school; CREATE DATABASE school DEFAULT CHARSET utf8 COLLATE utf8_general_ci; -- 使用數(shù)據(jù)庫 school USE school; -- 創(chuàng)建學(xué)生表 DROP TABLE IF EXISTS student; CREATE TABLE student ( no INT NOT NULL, -- 編號(hào) name VARCHAR(20) NOT NULL, -- 姓名 gender ENUM('男', '女') NOT NULL, -- 性別 birthday DATETIME, -- 出生日期 CONSTRAINT PRIMARY KEY (no) -- 編號(hào)為主鍵 ); -- 為學(xué)生表插入數(shù)據(jù) INSERT INTO student VALUES (1, '閆浩然', '男', '1999-09-01'), (2, '肖雪', '女', '2000-03-21'), (3, '張如意', '女', '2001-08-08'); -- 創(chuàng)建課程表 DROP TABLE IF EXISTS course; CREATE TABLE course ( no INT NOT NULL, -- 編號(hào) name VARCHAR(20) NOT NULL, -- 名稱 CONSTRAINT PRIMARY KEY (no) -- 編號(hào)為主鍵 ); -- 為課程表插入數(shù)據(jù) INSERT INTO course VALUES (1, '語文'), (2, '數(shù)學(xué)'), (3, '英語'); -- 創(chuàng)建成績(jī)表 DROP TABLE IF EXISTS score; CREATE TABLE score ( student_no INT NOT NULL, -- 學(xué)生編號(hào) course_no INT NOT NULL, -- 課程編號(hào) degree DECIMAL(4, 1) NOT NULL, -- 分?jǐn)?shù) CONSTRAINT PRIMARY KEY (student_no, course_no), -- 學(xué)生編號(hào)與課程編號(hào)為聯(lián)合主鍵 CONSTRAINT FOREIGN KEY (student_no) REFERENCES student(no), -- 學(xué)生編號(hào)為外鍵 CONSTRAINT FOREIGN KEY (course_no) REFERENCES course(no) -- 課程編號(hào)為外鍵 ); -- 為成績(jī)表插入數(shù)據(jù) INSERT INTO score VALUES (1, 1, 90.5), (1, 2, 88.0), (1, 3, 98.0), (2, 1, 78.5), (2, 2, 68.0), (2, 3, 93.0), (3, 1, 83.0), (3, 2, 94.5), (3, 3, 73.0);
2 jOOQ Java 代碼生成
該部分嘗試用 jOOQ Maven 插件(jooq-codegen-maven
)的方式來生成 Java 代碼。
本文使用的是在本地搭建的 MySQL 數(shù)據(jù)庫,將第一部分的 SQL 語句在數(shù)據(jù)庫執(zhí)行后,即可以嘗試使用 jOOQ Maven 插件來生成 Java 代碼了(主要是表相關(guān)的 Java 類和 POJO 類)。
插件jooq-codegen-maven
在 Maven 配置文件pom.xml
中的配置信息如下:
<plugin> <groupId>org.jooq</groupId> <artifactId>jooq-codegen-maven</artifactId> <version>${jooq.version}</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <jdbc> <driver>com.mysql.cj.jdbc.Driver</driver> <url>jdbc:mysql://localhost:3306/school</url> <user>root</user> <password>root</password> </jdbc> <generator> <generate> <pojos>true</pojos> </generate> <database> <includes>.*</includes> <inputSchema>school</inputSchema> </database> <target> <packageName>com.leileiluoluo.jooq.model.generated</packageName> <directory>src/main/java</directory> </target> </generator> </configuration> </plugin>
然后,使用如下命令生成 Java 代碼:
mvn clean generate-sources
可以看到,代碼被生成到了src/main/java
文件夾下的com.leileiluoluo.jooq.model.generated
包下。
3 jOOQ 初步使用
使用 jOOQ 的一個(gè)主要目的可能是想借力其豐富的 SQL 構(gòu)造能力。
下面即會(huì)使用 jOOQ 以及在第二部分生成的 Java 代碼(主要是表相關(guān)的類和 POJO 類)來實(shí)現(xiàn)一些常用的查詢。
如下即是使用 jOOQ 來查詢所有 Student 的一段示例代碼:
import com.leileiluoluo.jooq.model.generated.tables.pojos.Student; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.impl.DSL; import java.sql.Connection; import java.sql.DriverManager; import java.util.List; import static com.leileiluoluo.jooq.model.generated.Tables.STUDENT; public class JOOQSimpleQueryTest { public static void main(String[] args) { String username = "root"; String password = "root"; String url = "jdbc:mysql://localhost:3306/school"; try (Connection conn = DriverManager.getConnection(url, username, password)) { DSLContext context = DSL.using(conn, SQLDialect.MYSQL); List<Student> students = context.selectFrom(STUDENT) .fetchInto(Student.class); students.forEach(student -> { System.out.printf("no: %s, name: %s, gender: %s, birthday: %s\n", student.getNo(), student.getName(), student.getGender(), student.getBirthday()); }); } catch (Exception e) { e.printStackTrace(); } } }
可以看到,上面這段代碼首先使用DriverManager.getConnection(url, username, password);
來創(chuàng)建了一個(gè)數(shù)據(jù)庫連接;然后使用DSL.using(conn, SQLDialect.MYSQL);
來創(chuàng)建了DSLContext
對(duì)象;然后即可以用DSLContext
來像寫 SQL 語句一樣(context.selectFrom(STUDENT).fetchInto(Student.class);
)來拼裝查詢語句了,查詢結(jié)果會(huì)自動(dòng)轉(zhuǎn)換為 POJO 類的類型,非常方便快捷。
程序運(yùn)行結(jié)果如下:
no: 1, name: 閆浩然, gender: 男, birthday: 1999-09-01T00:00
no: 2, name: 肖雪, gender: 女, birthday: 2000-03-21T00:00
no: 3, name: 張如意, gender: 女, birthday: 2001-08-08T00:00
上面的示例針對(duì)的是單表查詢的情形,下面再看一下復(fù)雜查詢的拼裝:
DSLContext context = DSL.using(conn, SQLDialect.MYSQL); List<Record3<String, String, BigDecimal>> studentCourseScores = context.select( STUDENT.NAME, COURSE.NAME, SCORE.DEGREE ).from(SCORE) .join(STUDENT).on(SCORE.STUDENT_NO.eq(STUDENT.NO)) .join(COURSE).on(SCORE.COURSE_NO.eq(COURSE.NO)) .fetch(); studentCourseScores.forEach(record -> { String studentName = record.getValue(STUDENT.NAME); String courseName = record.getValue(COURSE.NAME); BigDecimal degree = record.getValue(SCORE.DEGREE); System.out.printf("student: %s, course: %s, degree: %s\n", studentName, courseName, degree); });
上面的查詢涉及三個(gè)表的連接,依然可以像寫 SQL 一樣來進(jìn)行構(gòu)造。
程序運(yùn)行結(jié)果如下:
student: 張如意, course: 語文, degree: 83.0
student: 肖雪, course: 語文, degree: 78.5
student: 閆浩然, course: 語文, degree: 90.5
student: 張如意, course: 數(shù)學(xué), degree: 94.5
student: 肖雪, course: 數(shù)學(xué), degree: 68.0
student: 閆浩然, course: 數(shù)學(xué), degree: 88.0
student: 張如意, course: 英語, degree: 73.0
student: 肖雪, course: 英語, degree: 93.0
student: 閆浩然, course: 英語, degree: 98.0
其對(duì)應(yīng)的 SQL 語句如下:
SELECT s.name, c.name, sc.degree FROM score sc JOIN student s ON sc.student_no=s.no JOIN course c ON sc.course_no=c.no;
通過這兩段示例程序,即可以看到 jOOQ 的使用非常的簡(jiǎn)單。針對(duì)單表的查詢,可以直接將結(jié)果映射到 POJO 類;對(duì)于多表連接等復(fù)雜查詢,拼裝起來也并不復(fù)雜,且結(jié)果可以轉(zhuǎn)換為一個(gè)多值的類RecordN<?, ?, ?, ...>
。
4 jOOQ 與 Spring Boot 的集成
第三部分的示例僅適用于本地測(cè)試的情形,對(duì)于實(shí)際的項(xiàng)目,還需要考慮其如何與框架進(jìn)行集成。
該部分即會(huì)探索 jOOQ 與 Spring Boot 的集成,主要會(huì)探索兩個(gè)方面:DSLContext
的自動(dòng)創(chuàng)建、DAO 層的封裝。
4.1 DSLContext 的自動(dòng)創(chuàng)建
在 Spring Boot 中使用 jOOQ 時(shí),DSLContext
如何進(jìn)行創(chuàng)建,這些交給spring-boot-starter-jooq
就可以了,我們依然在application.xml
采用通用的數(shù)據(jù)庫配置即可,DSLContext
會(huì)由 Spring 容器自動(dòng)創(chuàng)建,我們只需在需要的地方進(jìn)行自動(dòng)注入就可以了。
# application.yaml spring: datasource: url: jdbc:mysql://localhost:3306/school username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver
// StudentDao.java @Service public class StudentDaoImpl implements StudentDao { @Autowired private DSLContext context; }
4.2 DAO 層的封裝
雖然 jOOQ 也支持自動(dòng)生成 DAO 層,但其生成的 DAO 層代碼比較泛化,有很多方法可能根本就用不著。所以,經(jīng)過調(diào)研后,本人決定僅使用其構(gòu)建 SQL 的能力(以及自動(dòng)生成的表相關(guān)的類和 POJO 類),DAO 層還是根據(jù)業(yè)務(wù)情形自己來實(shí)現(xiàn)比較好一些。
如下即是為 Student 查詢?cè)O(shè)計(jì)的 StudentDao 的示例代碼:
// StudentDao.java package com.leileiluoluo.jooq.dao; import com.leileiluoluo.jooq.model.generated.tables.pojos.Student; import java.util.List; import java.util.Optional; public interface StudentDao { Integer countAll(); List<Student> listAll(); List<Student> listWithPagination(int offset, int limit); Optional<Student> getByNo(Integer no); void save(Student record); void update(Student record); void deleteByNo(Integer no); }
// StudentDaoImpl.java package com.leileiluoluo.jooq.dao.impl; import com.leileiluoluo.jooq.dao.StudentDao; import com.leileiluoluo.jooq.model.generated.tables.pojos.Student; import org.jooq.DSLContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; import static com.leileiluoluo.jooq.model.generated.Tables.STUDENT; @Service public class StudentDaoImpl implements StudentDao { @Autowired private DSLContext context; @Override public Integer countAll() { return context.fetchCount(STUDENT); } @Override public List<Student> listAll() { return context.selectFrom(STUDENT) .fetchInto(Student.class); } @Override public List<Student> listWithPagination(int offset, int limit) { return context.selectFrom(STUDENT) .offset(offset) .limit(limit) .fetchInto(Student.class); } @Override public Optional<Student> getByNo(Integer no) { Student student = context.select() .from(STUDENT) .where(STUDENT.NO.eq(no)) .fetchOneInto(Student.class); return Optional.ofNullable(student); } @Override public void save(Student student) { context.insertInto(STUDENT) .set(STUDENT.NO, student.getNo()) .set(STUDENT.NAME, student.getName()) .set(STUDENT.GENDER, student.getGender()) .set(STUDENT.BIRTHDAY, student.getBirthday()) .execute(); } @Override public void update(Student student) { context.update(STUDENT) .set(STUDENT.NAME, student.getName()) .set(STUDENT.GENDER, student.getGender()) .set(STUDENT.BIRTHDAY, student.getBirthday()) .where( STUDENT.NO.eq(student.getNo()) ) .execute(); } @Override public void deleteByNo(Integer no) { context.deleteFrom(STUDENT) .where( STUDENT.NO.eq(no) ).execute(); } }
可以看到,增、刪、改、查都有了,基本滿足了實(shí)際業(yè)務(wù)中的需要;在其上設(shè)計(jì) Service 和 Controller 即可以實(shí)現(xiàn)真實(shí)的 REST 業(yè)務(wù)需求了。
綜上,本文準(zhǔn)備了一些測(cè)試數(shù)據(jù),探索了 jOOQ 的代碼生成和 SQL 構(gòu)建能力,最后還思考了其與 Spring Boot 的集成??傮w來看,jOOQ 還是比較易用的,是一個(gè)不錯(cuò)的 MyBatis 或 Hibernate 替代方案。
以上就是淺析Java數(shù)據(jù)庫操作工具包jOOQ的使用的詳細(xì)內(nèi)容,更多關(guān)于Java jOOQ數(shù)據(jù)庫操作工具包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java serialVersionUID解決序列化類版本不一致問題面試精講
這篇文章主要為大家介紹了serialVersionUID解決序列化類版本不一致問題的面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Maven項(xiàng)目web多圖片上傳及格式驗(yàn)證的實(shí)現(xiàn)
本文主要介紹了Maven項(xiàng)目web多圖片上傳及格式驗(yàn)證的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10MyBatis 延遲加載、一級(jí)緩存、二級(jí)緩存(詳解)
下面小編就為大家?guī)硪黄狹yBatis 延遲加載、一級(jí)緩存、二級(jí)緩存(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08SpringBoot中優(yōu)化if-else語句的七種方法
if-else語句是控制流程的基本工具,但過度使用會(huì)使代碼變得復(fù)雜且難以維護(hù),在SpringBoot , SpringCloud項(xiàng)目中,優(yōu)化if-else結(jié)構(gòu)變得尤為重要,本文將深入探討七種策略,旨在減少SpringBoot , SpringCloud項(xiàng)目中 if-else的使用,需要的朋友可以參考下2024-07-07Java多線程Thread基礎(chǔ)學(xué)習(xí)
每一個(gè)正在執(zhí)行的程序都是一個(gè)進(jìn)程,資源只有一塊,所以在同一時(shí)間段會(huì)有多個(gè)程序同時(shí)執(zhí)行,但是在一個(gè)時(shí)間點(diǎn)上,只能由一個(gè)程序執(zhí)行,多線程是在一個(gè)進(jìn)程的基礎(chǔ)之上的進(jìn)一步劃分,需要的朋友可以參考下2023-04-04Java找出兩個(gè)大數(shù)據(jù)量List集合中的不同元素的方法總結(jié)
本文將帶大家了解如何快速的找出兩個(gè)相似度非常高的List集合里的不同元素。主要通過Java API、List集合雙層遍歷比較不同、借助Map集合查找三種方式,需要的可以參考一下2022-10-10