78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率

总结:
SpringBoot 整合 jOOQ,作用是:可以根据数据库的数据(如:数据表),自动生成对应的一些代码,通过这些代码做sql查询,可以减少错误率。
如图:在写对应的sql查询时,使用到生成的表名、列名,可以减少书写错误
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第1张图片

★ jOOQ的特征:

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语句。

★ jOOQ 根据 数据库 生成 代码:

方法1: 直接使用命令行的方式来生成代码。

(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 配置文件

方法2:使用Maven项目来生成

(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   就可以了

代码演示:使用Maven项目来生成代码

pom.xml 文件配置
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第2张图片
plugin 插件
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第3张图片

可以直接点这个生成代码(还没尝试)
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第4张图片

或者这个:
双击 Ctrl ,输入 mvn generate-sources 命令,执行根据数据库生成对应代码的命令
这样就生成了对应数据库数据的代码
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第5张图片

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第6张图片

★ DSLContext

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

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所生成代码中的成员。

★ 类型安全的SQL语句

- 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语句,借助于编译器的检查。

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第7张图片

代码演示:

演示如何通过自动生成的代码,进行sql查询
上面已经通过pom.xml 配置文件生成对应的代码了,现在写Dao组件

之前写在domian的实体类,现在不用自己写了,都自动生成了
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第8张图片

UserDao

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第9张图片

UserDaoImpl

  • 增删改用 execute() -> 执行 sql 语句
  • 查用fetch() -> fetch 默认就是抓取查询到的所有列 或 fetchOne() -> fetchOne 只能抓取一条记录
  • selectFrom 就是把这个表的所有列都拿出来供我们查询,查所有列就用selectFrom
  • select 如果只要查询表中的几列数据,就用select
  • 使用DSLContext来引用数据库的表、列、视图、索引……一切数据库对象时,都不是直接用它们的名字。而是使用jOOQ所生成代码中的类或者成员

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第10张图片
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第11张图片
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第12张图片
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第13张图片

UserDaoTest

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第14张图片
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第15张图片
78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第16张图片

测试结果:
都通过了,而且查询出来的数据也很整齐

78、SpringBoot 整合 jOOQ --- 根据数据库表,自动生成代码,作用于减少写sql查询的错误率_第17张图片

★ jOOQ的高级配置

如果要对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的定制组件。

★ 配置Configuration(全面接管)

若要完全控制jOOQ的创建、配置过程,还可以直接在Spring容器中配置一个jOOQ的Configuration Bean,

该Configuration Bean将会完全取代Spring Boot自动配置的Configuration,

这样就取得了对jOOQ Configuration全部的控制权。

完整代码

UserDao

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

}

UserDaoImpl

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

UserDaoTest

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

pom.xml

<?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>

application.properties

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

你可能感兴趣的:(springboot,数据库,spring,boot,sql,JOOQ)