且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

SpringCloud系列----->SpringBoot项目中整合jooq和postgresql数据库

更新时间:2021-10-04 04:46:16

前言:
    公司的BI项目采取的是SpringBoot + Jooq + postgresql 组织形势,现在将这个配置过程,详细记录下来。
    Jooq和MyBatis和spring data jpa作用是一样的,都是用来链接操作数据库的。
    Jooq的优点:
        (1)、DSL(Domain Specific Language )风格,代码够简单和清晰。遇到不会写的sql可以充分利用IDEA代码提示功能轻松完成。
        (2)、保留了传统ORM 的优点,简单操作性,安全性,类型安全等。不需要复杂的配置,并且可以利用Java 8 Stream API 做更加复杂的数据转换。
        (3)、支持主流的RDMS和更多的特性,如self-joins,union,存储过程,复杂的子查询等等。
        (4)、丰富的Fluent API和完善文档。
        (5)、runtime schema mapping 可以支持多个数据库schema访问。简单来说使用一个连接池可以访问N个DB schema,使用比较多的就是SaaS应用的多租户场景。
    好了,不多啰嗦了,Jooq的详细文档还是请大家,看官方文档:https://www.jooq.org/
    公司的项目是采用gradle 组织的,gradle和maven是一样的,但是gradle的配置文件更清晰,maven的xml组织形式,啰嗦长,看着就晕,不简洁。有关gradle和maven的相关详细使用方法的请参考gradle和maven的官网。
    闲话少劳聊,直接上build.gradle代码,在代码注释中详细说明每个配置的详细的作用:
    
    plugins {
      id 'org.springframework.boot' version '2.1.3.RELEASE'   #springboot版本
        id 'java'                                               #标识是java项目
         id 'nu.studer.jooq' version '3.0.3'                     #jooq的版本号
    }

    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'jacoco'                                        #引入生成文档的jar包
    group = 'com.jingdata.asset.manage.bi'                        #略
    version = '0.0.1-SNAPSHOT'                                    #略
    sourceCompatibility = '1.8'                                   #java版本

    repositories {
      mavenLocal()
      mavenCentral()
    }

    dependencies {
            implementation 'org.springframework.boot:spring-boot-starter-data-redis'
            implementation 'org.springframework.boot:spring-boot-starter-jooq'      #springboot对jooq直接支持,jooq的springboot的starter
            implementation 'org.springframework.boot:spring-boot-starter-web'
            implementation 'org.springframework.boot:spring-boot-starter-aop'
            implementation 'org.springframework.boot:spring-boot-starter-actuator'
            implementation 'org.aspectj:aspectjrt:1.6.11'
            implementation 'org.aspectj:aspectjweaver:1.6.11'
            implementation 'cglib:cglib:2.1'
            implementation 'org.springframework:spring-aop:4.3.9.RELEASE'
            implementation 'io.springfox:springfox-swagger2:2.8.0'
            implementation 'io.springfox:springfox-swagger-ui:2.8.0'
            compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1'
            compile("org.togglz:togglz-spring-boot-starter:2.6.1.Final")
            runtimeOnly 'org.postgresql:postgresql'                                  #postgresql的链接支持
            testImplementation 'org.springframework.boot:spring-boot-starter-test'
            compileOnly 'org.projectlombok:lombok:1.18.2'
            jooqRuntime 'postgresql:postgresql:9.1-901-1.jdbc4'                       #postgresql的链接支持
    }
  
    test {
     include '**/*Test.class'                                            #单元测试命令,执行gradle   test命令只会执行测试文件中以Test结尾的文件中的所有的测试方法。
    }
  
    task testInteg(type: Test) {    
      include '**/*Integ.class'                                          #单元测试命令,执行gradle testInteg命令只会执行测试文件中以Integ结尾的文件中的所有的测试方法。
    
    #之所以需要gradle test , gradle testInteg两个命令,是因为有些我们这里需要区分涉及外部依赖(redis,mongodb,pg数据库,elasticsearch等)的单元测试,和不涉及外部依赖的单元测试(内存中执行就行),几十个组件的集成测试时,组件之间彼此需要依赖彼此产生的数据时,h2之类的内存数据库,就不能满足测试需要了,跑一次gradle testInteg 命令,把需要的数据保存在redis,mongodb,pg数据库,elasticsearch 等中。
    #公司的项目要求,每次git push提交代码和jenkins部署项目的时候需要执行一下gradle test 命令,确认修改的代码没有破坏以前的功能。
    #测试同事在整个系统集成的时候,需要执行gradle test , gradle testInteg两个命令,其中gradle test经常执行,gradle testInteg 在系统几十个模块集成的时候,会执行。
    
    }
    
    #jooq配置,自动生成数据库表和字段的对应关系的一系列的java class文件
    jooq {
    version = '3.11.9'
    edition = 'OSS'
    sample(sourceSets.main) {
    jdbc {     
            driver = 'org.postgresql.Driver'                      #jooq链接数据库的驱动 
            url = 'jdbc:postgresql://127.0.0.1:5432/invest111'    #数据库链接地址
            user = 'inves111'                                     #连接数据库的用户名
            password = 'invest111'                                #连接数据库的密码
         }
    generator {
        name = 'org.jooq.codegen.DefaultGenerator'
        strategy {
            name = 'org.jooq.codegen.DefaultGeneratorStrategy'
            // ...
        }
        database() {
                    name = 'org.jooq.meta.postgres.PostgresDatabase'
                    inputSchema = 'public'                     #只生成public schema中的表
                        includes='paas_bi_.*|paas_datarights_.*|paas_mt_.*|paas_auth_.*|paas_org_.*'    #只需要paas_bi 、 paas_datarights、paas_mt、paas_auth、paas_org 开头的一系列的表。
            
                    } 
        generate() {}
        target {
                packageName = 'com.jingdata.asset.manage.bi.assetmanagesystem.bidb'                          #生成文件夹的名字
                directory = 'src/main/java'                   #生成文件所在的目录
                  }
            }
        }
    }

  settings.gradle文件的内容:
  
  pluginManagement {
    repositories {
    gradlePluginPortal()
    }
  }
  rootProject.name = 'asset-manage-system'       
  
  所有这些配置完成后,就会如图所示,执行这个命令,就会把数据库中的表自动生成出来:
  ![1564371259390](https://yqfile.alicdn.com/23bcb12200ad1136ea627ee340584ecdcae8e30d.jpeg)

  我们执行gradle  test命令结果如下:
  ![2222](https://yqfile.alicdn.com/0457c862e6b19e4203ba81d8e772391c194d0730.jpeg)
  
  会在项目的目录下生成一个build文件夹,这个目录下会有本次执行单元测试生成的单元测试报告,单元测试报告打开后是这样一个效果:
  ![4444](https://yqfile.alicdn.com/4d75c24182206129c9e068eaf038c909b314981b.jpeg)

  gradle testInteg命令生成的单元测试也在这里,我这里就不赘述了。
  
  这一部分是jooq自动生成的对应的数据库的表的java文件:
  ![6666](https://yqfile.alicdn.com/c209be982f43530097f82b35871a919929a8ea30.jpeg)
  
  项目的src/main/resources下的application.properties文件中的配置内容:
  server.port=8006
  spring.datasource.url = jdbc:postgresql://127.0.0.1:5432/invest111
  spring.datasource.driver-class-nam = org.postgresql.Driver
  spring.datasource.username = invest111
  spring.datasource.password = invest111
  spring.datasource.minIdle = 5
  spring.datasource.maxActive = 50
  spring.datasource.maxWait = 60000
  spring.datasource.timeBetweenEvictionRunsMillis = 60000
  spring.datasource.minEvictableIdleTimeMillis = 300000
  spring.datasource.validationQuery = SELECT 1 FROM DUAL
  spring.datasource.testWhileIdle = true
  spring.datasource.testOnBorrow = false
  spring.datasource.testOnReturn = false
  spring.datasource.poolPreparedStatements = true
  spring.datasource.maxPoolPreparedStatementPerConnectionSize = 20
  spring.datasource.connectionProperties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  spring.jooq.sql-dialect = postgres
  bi.http.port =  8006
  bi.client.host = clientbi.2222.com
  #日志相关配置
  logging.file= ./logs/jingdata-bi-pg
  logging.level.root= ERROR
  logging.level.com.jingdata= DEBUG
  logging.level.org.springframework.web= DEBUG
  logging.level.com.netflix.discovery.DiscoveryClien= OFF
  logging.level.com.netflix.discovery.InstanceInfoReplicator= OFF
  
  基本是一看就行,也就不啰嗦了。
  
  下面再给出,一些使用jooq在postgresql 数据中的表中做CURD操作的代码,不啰嗦,直接上,几乎是一看就懂:
  
  package com.jingdata.asset.manage.bi.assetmanagesystem.system.impl;

  import com.jingdata.asset.manage.bi.assetmanagesystem.system.function.IAnnouncementReferencesService;
  import com.jingdata.asset.manage.bi.assetmanagesystem.system.impl.base.BaseImpl;
  import com.jingdata.asset.manage.bi.assetmanagesystem.transform.ReferenceInfoVo;
  import com.jingdata.asset.manage.bi.assetmanagesystem.transform.ReferenceTransform;
  import org.jooq.Record;
  import org.jooq.Result;
  import org.jooq.impl.DSL;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.stereotype.Service;
  import java.sql.Timestamp;
  import java.util.List;
  import static com.jingdata.asset.manage.bi.assetmanagesystem.bidb.Tables.PAAS_BI_ANNOUNCEMENT_REFERENCES;

  /**
   * @date   2019-02-28
   *
   */
  @Service
  public class AnnouncementReferencesImpl extends BaseImpl implements IAnnouncementReferencesService {

      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      private ReferenceTransform referenceTransform = new ReferenceTransform();

      @Override
      public List<ReferenceInfoVo> getOneAnnouncementReferences(Integer announcementId) {
            Result<Record> records = getDSLContext().select().from(PAAS_BI_ANNOUNCEMENT_REFERENCES)
            .where(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.USER_ID).eq("5bcfd9f06faa7b79fd28c304"))
            .and(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.IS_DELETED).eq(0))
            .and(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.REFERENCE_ID).eq(announcementId))
            .orderBy(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.ID).desc())
            .fetch();

            return  referenceTransform.transformReferenceRecord(records);
      }

      @Override
      public Integer addOneAnnouncementReferences(Integer announcementId, String reportList, String chartList) {
            return getDSLContext().insertInto(PAAS_BI_ANNOUNCEMENT_REFERENCES)
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.APP_ID), "1111122222")
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.USER_ID), "1111122222")
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.TENANT_ID), "1111122222")
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.CREATED_BY), "1111122222")
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.UPDATED_BY), "1111122222")
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.IS_DELETED), 0)
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.UPDATED_AT), new Timestamp(System.currentTimeMillis()))
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.CREATED_AT), new Timestamp(System.currentTimeMillis()))
                //1表示报表,2表示图表
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.REFERENCE_TYPE), 1)
                //1表示报表,2表示图表
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.REFERENCE_ID), Integer.parseInt("111"))
                //1表示报表,2表示图表
                .set(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.SOURCE_ID), announcementId)
                .execute();
          }

          @Override
          public Integer deleteOneAnnouncementReferences(String referenceIds) {
                return getDSLContext().delete(PAAS_BI_ANNOUNCEMENT_REFERENCES)
          .where(DSL.field(PAAS_BI_ANNOUNCEMENT_REFERENCES.ID).eq(Integer.parseInt(referenceIds))).execute();
          }
          
          @Override
          public void deleteByMasterChartId(Long masterId, Context context) {
                dslContext.update(PAAS_BI_DASHBOARD_LINKAGE)
                  .set(PAAS_BI_DASHBOARD_LINKAGE.IS_DELETED, 1)
                  .set(PAAS_BI_DASHBOARD_LINKAGE.UPDATE_BY, context.getUserId())
                  .set(PAAS_BI_DASHBOARD_LINKAGE.UPDATE_TIME, System.currentTimeMillis())
                  .where(PAAS_BI_DASHBOARD_LINKAGE.MASTER_CHART_ID.eq(masterId))
                  .and(PAAS_BI_DASHBOARD_LINKAGE.TENANT_ID.eq(context.getTenantId()))
                  .and(PAAS_BI_DASHBOARD_LINKAGE.APP_ID.eq(context.getAppId()))
                  .and(PAAS_BI_DASHBOARD_LINKAGE.IS_DELETED.eq(0))
                  .execute();
            }
  }
  
  基本上增、删、改、查都有了。
  
  更高级的用法,jion 、union等相关写法,请参考jooq的官方文档。

  本周会给出在码云上的git 源码的链接地址,如果有疑问的,请参考源码,代码胜千言!!!!!