基于springboot+jpa 实现多租户动态切换多数据源 - 使用Flyway实现多数据源数据库脚本管理和迭代更新

news2024/11/23 20:14:40

多租户动态多数据源系列

1、基于springboot+jpa 实现多租户动态切换多数据源 - 数据隔离方案选择分库还是分表
2、基于springboot+jpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源
3、基于springboot+jpa 实现多租户动态切换多数据源 - 使用Flyway实现多数据源数据库脚本管理和迭代更新

目录

  • 多租户动态多数据源系列
  • 前言
  • Liquibase 还是 Flyway
    • Flyway
    • Liquibase
    • 对比
    • 结论
  • 项目结合Flyway实现数据库迭代更新
    • pom配置
    • yaml配置
    • 根据locations配置创建文件夹
    • 项目文件结构
    • 对应文件夹下添加需要运行的sql脚本
    • 基于SQL迁移的文件命名规则
    • Flyway配置类
    • 启动项目
    • 注意事项
      • 1.严格遵守脚本命名规则
      • 2.脚本文件版本号必须>基线版本号
      • 3.禁止删除或修改已执行的 SQL 文件
      • 4.脚本的版本号应严格不同
      • 5.⚠️慎用地雷配置项
      • 6.Druid 与 Flyway 的冲突
      • 7.DDL 与 DML 语句不能写在同一 SQL 文件
      • 8.禁止提交执行失败的 SQL 文件
    • 脚本执行失败排查及修复
      • 1.查看错误日志及原因
      • 2.根据错误信息,修复SQL脚本
      • 3.删除元数据表失败记录
      • 4.再次启动项目即可
  • 参考

前言

自从项目变成了多数据源架构,不同数据源的数据库表结构还不同,JPA的自动生成表结构功能已经不能再使用了,需要改为原生sql来支持多数据源。
每次服务的代码更新部署,难免会存在数据库结构的变更以及字典数据的添加,手动执行更新脚本是一个耗时耗力的工作,而且还会出现遗漏或者其他状况,这时急需一个自动执行数据库脚本的工具来解决问题

为此我做了相关调研,发现常见的开源迁移工具 Liquibase 和 Flyway使用和对比最多,那么就进行一场二选一的角逐吧!


Liquibase 还是 Flyway

在这里插入图片描述
首先快速概括下这两个工具:

Flyway 和 Liquibase都支持专业数据库重构和版本控制所需的所有功能,因此您将始终知道要处理的数据库模式的版本以及它是否与软件版本匹配。两种工具都集成在 Maven或 Gradle 构建脚本中以及 Spring Boot 生态系统中,因此您可以完全自动化数据库重构。

  • Flyway 使用 SQL或Java 定义数据库更改,因此您可以定制 SQL脚本,使其与基础数据库技术(例如Mysql、Oracle、PostgreSQL等)良好地配合使用。
  • Liquibase 使用 XML,YAML 或 JSON格式 来定义数据库更改来引入抽象层。因此,Liquibase 更适合在具有不同基础数据库技术的不同环境中安装的软件产品中使用。
对比项FlywayLiquibase
官网Welcome To FlywayLiquibase简介
描述创造者是一家名为Redgate的公司,它被描述为一个开源的数据库迁移工具,它更倾向于简单性和惯例而不是配置。开始于2006年,是一个用于数据库迁移的开源工具。
支持的数据库截至目前(2022-12-29),支持大多数数据库,有 Oracle、 SQL Server(包括 Amazon RDS 和 Azure SQL 数据库)、 Azure Synapse(以前的数据仓库)、 DB2、 MySQL(包括 Amazon RDS、Azure 数据库和 Google Cloud SQL)、 Aurora MySQL、 MariaDB、 Percona XtraDB Cluster、 测试容器、 PostgreSQL(包括 Amazon RDS、Azure 数据库、Google Cloud SQL、TimescaleDB、YugabyteDB 和 Heroku)、 Aurora PostgreSQL、 Redshift、 CockroachDB、 SAP HANA、 Sybase ASE、 Informix、 H2、 HSQLDB、 Derby、 Snowflake、 SQLite和 Firebird。支持大多数数据库,有Postgres、Oracle、DB2、H2、MariaDB、SQL Server、SQLite,以及其他许多数据库。许多基于云的数据库也被支持,例如Azure SQL、Amazon RDS、Amazon Aurora。
编写方式脚本可以用纯SQL编写(支持许多方言)或用Java编写(主要用于更复杂的转换)它是基于变化日志和变化集文件的概念,这些文件可以用SQL、XML、YAML、JSON来写。
使用方式有一个命令行客户端,但也提供Maven和Gradle插件。更重要的是,它有Java API,也适用于Android。可以从shell中运行Liquibase迁移脚本,使用Maven Gradle甚至Ant等构建工具。此外,可以生成纯SQL查询去执行。

Flyway

实现数据库变更原理:

  1. 项目启动时拉起Flyway,先检查数据库里面有没有Flyway元数据表,没有则创建,在数据库表中默认新建一个数据表用于存储flyway的运行信息,默认表名:flyway_schema_history;
  2. 检查 classpath 中所有的变更;
  3. 对比变更和自己的表,如果变更的版本低于或等于当前版本,不做任何变动;否则,变更会按从低到高排序,并依次执行;
  4. 执行完,在 元数据表 做相应的记录

Liquibase

实现数据库变更原理:

  1. 默认情况下,Bean会在/db/changelog(相对于Classpath根目录)里查找db.changelog-master.yaml文件。Liquibase变更集都集中在一个文件里。
  2. changeset命令后的那行有一个id属性,要对数据库进行后续变更。可以添加一个新的changeset,只要id不一样就行。此外,id属性也不一定是数字,可以包含任意内容。
  3. 应用程序启动时,Liquibase会读取db.changelog-master.yaml里的变更集指令集,与之前写入databaseChangeLog表里的内容做对比,随后执行未运行过的变更集。

对比

两者的基本功能其实都差不多

  • 都是 Java 开发的开源数据库变更管理工具
  • 支持大部分的数据库
  • 和 Maven/Gradle 无缝集成
  • 和 Spring 无缝集成
  • 非常类似的变更实现方式
  • 复杂变更如果 SQL 不能满足的话,都可以用 Java 代码实现

较大区别是 Flyway 的变更以纯 SQL 为脚本,简单直接;Liquibase 比较厚重,当然花样也比较多,包括:

  • 可指定不同的 profile
  • 具有通用的变更操作支持不同的数据库,如 createTable
  • Liquibase 开源版本支持 diff 模式,而此特性 Flyway 必须用商业版
  • Liquibase 开源版本支持回滚 rollback,而此特性 Flyway 必须用商业版,Liquibase 的付费版本据说对不同种类的回滚有更复杂的支持。
  • 两者指定变更执行顺序的方法不同,Flyway 通过固定的文件名格式来确定顺序,而 Liquibase 就是通过给定文件的顺序来执行,所以开发人员还要遵守好命名规则,例如按照日期/时间顺序命名

结论

  • 如果想完全控制 SQL,Flyway 是首选工具,因为可以使用完全定制的 SQL 甚至 Java 代码来更改数据库。
  • 多种数据库类型的数据源的情况下使用 Liquibase 会更加合适,不需要维护多种数据库脚本,和学习多种数据库语言,Liquibase 对于大型项目更加友好。

综上所述,我在项目中选择了 Flyway。原因有二:

  1. 项目是通过SQL脚本构建库表结构
  2. 虽然是多数据源,但数据库类型只有Mysql

项目结合Flyway实现数据库迭代更新

pom配置

<!-- flyway -->
<dependency>
	<groupId>org.flywaydb</groupId>
	<artifactId>flyway-core</artifactId>
	<version>7.10.0</version>
</dependency>

yaml配置

#mysql environment
spring:
  datasource:
    dynamic:
      hikari:
        connection-timeout: 5000
        idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收
        min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个
        max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个
        max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收
        is-auto-commit: true
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: 
          username: 
          password: 
          driver-class-name: com.mysql.cj.jdbc.Driver
          init:
            schema: db/primary_db_table.sql
            
# 上面是多数据源配置,下面开始才是flyway配置
  flyway:
    # 是否启用flyway
    enabled: true
    # metadata 版本控制信息表 默认 flyway_schema_history
    table: flyway_schema_history_test
    # 如果没有元数据表,在执行flyway migrate命令之前, 必须先执行flyway baseline命令
    # 非空数据库初始化Flyway时需要打开此开关进行Baseline操作
    baseline-on-migrate: true
    # 执行时标记的tag 默认为<<Flyway Baseline>>
    baseline-description: <<Flyway Baseline>>
    # 是否可以无序执行 开发环境建议 true  生产环境建议 false
    out-of-order: false
    # 执行迁移时是否自动调用验证SQL文件是否存在问题,当你的版本不符合逻辑时会抛出异常
    validate-on-migrate: true
    # SQL 脚本的目录 默认值 classpath:db/migration
    # 这里写项目启动时的主库sql变更版本路径,然后在配置类中根据不同的数据源转换即可
    locations: classpath:db/primary 

根据locations配置创建文件夹

根据在yaml配置文件的脚本存放路径的配置
在resource目录下建立文件夹db/primary、db/migration

项目文件结构

可以看到我这里classpath:db/路径下有primary 和 migration两个文件夹,这里就为了不同的数据库表结构可以执行不同的sql脚本,而不会相互影响到

在这里插入图片描述

对应文件夹下添加需要运行的sql脚本

sql脚本的命名规范为:V+版本号(版本号的数字间以”.“或”_“分隔开)+双下划线(用来分隔版本号和描述)+文件描述+后缀名,例如:V1.0.1__update_org_config_db_table.sql。

具体可参考上下文

基于SQL迁移的文件命名规则

常用的迁移方式就是基于sql的迁移,如上图,我就是采用的sql迁移方式,但是文件命名有一定的规则

在这里插入图片描述

文件名由如下几部分组成:

  • 前缀:V表示版本化,U表示撤销回退,R表示可重复迁移
  • 版本:版本一般为数字,多个数字之间用点分割,比如:V1.1,V1等
  • 分隔符:__双下划线 ,这里需要特别注意,是双下划线
  • 描述:表示当前脚本执行的操作类型
  • 后缀:.sql

上面的这些组成部分都是可以进行配置的,更多的配置可以参考官方文档:Flyway Documentation > Flyway CLI and API > Configuration

Configuration旧文档,感觉更好理解点

Flyway配置类

结合yaml配置和项目文件结构图理解,其实本质就是将application文件的flyway的locations设置为db/primary主库的路径,让主库只去执行primary文件夹下的sql文件,然后在配置类中拆解locations,根据不同的数据源重新组成对应的locations路径。

配置类的代码如下:

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Map;

@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class FlywayConfig {

    private final DataSource dataSource;

    @Value("${spring.flyway.locations}")
    private String SQL_LOCATION;

    @Value("${spring.flyway.table}")
    private String VERSION_TABLE;

    @Value("${spring.flyway.baseline-on-migrate}")
    private boolean BASELINE_ON_MIGRATE;

    @Value("${spring.flyway.out-of-order}")
    private boolean OUT_OF_ORDER;

    @Value("${spring.flyway.validate-on-migrate}")
    private boolean VALIDATE_ON_MIGRATE;

    @Bean
    @PostConstruct
    public void migrateOrder() {
        log.info("调用数据库生成工具");
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        Map<String, DataSource> dataSources = ds.getDataSources();
        dataSources.forEach((k, v) -> {
            log.info("正在执行多数据源生成数据库文件: " + k);

            if (k.equals("master")) {
                log.info("正在执行后台库数据源生成数据库文件");
                // 将路径转换
                SQL_LOCATION = SQL_LOCATION.split("/")[0] + "/primary"; 
                Flyway flyway = Flyway.configure()
                        .dataSource(v)
                        .locations(SQL_LOCATION)
                        .baselineOnMigrate(BASELINE_ON_MIGRATE)
                        .table(VERSION_TABLE)
                        .outOfOrder(OUT_OF_ORDER)
                        .validateOnMigrate(VALIDATE_ON_MIGRATE)
                        .load();
                flyway.migrate();
            } else {
                log.info("正在执行多机构数据源生成数据库文件");
                // 将路径转换
                SQL_LOCATION = SQL_LOCATION.split("/")[0] + "/migration";
                Flyway flyway = Flyway.configure()
                            .dataSource(v)
                            .locations(SQL_LOCATION)
                            .baselineOnMigrate(BASELINE_ON_MIGRATE)
                            .table(VERSION_TABLE)
                            .outOfOrder(OUT_OF_ORDER)
                            .validateOnMigrate(VALIDATE_ON_MIGRATE)
                            .load();
                flyway.migrate();
            }
        });
    }

}

启动项目

启动日志验证

在这里插入图片描述
数据库验证

在数据库中可以看到已按照定义好的脚本,完成数据库变更,并在flyway_schema_history表插入了sql执行记录:
在这里插入图片描述
如果上述验证都没问题了,那就是没有问题了


注意事项

下面的注意事项一定要认真仔细的阅读,不然可能就删库跑路

1.严格遵守脚本命名规则

V+版本号+双下划线+脚本变更描述+后缀

例如:V1.1.0__create_table.sql

flyway.sql-migration-prefix配置前缀,默认V
flyway.sql-migration-separator配置分隔符,默认双下划线
flyway.sql-migration-suffix配置脚本后缀,默认.sql

特别注意:V1__.sql == V1.0__.sql V1.1__.sql == V1.1.0__.sql
所以配置时,最好设置显示递增的版本号,否则会报错

2.脚本文件版本号必须>基线版本号

文件的版本号必须 > 基线初始版本号,否则不会执行你的脚本
比如基线默认版本号为1,所以你的脚本版本号必须大于1,例如V1.1****

例如文件为:V1.0.0__create_table.sql,你会发现该脚本并不会执行

3.禁止删除或修改已执行的 SQL 文件

如果项目已经执行了过了某个脚本,那么这个脚本不能删除,也不能修改,否则在项目启动时会报错

  • 删除了则是找不到以前执行的文件
  • 修改了则是在对比checksum时报不一致
  • 如果执行的sql脚本有问题,第一次没有跑成功,重新跑时,要么重新定义脚本的版本号,要么删除表schema_version的当前版本记录

4.脚本的版本号应严格不同

两个脚本的版本号应该严格不同,不能出现1中的特别注意项

特别注意:V1__.sql == V1.0__.sql V1.1__.sql == V1.1.0__.sql
所以配置时,最好设置显示递增的版本号,否则会报错

5.⚠️慎用地雷配置项

flyway.clean-on-validation-error:这个配置项一定要小心了
如果配置为true,当你的sql脚本执行失败时,就会执行删除库中所有表的操作,即之前的clean操作,所以一定要慎重,慎重,慎重!!!

6.Druid 与 Flyway 的冲突

Flyway通过 SQL 脚本来执行数据库的建立与更新。当同时集成了 Druid 和 Flyway 之后,Druid 的 wall防火墙极可能直接干预 SQL 脚本的操作,继而导致 Flyway 执行中断。在项目开发的过程中,配置以下防火墙属性以放行 Flyway 的SQL 操作

spring:
  datasource:
    druid:
      wall:
        config:
          variantCheck: false
          noneBaseStatementAllow: true
          commentAllow: true
          multiStatementAllow: true

7.DDL 与 DML 语句不能写在同一 SQL 文件

8.禁止提交执行失败的 SQL 文件

执行失败的 SQL 文件一定要成功解决,然后确保application 启动成功后,再提交代码到远程。

脚本执行失败排查及修复

由于Flyway导致application执行失败时,元数据表会有错误记录
在这里插入图片描述

1.查看错误日志及原因

在console 中查看错误日志及原因,如下图:

有问题的脚本:V1.0.3__creat_table.sql
具体错误信息:Table ‘df_org_config_test_001’ already exists
在这里插入图片描述

2.根据错误信息,修复SQL脚本

我的具体错误信息是已经存在了表df_org_config_test_001,那么我修改为创建表df_org_config_test_002

3.删除元数据表失败记录

在数据库中打开 df_flyway_schema_history_test 表,删除 success 字段为 0 (0状态为失败,1状态为成功) 的记录(表中最后一条记录)

4.再次启动项目即可

一般来说这样就解决了问题,如果还是启动不成功,那就要再仔细检查一下脚本了

多数据源系列目前就告一段落了!!!!


参考

Flyway针对多数据源配置实战方案

SQL脚本难以维护,各环境数据库版本不一致该怎么办?

Spring Boot - Flyway Database

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kafka Producer Acks机制

Kafka Producer Acks 设置ACK props.put("acks", "all");通过上述代码&#xff0c;配置kafka生产者发送消息后&#xff0c;是否等待Broker的回执信息。在集群环境下&#xff0c;该配置是kafka保证数据不丢的重要的参数之一&#xff0c;今天来学习一下&…

深入理解Elasticsearch分片

了解分片的基本原理&#xff0c;对Elasticsearch性能调优有帮助。 关系梳理 ES底层使用的是Lucene库&#xff0c;ES的分片&#xff08;shard &#xff09;是Lucene的索引&#xff0c;ES的索引是分片的集合&#xff0c;Lucene的索引是由多个段&#xff08;segment&#xff09;…

青岛OJ如何导入题库详细图示

打开你的后台管理 找到问题位置 增加题目是可以编辑题目&#xff0c;导入数据。 导入导出是用题目和数据直接导入的。 这个ID的话就是题目ID不能设置一样的 然后题目输入输出就都不说了 按照格式就可以了&#xff0c;这里说一下Tag是标签&#xff0c;每次都要设置&#xff…

shell-条件测试

1、编写一个 Shell脚本&#xff0c;程序执行时从键盘读入一个目录名&#xff0c;如果用户输入的目录不存在&#xff0c;则提示file does not exist&#xff1b;如果用户输入的不是目录则提示用户必须输入目录名&#xff1b;如果用户输入的是目录则显示这个目录下所有文件的信息…

小程序版 Three.js 框架下载及目录配置

1.库文件说明 由于微信官方提供的threejs适配库已经很久没有更新&#xff0c;而且开发者普遍反映使用起来很难用。 我这里分享的是独立的库文件&#xff0c;不需要npm安装&#xff0c;下载后将库文件放到项目中即可使用。 2.下载后的压缩包文件 3.解压后的文件夹结构 4.文件…

Vue2和Vue3的双向数据绑定原理

目录前言&#xff1a;vue2.x 是如何实现响应式系统的&#xff1a;defineProperty 的痛点&#xff1a;Object.defineProperty 代码的使用Proxy 方法的理解Proxy 代码的使用&#xff1a;总结&#xff1a;前言&#xff1a; 今天小编给大家讲解一下&#xff0c;Vue2和Vue3的双向数据…

CAPL学习之路-测试功能集函数(诊断测试)

TestCollectDiagEcuInformation 向诊断目标的诊断类下的所有诊断服务发送诊断请求,并将诊断响应写入测试报告中 testcase TCExample() {int status;status = TestCollectDiagEcuInformation( "Door", "Sessions");if( status == 0)TestStepPass( "EC…

javaweb项目接入CAS单点认证(含自身系统的三员过滤)

一、搭建cas server 1.下载war包 2.打开cmd窗口执行以下命令&#xff0c;命令如下(指定ip)&#xff1a; keytool -genkey -v -alias casbm -keyalg RSA -keystore D:\cas\keystore\casbm.keystore -ext SANIP:192.168.2.166 3.我们生成秘钥库后需要从秘钥库中导出证书&#x…

D. Distinct Characters Queries(set维护)

Problem - 1234D - Codeforces 给你一个由小写拉丁字母组成的字符串s和对这个字符串的q个查询。 回顾一下&#xff0c;字符串s的子串s[l;r]就是字符串slsl1...sr。例如&#xff0c;"codeforces "的子串是 "code"、"force"、"f"、&quo…

spring6(概念;ioc详解,各种数据的注入方式)

第一章 启示录 一个普通的三层架构&#xff0c;不借助spring的情况下。需要程序员自己去new对象。 如果使用这种方式&#xff0c;那么代码上就把功能写死了&#xff0c;这时候需要更改一个数据库连接。这是时候数据交互层的代码就需要修改。这违背了OCP开闭原则。 1.1 OCP开闭…

Bert 得到中文词向量

通过bert中文预训练模型得到中文词向量和句向量&#xff0c;步骤如下&#xff1a; 下载 bert-base-chiese模型 只需下载以下三个文件&#xff0c;然后放到bert-base-chinese命名的文件夹中 得到中文词向量的代码如下 import torch from transformers import BertTokenizer,…

can8.0-基础知识

1、canopen协议概述 1.1对象字典 CANopen 协议采用了带有 16 位索引和 8 位子索引的对象字典,对象字典的结构如表 1.2 常用的通信对象 1) 网络管理对象 (NMT) 2) 服务数据对象 (SDO) 3) 过程数据对象 (PDO) 4) 同步对象 (SYNC) 5) 紧急报文 (EMCY) 1.3 通信对象…

PISR 数据库,区块链和大数据的下一个最佳结合

据集合。如今&#xff0c;在大数据时代&#xff0c;数据管理越来越受到重视&#xff0c;已经成为一项非常重要的资产。因此&#xff0c;数据安全也变得越来越重要&#xff0c;应该引起重视。尽管现有数据库可以满足单个公司的存储依赖性&#xff0c;但它们很难确保公司之间协作…

React学习03-基于脚手架的React应用

初始化脚手架 使用 npx 创建 npx create-react-app 项目名使用 npm install 创建 全局安装create-react-app包&#xff1a; npm install -g create-react-app创建脚手架&#xff1a; create-react-app 项目名npm 镜像 执行 create-react-app时&#xff0c;还会自动安装一…

C++程序员学习资料汇总

小白入门 计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09; 非常适合小白学习&#xff0c;没有废话&#xff0c;非常生动 《计算机是怎样跑起来的》 《程序是怎样跑起来的》 《网络是怎样连接的》 基础 资料名备注状态阅读时间《深入理解计算机系统》很多大厂面…

实验室预约系统|基于Springboot+Vue实现学校实验室预约管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

JAVA中那些令人眼花缭乱的锁

一、开局一张图带你了解java相关的锁 二、乐观锁和悲观锁 1、悲观锁 悲观锁对应于生活中悲观的人&#xff0c;悲观的人总是想着事情往坏的方向发展。 举个生活中的例子&#xff0c;假设厕所只有一个坑位了&#xff0c;悲观锁上厕所会第一时间把门反锁上&#xff0c;这样其他…

python pyqtgraph绘图库

pyqtgraph官网 PyQtGraph被大量应用于Qt GUI平台&#xff08;通过PyQt或PySide&#xff09;&#xff0c;因为它的高性能图形和numpy可用于大量数据处理。 特别注意的是&#xff0c;pyqtgraph使用了Qt的GraphicsView框架&#xff0c;它本身是一个功能强大的图形系统; 我们将最优…

知识付费海哥:这样做课,不赚钱都难

现在不少人开始了开发网课&#xff0c;卖网课赚钱&#xff0c; 但是在网课开发时&#xff0c;很多人开始的时候&#xff0c;关注的点就错了&#xff01; 自己喜欢钓鱼&#xff0c;就开发钓鱼的课&#xff0c; 自己喜欢演讲&#xff0c;就开发演讲的课&#xff0c; 自己喜欢…

Dubbo入门(二)——IDEA下Dubbo+Zookeeper搭建

目录一、Zookeeper1.1 下载1.2 安装1.3 修改配置文件1.4 启动二、Dubbo插件搭建三、手动创建3.1 创建项目3.1.1 pom依赖3.2 api模块3.2.1 pom依赖3.2.2 实体类3.2.3 service接口3.3 provider3.3.1 pom依赖3.3.2 配置文件3.3.3 mapper3.3.4 service实现类3.3.5 启动类3.4 consum…