jooq实践

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

用法

  sql语句

复制代码
SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(*)
    FROM AUTHOR
    JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID
   WHERE BOOK.LANGUAGE = 'DE'
     AND BOOK.PUBLISHED > DATE '2008-01-01'
GROUP BY AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME
  HAVING COUNT(*) > 5
ORDER BY AUTHOR.LAST_NAME ASC NULLS FIRST
   LIMIT 2
  OFFSET 1
复制代码

  java代码

复制代码
dsl.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count())
      .from(AUTHOR)
      .join(BOOK).on(AUTHOR.ID.equal(BOOK.AUTHOR_ID))
      .where(BOOK.LANGUAGE.eq("DE"))
      .and(BOOK.PUBLISHED.gt(date("2008-01-01")))
      .groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
      .having(count().gt(5))
      .orderBy(AUTHOR.LAST_NAME.asc().nullsFirst())
      .limit(2)
      .offset(1)
复制代码

 

示例代码

复制代码
增:
public void addMerchantUrgentIsochrone(ITbMerchantUrgentIsochrone merchantUrgentIsochrone) {
    TbMerchantUrgentIsochroneRecord record = dsl.newRecord(TB_MERCHANT_URGENT_ISOCHRONE, merchantUrgentIsochrone);
    record.store();
    merchantUrgentIsochrone.setId(record.getId());
}
删:
public int deleteRecords() {
    return dsl.delete(TB_MERCHANT_URGENT_ISOCHRONE).where(TB_MERCHANT_URGENT_ISOCHRONE.CREATED_AT.ge(Timestamp.valueOf(LocalDateTime.now().minusDays(1)))).execute();
}
改:
public int update(ITbUserArea userArea) {
    return dsl.update(TB_USER_AREA)
            .set(TB_USER_AREA.GEOHASH, userArea.getGeohash())
            .set(TB_USER_AREA.AREA, userArea.getArea())
            .set(TB_USER_AREA.CITY_ID, userArea.getCityId())
            .set(TB_USER_AREA.ORDER_COUNT, userArea.getOrderCount())
            .where(TB_USER_AREA.ID.eq(userArea.getId()))
            .execute();
}
查:
public List<ITbUserArea> getAreas(String preHash) {
    return dsl.selectFrom(TB_USER_AREA)
            .where(TB_USER_AREA.GEOHASH.like(preHash+"%"))
            .fetchInto(TbUserArea.class);
}
复制代码

jooq 可以执行sql语句

Result<Record> fetch(String var1) throws DataAccessException;
int execute(String var1) throws DataAccessException;

配置篇

  maven配置

复制代码
Maven依赖:(版本号可配)
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq</artifactId>
  <version>3.9.5</version>
</dependency>
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.9.5</version>
</dependency>
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.9.5</version>
</dependency>
 
工具生成映射配置:
建立 xxx.xml(名字任意)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
    <jdbc>
        <driver>...</driver>
        <url>...</url>
        <user>...</user>
        <password>...</password>
    </jdbc>
    <generator>
        <!-- 指定代码风格
            - org.jooq.util.ScalaGenerator
            - org.jooq.util.JavaGenerator
         -->
        <name>org.jooq.util.JavaGenerator</name>
 
        <database>
            <!-- 数据库类型 -->
            <name>org.jooq.util.postgres.PostgresDatabase</name>
            <!-- <name>org.jooq.util.mysql.MySQLDatabase</name> -->
            <excludes>
                <!-- 对tb_table1,tb_table2,tb_table3 表不生成代码-->
                tb_table1|tb_table2|tb_table3
            </excludes>
            <includeExcludeColumns>true</includeExcludeColumns>
            <inputSchema>public</inputSchema>
 
            <!-- jooq转换对象类型,如用enum代替int
                目标转换类型应实现org.jooq.Converter或org.jooq.Binding接口
                相应的配置标签为<customType>,<forcedType>
                参考地址: https://www.jooq.org/doc/3.9/manual/code-generation/custom-data-types/
                https://www.jooq.org/doc/3.9/manual/code-generation/custom-data-type-bindings/
             -->
            <customTypes>
                <customType>
                    <name>EffectiveStatus</name>
                    <type>xxx.isochrone.constant.EffectiveStatus</type>
                    <converter>xxx.geo.jooq.converter.EffectiveStatusConverter</converter>
                </customType>
            </customTypes>
            <forcedTypes>
                <!-- 使用converter -->
                <forcedType>
                    <name>EffectiveStatus</name>
                    <expressions>.*\.tb_isochrone_audit_info\.effective_status</expressions>
                    <types>.*</types>
                </forcedType>
                <!-- 使用binding -->
                <forcedType>
                    <userType>xxx.isochrone.pojos.GeographyPolygon</userType>
                    <binding>xxx.geo.jooq.binding.PGgeometryPolygonBinding</binding>
                    <expression>.*\.tb_isochrone.range|.*\.tb_merchant_area_isochrone.range</expression>
                    <types>.*</types>
                </forcedType>
            </forcedTypes>
        </database>
 
        <generate>
            <deprecated>false</deprecated>
            <daos>true</daos>
            <interfaces>true</interfaces>
        </generate>
        <target>
            <!-- 生成的包名,生成的类在此包下 -->
            <packageName>xxx.isochrone.jooq</packageName>
            <!-- 输出的目录 -->
            <directory>src/main/java</directory>
        </target>
    </generator>
</configuration>
 
相应pom.xml里添加对应的配置信息
<profiles>
    <profile>
        <id>db-gen</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.4.0</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>java</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <includeProjectDependencies>false</includeProjectDependencies>
                        <includePluginDependencies>true</includePluginDependencies>
                        <mainClass>org.jooq.util.GenerationTool</mainClass>
                        <cleanupDaemonThreads>false</cleanupDaemonThreads>
                        <arguments>
                            <!-- 匹配工具生成映射配置文件 -->
                            <argument>xxx.xml</argument>
                        </arguments>
                    </configuration>
                    <dependencies>
                        <dependency>
                          <groupId>org.jooq</groupId>
                          <artifactId>jooq</artifactId>
                          <version>3.9.5</version>
                        </dependency>
                        <dependency>
                            <groupId>org.jooq</groupId>
                            <artifactId>jooq-codegen</artifactId>
                            <version>3.9.5</version>
                        </dependency>
                        <dependency>
                            <groupId>org.jooq</groupId>
                            <artifactId>jooq-meta</artifactId>
                            <version>3.9.5</version>
                        </dependency>
                        <dependency>
                            <groupId>org.postgresql</groupId>
                            <artifactId>postgresql</artifactId>
                            <version>9.4.1208.jre7</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

执行代码自动生成命令
mvn -P db-gen exec:java
复制代码

  gradle配置

复制代码

application.yml

spring:
jooq:
#请勿动,当使用了ejdbc的配置后,Spring Boot无法识别正确的Dialect
sqlDialect: Mysql


jooq模块下的gradle文件
apply plugin: 'io.spring.dependency-management' dependencyManagement { resolutionStrategy { cacheChangingModulesFor 0, 'seconds' } imports { mavenBom 'io.spring.platform:platform-bom:Brussels-SR6' mavenBom 'org.jooq:jooq:3.10.1' } dependencies { dependency 'org.springframework.statemachine:spring-statemachine-core:1.2.6.RELEASE' } } apply plugin: 'nu.studer.jooq' emodule { dependency 'tms-jooq-model' } dependencies { compile 'org.jooq:jooq' jooqRuntime 'mysql:mysql-connector-java' } // Jooq Gradle Plugin 参考文档 https://github.com/etiennestuder/gradle-jooq-plugin jooq { version = '3.10.1' tms(sourceSets.main) { jdbc { driver = 'com.mysql.jdbc.Driver' url = 'jdbc:mysql://localhost:3306/test' user = 'xxx' password = 'xxx' } generator { name = 'org.jooq.util.DefaultGenerator' database { name = 'org.jooq.util.mysql.MySQLDatabase' inputSchema = 'scm_tms' outputSchemaToDefault = true includeExcludeColumns = true //重构,相关字段迁移到tb_docker_deployment_log中 excludes = ".*_bak|.*\\.drc_check_time|.*\\.is_delete|.*\\.updated_at" forcedTypes { forcedType { userType = "xxx.common.model.model.GeoPoint" converter = "xxx.jooq.model.converter.GeoPointConverter" expression = ".*_lnglat" types = ".*" } forcedType { userType = "xxx.base.api.model.UsedType" converter = "xxx.jooq.model.converter.VehicleIsUsedTypeConverter" /*A Java regular expression matching fully-qualified columns. Use the pipe to separate several expressions. ->database.table.column*/ expression = ".*\\.base_vehicle\\.is_used" /*Add a Java regular expression matching data types to be forced to have this type*/ types = ".*" } } } generate { relations = true deprecated = false records = true interfaces = true pojos = true daos = false fluentSetters = true } target { packageName = project.groupPrefix + "." + project.groupName + ".db.model" directory = 'src/main/java' } } } } //不自动执行Jooq的代码生成和清除任务 //数据库更改后,手动进入 jooq模块 //执行 gradle cleanGenerateTmsJooqSchemaSource generateTmsJooqSchemaSource //生成数据库对象在tms-db-model下 project.tasks.getByName('compileJava').dependsOn -= 'generateTmsJooqSchemaSource' project.tasks.getByName('clean').dependsOn -= 'cleanGenerateTmsJooqSchemaSource'

复制代码

 

jooq Converter示例

复制代码
import xxx.GeoPoint;
import org.jooq.Converter;

public class GeoPointConverter implements Converter<String, GeoPoint> {
    @Override
    public GeoPoint from(String databaseObject) {
        return new GeoPoint(databaseObject);
    }

    @Override
    public String to(GeoPoint point) {
        return point==null?"":point.gdFormat();
    }

    @Override
    public Class<String> fromType() {
        return String.class;
    }

    @Override
    public Class<GeoPoint> toType() {
        return GeoPoint.class;
    }
}

public class GeoPoint {
    /**
     * 经度值
     */
    private Double longitude;
    /**
     * 纬度值
     */
    private Double latitude;

    public GeoPoint() {
    }

    public GeoPoint(String location) {
        if (!StringUtils.isEmpty(location)) {
            String[] lnglat = location.split(",");
            this.longitude = Double.valueOf(lnglat[0]);
            this.latitude = Double.valueOf(lnglat[1]);
        }
    }

    public Double getLongitude() {
        return longitude;
    }

    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    public Double getLatitude() {
        return latitude;
    }

    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    public String gdFormat() {
        if (longitude == null || latitude == null) {
            return "";
        }
        return longitude + "," + latitude;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        GeoPoint geoPoint = (GeoPoint) o;

        if (longitude != null ? !longitude.equals(geoPoint.longitude) : geoPoint.longitude != null) return false;
        return latitude != null ? latitude.equals(geoPoint.latitude) : geoPoint.latitude == null;
    }

    @Override
    public int hashCode() {
        int result = longitude != null ? longitude.hashCode() : 0;
        result = 31 * result + (latitude != null ? latitude.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "GeoPoint{" +
                "longitude=" + longitude +
                ", latitude=" + latitude +
                '}';
    }

}
复制代码

 

复制代码
import xxx.scm.tms.common.model.model.WarehouseType;
import org.jooq.Converter;

/**
 * WarehouseTypeConverter
 */
public class WarehouseTypeConverter implements Converter<Byte, WarehouseType> {
    @Override
    public WarehouseType from(Byte databaseObject) {
        return WarehouseType.getTypeByCode(databaseObject);
    }

    @Override
    public Byte to(WarehouseType warehouseType) {
        return warehouseType.getCode();
    }

    @Override
    public Class<Byte> fromType() {
        return Byte.class;
    }

    @Override
    public Class<WarehouseType> toType() {
        return WarehouseType.class;
    }
}

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

public enum WarehouseType {
    NORMAL_TEMPERATURE((byte)1, "常温"),
    COLD_STORAGE((byte)2, "冷藏"),
    FREEZING((byte)3, "冷冻");

    private byte code;
    private String label;

    WarehouseType(byte code, String label) {
        this.code = code;
        this.label = label;
    }

    public byte getCode() {
        return code;
    }

    public String getLabel() {
        return label;
    }

    private static Map<Byte, WarehouseType> map = new HashMap<>();
    static {
        EnumSet.allOf(WarehouseType.class)
                .forEach(e -> map.put(e.getCode(), e));
    }

    public static WarehouseType getTypeByCode(Byte code) {
        return map.get(code);
    }
}
复制代码

 

高级用法代码示例

复制代码
public List<BaseWarehouse> searchWarehouses(WarehouseCondition warehouseCondition) {
    SelectConditionStep<?> step = dsl.selectFrom(Tables.BASE_WAREHOUSE).where(DSL.trueCondition());

    StringBuilder likeValue = new StringBuilder();

    if (!Objects.isNull(warehouseCondition.getWarehouseId())) {
        step.and(Tables.BASE_WAREHOUSE.WAREHOUSE_ID.eq(warehouseCondition.getWarehouseId()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getWarehouseName())) {
        likeValue.setLength(0);
        likeValue.append("%").append(warehouseCondition.getWarehouseName()).append("%");
        step.and(Tables.BASE_WAREHOUSE.WAREHOUSE_NAME.likeIgnoreCase(likeValue.toString()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getCompany())) {
        likeValue.setLength(0);
        likeValue.append("%").append(warehouseCondition.getCompany()).append("%");
        step.and(Tables.BASE_WAREHOUSE.COMPANY.likeIgnoreCase(likeValue.toString()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getProvinceName())) {
        step.and(Tables.BASE_WAREHOUSE.PROVINCE_NAME.eq(warehouseCondition.getProvinceName()));
    }

    if (!Objects.isNull(warehouseCondition.getProvinceId())) {
        step.and(Tables.BASE_WAREHOUSE.PROVINCE_ID.eq(warehouseCondition.getProvinceId()));
    }


    if (StringUtils.isNotBlank(warehouseCondition.getCityName())) {
        step.and(Tables.BASE_WAREHOUSE.CITY_NAME.eq(warehouseCondition.getCityName()));
    }

    if (!Objects.isNull(warehouseCondition.getCityId())) {
        step.and(Tables.BASE_WAREHOUSE.CITY_ID.eq(warehouseCondition.getCityId()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getDistrictName())) {
        step.and(Tables.BASE_WAREHOUSE.DISTRICT_NAME.eq(warehouseCondition.getDistrictName()));
    }

    if (!Objects.isNull(warehouseCondition.getDistrictId())) {
        step.and(Tables.BASE_WAREHOUSE.DISTRICT_ID.eq(warehouseCondition.getDistrictId()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getAddress())) {
        likeValue.setLength(0);
        likeValue.append("%").append(warehouseCondition.getAddress()).append("%");
        step.and(Tables.BASE_WAREHOUSE.ADDRESS.like(likeValue.toString()));
    }

    if (!Objects.isNull(warehouseCondition.getWarehouseLnglat())) {
        step.and(Tables.BASE_WAREHOUSE.WAREHOUSE_LNGLAT.eq(warehouseCondition.getWarehouseLnglat()));
    }

    if (StringUtils.isNotBlank(warehouseCondition.getContactName())) {
        step.and(Tables.BASE_WAREHOUSE.CONTACT_NAME.eq(warehouseCondition.getContactName()));
    }
    if (StringUtils.isNotBlank(warehouseCondition.getContactPhone())) {
        step.and(Tables.BASE_WAREHOUSE.CONTACT_PHONE.eq(warehouseCondition.getContactPhone()));
    }
    return step.fetchInto(BaseWarehouse.class);
}




 private Collection<SelectField<?>> getListField() {
    Collection<SelectField<?>> fields = new ArrayList<>();
    fields.add(SHIPMENT.SHIPMENT_ID);
    fields.add(SHIPMENT.OUTBOUND_ID);
    fields.add(SHIPMENT.BIZ_TYPE);
    fields.add(SHIPMENT.BIZ_SUBTYPE);
    fields.add(SHIPMENT.PICKUP_NAME);
    fields.add(SHIPMENT.PICKUP_CONTACT_NAME);
    fields.add(SHIPMENT.PICKUP_CONTACT_PHONE);
    fields.add(SHIPMENT.PICKUP_PROVINCE_NAME);
    fields.add(SHIPMENT.PICKUP_CITY_NAME);
    fields.add(SHIPMENT.PICKUP_DISTRICT_NAME);
    fields.add(SHIPMENT.PICKUP_ADDRESS);
    fields.add(SHIPMENT.DESTINATION_NAME);
    fields.add(SHIPMENT.DESTINATION_CONTACT_NAME);
    fields.add(SHIPMENT.DESTINATION_CONTACT_PHONE);
    fields.add(SHIPMENT.DESTINATION_PROVINCE_NAME);
    fields.add(SHIPMENT.DESTINATION_CITY_NAME);
    fields.add(SHIPMENT.DESTINATION_DISTRICT_NAME);
    fields.add(SHIPMENT.DESTINATION_ADDRESS);
    fields.add(SHIPMENT.DESTINATION_REMARK);
    fields.add(SHIPMENT.ORDER_ID);
    fields.add(SHIPMENT.ORDER_CREATED_AT);
    fields.add(SHIPMENT.STATUS);
    fields.add(DSL.ifnull(SHIPMENT_SKU.SHIPMENT_ID.count(), 0).as("COUNT"));
    fields.add(DSL.ifnull(SHIPMENT_SKU.SKU_COUNT.sum(), BigDecimal.ZERO).as("SKU_COUNT"));
    fields.add(DSL.ifnull(SHIPMENT_SKU.OUTBOUND_COUNT.sum(), BigDecimal.ZERO).as("OUTBOUND_COUNT"));
    fields.add(DSL.ifnull(SHIPMENT_SKU.SIGN_COUNT.sum(), BigDecimal.ZERO).as("SIGN_COUNT"));

    fields.add(SHIPMENT.PICKUP_LNGLAT);
    fields.add(SHIPMENT.DESTINATION_LNGLAT);
    fields.add(SHIPMENT.EXPECT_ARRIVE_BEGIN_AT);
    fields.add(SHIPMENT.EXPECT_ARRIVE_END_AT);
    fields.add(SHIPMENT.BIZ_JSON);
    fields.add(SHIPMENT.WAREHOUSE_ID);
    fields.add(SHIPMENT.DRIVER_REMARK);

    //detail使用字段
    fields.add(SHIPMENT.DRIVER_ID);
    fields.add(SHIPMENT.CUSTOMER_SERVICE);
    fields.add(SHIPMENT.CARRIER_ID);
    fields.add(SHIPMENT.CARRIER_NAME);
    fields.add(SHIPMENT.ROUTE_ID);

    return fields;
}
Result<Record> records = dsl.select(getListField())
        .from(SHIPMENT)
        .leftJoin(SHIPMENT_SKU)
        .on(SHIPMENT.SHIPMENT_ID.eq(SHIPMENT_SKU.SHIPMENT_ID))
        .where(getListConditions(query))
        .groupBy(SHIPMENT.SHIPMENT_ID)
        .orderBy(SHIPMENT.CREATED_AT.desc())
        .offset(query.getOffset())
        .limit(query.getLimit())
        .fetch();
复制代码

 

DSLContext executeUpdate

   dsl.executeUpdate(dsl.newRecord(BASE_WAREHOUSE, baseWarehouse)), 如果vo类中的主键为null的时候

  

  要指定 condition

  

 

v乐观锁实现

  https://www.jooq.org/doc/3.10/manual-single-page/#optimistic-locking

  https://www.jooq.org/doc/3.10/manual-single-page/#codegen-config-record-version-timestamp-fields










本文转自 小眼儿 博客园博客,原文链接:http://www.cnblogs.com/hujunzheng/p/8342999.html,如需转载请自行联系原作者
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
7月前
|
前端开发 JavaScript Java
JAVAEE框架技术之4springMVC入门
JAVAEE框架技术之4springMVC入门
137 0
JAVAEE框架技术之4springMVC入门
|
7月前
|
SQL Java 数据库连接
JAVAEE框架技术之7-myBatis ORM框架入门基础CRUD
JAVAEE框架技术之7-myBatis ORM框架入门基础CRUD
168 0
JAVAEE框架技术之7-myBatis ORM框架入门基础CRUD
QGS
|
7月前
|
前端开发 Java easyexcel
Springboot3+EasyExcel由浅入深
Springboot3+EasyExcel由浅入深
QGS
312 1
|
7月前
|
XML Java 数据库连接
Hibernate与Spring整合实践实例
Hibernate与Spring整合实践实例
148 0
|
SQL 缓存 Java
《SSH框架》---Hibernate
《SSH框架》---Hibernate
109 0
|
JSON Java 编译器
告别BeanUtils,Mapstruct从入门到精通
如果你现在还在使用BeanUtils,看了本文,也会像我一样,从此改用Mapstruct。
1146 0
告别BeanUtils,Mapstruct从入门到精通
|
Java 关系型数据库 测试技术
SpringCloud系列----->SpringBoot项目中整合jooq和postgresql数据库
前言: 公司的BI项目采取的是SpringBoot + Jooq + postgresql 组织形势,现在将这个配置过程,详细记录下来。 Jooq和MyBatis和spring data jpa作用是一样的,都是用来链接操作数据库的。
2977 0
|
Java 开发工具 git
|
Java 数据库连接 数据库
第三章 : JavaORM框架之Mybatis篇(Ibatis)
欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系。本系列教程希望您能站在上帝的角度去观察(了解)Java体系。
3117 0
|
关系型数据库 MySQL Java
jooq实践
用法   sql语句 SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(*) FROM AUTHOR JOIN BOOK ON AUTHOR.
1964 0