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

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

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

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文件,在该文件的 <plugins…/> 元素中添加 <plugin…/> 配置插件

jOOQ的代码生成器插件:

     -- 插件坐标:添加  org.jooq:jooq-codegen-maven  插件
     
     -- <executions.../>元素将  jooq-codegen-maven  插件的  generate  goal  绑定到
         Maven  的  generate-sources  Phase
         
     -- 使用 <configuration.../> 元素  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。

就是添加的依赖是这个 < artifactId > spring-boot-starter-jooq < /artifactId > 就可以了

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

pom.xml 文件配置

在这里插入图片描述

plugin 插件

在这里插入图片描述

可以直接点这个生成代码(还没尝试)

在这里插入图片描述

或者这个:
双击 Ctrl ,输入 mvn generate-sources 命令,执行根据数据库生成对应代码的命令
这样就生成了对应数据库数据的代码
在这里插入图片描述

在这里插入图片描述

DSLContext(是 jOOQ 的 核心API)

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

在这里插入图片描述

代码演示:

演示如何通过自动生成的代码,进行sql查询

上面已经通过pom.xml 配置文件生成对应的代码了,现在写Dao组件

之前写在domian的实体类,现在不用自己写了,都自动生成了
在这里插入图片描述

UserDao

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

UserDaoTest

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试结果:

都通过了,而且查询出来的数据也很整齐

在这里插入图片描述

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_L_J_H_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值