总结:
SpringBoot 整合 jOOQ,作用是:可以根据数据库的数据(如:数据表),自动生成对应的一些代码,通过这些代码做sql查询,可以减少错误率。
如图:在写对应的sql查询时,使用到生成的表名、列名,可以减少书写错误
JOOQ-------Java Object Oriented Query
jOOQ 是一个强大且灵活的【动态查询框架】,相当于 Specification 查询
Spring Data JPA 的 Specification 动态查询
jOOQ 完全不是 ORM 框架,它采用了数据库优先的设计,jOOQ 会根据底层数据库来生成大量Java代码,然后就能利用 jOOQ 的 流式API 构建【类型安全的SQL语句】。—>避免sql语句会写错的问题。
虽然 jOOQ 不是 ORM 框架,但它别出心裁设计、再加上代码生成的加持,使得它在简便性上不输给任何ORM框架;
——jOOQ最终查询返回的结果,也是对象。
与此同时,它提供的流式API又能构建灵活且强大的、类型安全的SQL语句。
(1)准备Java包:包括jOOQ的3个JAR包及其依赖包
(jooq-3.13.6.jar、jooq-meta-3.13.6.jar、jooq-codegen-3.13.6.jar、reactive-streams-1.0.3.jar)和数据库驱动包。
(2)准备一份配置文件,该配置文件用于指定数据库的连接信息(毕竟要根据数据库生成Java代码)及代码生成信息。
(3)在命令行窗口执行如下命令:
java -cp jar包 org.jooq.codegen.GenerationTool 配置文件
(1)修改pom.xml文件,在该文件的 元素中添加 配置插件
(jOOQ的代码生成器插件):
-- 插件坐标:添加 org.jooq:jooq-codegen-maven 插件
-- 元素将 jooq-codegen-maven 插件的 generate goal 绑定到
Maven 的 generate-sources Phase
-- 使用 元素 jooq-codegen-maven 插件指定配置信息:
指定数据库连接信息,及代码生成信息。
(2)执行如下命令:
命令:maven generate-sources
解释:让Maven执行到generate-sources阶段,
由于代码生成器插件的generate goal绑定到了generate-sources阶段,
因此这样就可让代码生成器插件的generate goal得到执行。
maven jooq-codegen:generate ——直接指定让Maven执行代码生成器插件的generate goal
实际上,推荐使用第一种方式,因为这种方式更符合Maven的用法习惯,
而且可以保证每次构建项目时都会重新去生成数据库对应的代码。
【说明:】 Spring Boot整合jOOQ时,不需要使用Spring Data。
就是添加的依赖是这个 spring-boot-starter-jooq 就可以了
或者这个:
双击 Ctrl ,输入 mvn generate-sources 命令,执行根据数据库生成对应代码的命令
这样就生成了对应数据库数据的代码
jOOQ 的 核心API 就是 DSLContext,它提供了大量流式API来拼接类型安全的SQL语句,从而操作数据库。
- SELECT子句 对应于 DSLContext的 select()方法。
- FROM子句 对应于DSLContex的 from()方法。
- 选出所有列,可直接用DSLContex的selectFrom()方法
- XXX JOIN子句对应于DSLContex的xxxJoin()方法。
- WHERE子句对应于where()方法,where()方法可以接受个数可变的参数,
这些参数代表了不同的多个过滤条件,多个连接条件是AND关系。
- GROUP BY子句对应于groupBy()方法。
- HAVING子句对应于having()方法。
- ORDER BY子句对应于orderBy方法,
orderBy()方法能接受个数可变的参数,这里的每个参数分别代表一个排序列。
- LIMIT...OFFSET子句对应于limit()、offset()方法。
- INSERT INTO...VALUES语句对应于DSLContex的insertInto()、values()方法。
- UPATE语句对应于DSLContex的update()方法,UPDATE语句中的SET子句则对应于set()方法。
- DELETE语句对应于DSLContex的delete()方法。
详细用法可参考https://www.jooq.org/doc/3.15/manual-single-page页面
【总结】:SQL语句中所用的子句,基本上和DSLContext对应的方法是具有相同名字——保证开发者无需查看文档即可使用。
只有当你对某个SQL子句与DSLContxt对应的方法不太确定时,你才去查询文档。
Spring Boot整合jOOQ之后,对jOOQ的帮助并不是特别大。
- jOOQ的核心API就是DSLContext。
- 当独立使用jOOQ时,程序需要自己获取DSLContext.
- 当jOOQ整合了Spring Boot之后,Spring Boot 会自动配置 DSLContext,
并且可以将它注入任何组件(主要还是DAO组件),这样该组件即可利用DSLContext来操作数据库了。
▲ 开发步骤:
(1)使用jOOQ的代码生成器来生成数据类,因此无需开发者自己写数据类。
每个数据表对应的数据类, 数据表名的驼峰写法 + Record.
此外,jOOQ还提供了Record2...Record22,用于封装包含2列……22列的数据行
数据类、以及Record2...Record22的toString()非常好看,输出的就像数据表格一样。
(2)定义DAO接口,其中数据类就使用第一步生成的数据类。
(3)使用DSLContext来实现DAO的实现类
使用DSLContext来引用数据库的表、列、视图、索引……一切数据库对象时,都不是直接用它们的名字。
而是使用jOOQ所生成代码中的成员。
- DSLContext要引用user_inf数据表时,它并不是直接写“user_inf”字符串,而是用Tables.USER_INF,
其中Tables就是jOOQ所生成的一个类,它代表了当前数据库中所有表的集合
——当前数据库中每一个表都是Tables类的一个成员变量,
因此程序通过Tables.USER_INF来引用user_inf表。
- 类似的,当程序要引用user_inf表的name列时,则通过Tables.USER_INF.NAME
- 这种形式的数据列提供了大量方法来对应各种SQL运算符,
如上面代码所用的 gt()方法对应“>”运算符、
lt()方法对应于“<”运算符、 between()、 and()方法 对应于 between. ..and 运算符。
通过使用jOOQ生成类来引用表、列即可保证生成类型安全的SQL语句,借助于编译器的检查。
演示如何通过自动生成的代码,进行sql查询
上面已经通过pom.xml 配置文件生成对应的代码了,现在写Dao组件
之前写在domian的实体类,现在不用自己写了,都自动生成了
测试结果:
都通过了,而且查询出来的数据也很整齐
如果要对jOOQ进行高级定制,可通过@Bean注解修饰实现了如下jOOQ接口的类:
- ConnectionProvider
- ExecutorProvider
- TransactionProvider
- RecordMapperProvider
- RecordUnmapperProvider
- Settings
- RecordListenerProvider
- ExecuteListenerProvider
- VisitListenerProvider
- TransactionListenerProvider
只要这些Bean被部署在Spring容器中,Spring Boot就会自动应用它们来创建、配置jOOQ的Configuration。
【总结】:是否整合Soring Boot,定制jOOQ方面所存在的区别是:只是注册这些定制器的方式不同。
如果你不整合Spring Boot,你就需要调用jOOQ自己的API来注册这些定制组件。
如果你整合Spring BOot,你只需要将这些定制组件部署成Spring容器中的Bean,Spring Boot就会将它们注册为JOOQ的定制组件。
若要完全控制jOOQ的创建、配置过程,还可以直接在Spring容器中配置一个jOOQ的Configuration Bean,
该Configuration Bean将会完全取代Spring Boot自动配置的Configuration,
这样就取得了对jOOQ Configuration全部的控制权。
package cn.ljh.app.dao;
import cn.ljh.app.generated.tables.records.UserInfRecord;
import org.jooq.Record2;
import java.util.List;
public interface UserDao
{
//增
int save(UserInfRecord user);
//删
int deleteById(Integer id);
//改
int update(UserInfRecord user);
//查
UserInfRecord findById(Integer id);
//根据名字模糊查询
List<UserInfRecord> findByNameLike(String namePattern);
//根据年龄大小进行范围查询
List<UserInfRecord> findByAgeGreaterThan(int startAge);
//根据年龄区间进行范围查询
List<UserInfRecord> findByAgeBetween(int startAge, int endAge);
//根据年龄范围修改名字
int updateNameByAge(String name, int startAge, int endAge);
//查 name 和 age 这两列数据
List<Record2<String , Integer>> findNameAndAgeById(Integer id);
}
package cn.ljh.app.dao.impl;
import cn.ljh.app.dao.UserDao;
import cn.ljh.app.generated.Tables;
import cn.ljh.app.generated.tables.records.UserInfRecord;
import org.jooq.*;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 增删改用 execute() -> 执行 sql 语句
* 查用fetch() -> fetch 默认就是抓取查询到的所有列 或 fetchOne() -> fetchOne 只能抓取一条记录
* selectFrom 就是把这个表的所有列都拿出来供我们查询,查所有列就用selectFrom
* select 如果只要查询表中的几列数据,就用select
* 使用DSLContext来引用数据库的表、列、视图、索引……一切数据库对象时,都不是直接用它们的名字。而是使用jOOQ所生成代码中的类或者成员
*/
@Repository
public class UserDaoImpl implements UserDao
{
//注入到spring容器
private final DSLContext create;
public UserDaoImpl(DSLContext create)
{
this.create = create;
}
//增
@Override
public int save(UserInfRecord user)
{
//Tables 表示数据库中的所有表, Tables.USER_INF表示往 USER_INF 这个表查记录
int execute = create.insertInto(Tables.USER_INF)
.values(user.getUserId(), user.getName(), user.getPassword(), user.getAge())
//执行 sql 语句
.execute();
return execute;
}
//删
@Override
public int deleteById(Integer id)
{
//选择deleteFrom删除功能,删除的数据是基于这张表Tables.USER_INF
int execute = create.deleteFrom(Tables.USER_INF)
//条件语句
.where(Tables.USER_INF.USER_ID.equal(id))
//执行sql语句
.execute();
return execute;
}
//改
@Override
public int update(UserInfRecord user)
{
//选择update修改的功能,修改数据的表是这张表Tables.USER_INF
int execute = create.update(Tables.USER_INF)
//设置值
.set(Tables.USER_INF.NAME, user.getName())
.set(Tables.USER_INF.PASSWORD, user.getPassword())
.set(Tables.USER_INF.AGE, user.getAge())
//条件语句
.where(Tables.USER_INF.USER_ID.eq(user.getUserId()))
//执行sql语句
.execute();
return execute;
}
//根据id查询
@Override
public UserInfRecord findById(Integer id)
{
//selectFrom 就是把这个表的所有列都拿出来供我们查询,也就是查询表中的所有列,查询某几个列可以用 select(xx列,xx列).from(xx表)
UserInfRecord one = create.selectFrom(Tables.USER_INF)
.where(Tables.USER_INF.USER_ID.eq(id))
//fetch 默认就是抓取查询到的所有列,
.fetchOne();//fetchOne 只能抓取一条记录
return one;
}
//根据名字模糊查询
@Override
public List<UserInfRecord> findByNameLike(String namePattern)
{
Result<UserInfRecord> fetch = create.selectFrom(Tables.USER_INF)
.where(Tables.USER_INF.NAME.like(namePattern))
.fetch();
return fetch;
}
//根据年龄大小查询
@Override
public List<UserInfRecord> findByAgeGreaterThan(int startAge)
{
Result<UserInfRecord> fetch = create.selectFrom(Tables.USER_INF)
.where(Tables.USER_INF.AGE.gt(startAge))
.fetch();
return fetch;
}
//根据年龄区间查询
@Override
public List<UserInfRecord> findByAgeBetween(int startAge, int endAge)
{
Result<UserInfRecord> fetch = create.selectFrom(Tables.USER_INF)
.where(Tables.USER_INF.AGE.between(startAge, endAge))
.fetch();
return fetch;
}
//根据年龄区间修改名字
@Override
public int updateNameByAge(String name, int startAge, int endAge)
{
int execute = create.update(Tables.USER_INF)
.set(Tables.USER_INF.NAME, name)
.where(Tables.USER_INF.AGE.between(startAge, endAge))
.execute();
return execute;
}
//查 name 和 age 这两列数据
@Override
public List<Record2<String, Integer>> findNameAndAgeById(Integer id)
{
//选择只查询name和age这两列数据
Result<Record2<String, Integer>> fetch = create
//通过select选择查询哪几列
.select(Tables.USER_INF.NAME, Tables.USER_INF.AGE)
//选择查询哪张表
.from(Tables.USER_INF)
//条件语句
.where(Tables.USER_INF.USER_ID.eq(id))
//执行查询方法
.fetch();
return fetch;
}
}
package cn.ljh.app;
import cn.ljh.app.dao.UserDao;
import cn.ljh.app.generated.tables.records.UserInfRecord;
import org.jooq.Record2;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class UserDaoTest
{
@Autowired
private UserDao userDao;
//添加user对象
@ParameterizedTest
@CsvSource({"ff,fff,2", "ee,ee,3"})
public void testSave(String name, String password, int age)
{
//没有id,save就是添加
int save = userDao.save(new UserInfRecord(null, name, password, age));
System.err.println(save);
}
//根据id删除用户对象
@ParameterizedTest
@ValueSource(ints = {20})
public void testDelete(Integer id)
{
userDao.deleteById(id);
}
//根据id修改对象
@ParameterizedTest
@CsvSource({"3,猪八戒aaa,xxx,222"})
public void testUpdate(Integer id, String name, String password, int age)
{
//有id,save就是修改
int update = userDao.update(new UserInfRecord(id, name, password, age));
System.err.println(update);
}
//根据id查询对象
@ParameterizedTest
@ValueSource(ints = {1})
public void testFindById(Integer id)
{
UserInfRecord user = userDao.findById(id);
System.err.println(user);
}
//根据名字模糊查询
@ParameterizedTest
@ValueSource(strings = {"孙%", "%精"})
public void testFindByNameLike(String namePattern)
{
List<UserInfRecord> users = userDao.findByNameLike(namePattern);
users.forEach(System.err::println);
}
//根据年龄大小进行范围查询
@ParameterizedTest
@ValueSource(ints = {500, 10})
public void testFindByAgeGreaterThan(int startAge)
{
List<UserInfRecord> users = userDao.findByAgeGreaterThan(startAge);
users.forEach(System.err::println);
}
//根据年龄区间进行范围查询
@ParameterizedTest
@CsvSource({"15,20", "500,1000"})
public void testFindByAgeBetween(int startAge, int endAge)
{
List<UserInfRecord> users = userDao.findByAgeBetween(startAge, endAge);
users.forEach(System.err::println);
}
//根据年龄范围修改名字
@ParameterizedTest
@CsvSource({"牛魔王ddxxx,800,1000"})
@Transactional
@Rollback(false)
public void testUpdateNameByAge(String name, int startAge, int endAge)
{
int i = userDao.updateNameByAge(name, startAge, endAge);
}
//查 name 和 age 这两列数据
@ParameterizedTest
@ValueSource(ints = {3,5})
public void testFindNameAndAgeById(Integer id)
{
List<Record2<String, Integer>> record2s = userDao.findNameAndAgeById(id);
record2s.forEach(System.err::println);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<groupId>cn.ljh</groupId>
<artifactId>jooq</artifactId>
<version>1.0.0</version>
<name>jooq</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- SpringBoot 整合 JOOQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>
<!-- jooq 代码生成器的依赖 -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.15.1</version>
<executions>
<execution>
<!-- 指定将代码生成器的 generate goal 绑定到
Maven 的生命周期的 generate-sources 阶段 -->
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<!-- 为代码生成器插件指定配置信息 -->
<configuration>
<!-- 数据库连接 -->
<jdbc>
<driver>com.mysql.cj.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC</url>
<user>root</user>
<password>123456</password>
</jdbc>
<!-- 指定代码生成器的参数 -->
<generator>
<database>
<!-- 指定数据库的类型 -->
<name>org.jooq.meta.mysql.MySQLDatabase</name>
<!-- 对哪些表来生成代码 -->
<includes>.*</includes>
<excludes></excludes>
<!-- 指定为哪个数据库生成代码 -->
<inputSchema>springboot</inputSchema>
</database>
<target>
<!--指定生成的源代码放在这里-->
<packageName>cn.ljh.app.generated</packageName>
<directory>src/main/java</directory>
</target>
</generator>
</configuration>
</plugin>
</plugins>
</build>
</project>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
# 如果想看到SQL语句输出,需要将Mapper组件的日志级别设置为debug
logging.level.cn.ljh.app.dao=debug