广告
返回顶部
首页 > 资讯 > 数据库 >Spring Boot 整合 Canal
  • 842
分享到

Spring Boot 整合 Canal

java数据库mysql 2023-09-02 09:09:32 842人浏览 薄情痞子
摘要

前言 canal 是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了Mysql(也支持mariaDB)。 canal [kə’næl],译意

前言

canal 是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了Mysql(也支持mariaDB)。

在这里插入图片描述

canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 mysql 数据库增量日志解析,提供增量数据订阅和消费。

基于日志增量订阅和消费的业务包括

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理

当前的 canal 支持源端 Mysql 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

Canal工作原理

在这里插入图片描述
Canal工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump
    协议。
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )。
  • canal 解析 binary log 对象(原始为 byte 流)。

Canal架构

在这里插入图片描述

  • server 代表一个 canal 运行实例,对应于一个 JVM
  • instance 对应于一个数据队列 (1个 canal server 对应 1…n 个 instance )
  • instance 下的子模块:
          eventParser: 数据源接入,模拟 slave 协议和 master 进行交互,协议解析
          eventSink: Parser 和 Store 链接器,进行数据过滤,加工,分发的工作
          eventStore: 数据存储
          metaManager: 增量订阅 & 消费信息管理器

EventParser在向MySQL发送dump命令之前会先从Log Position中获取上次解析成功的位置(如果是第一次启动,则获取初始指定位置或者当前数据段binlog位点)。mysql接受到dump命令后,由EventParser从mysql上pull binlog数据进行解析并传递给EventSink(传递给EventSink模块进行数据存储,是一个阻塞操作,直到存储成功 ),传送成功之后更新Log Position。流程图如下:

在这里插入图片描述

  • EventSink起到一个类似channel的功能,可以对数据进行过滤、分发/路由(1:n)、归并(n:1)和加工。EventSink是连接EventParser和EventStore的桥梁。
  • EventStore实现模式是内存模式,内存结构为环形队列,由三个指针(Put、Get和Ack)标识数据存储和读取的位置。
  • MetaManager是增量订阅&消费信息管理器,增量订阅和消费之间的协议包括get/ack/rollback,分别为:
Message getWithoutAck(int batchSize),允许指定batchSize,一次可以获取多条,每次返回的对象为Message,包含的内容为:batch id[唯一标识]和entries[具体的数据对象]void rollback(long batchId),顾名思义,回滚上次的get请求,重新获取数据。基于get获取的batchId进行提交,避免误操作void ack(long batchId),顾名思议,确认已经消费成功,通知server删除数据。基于get获取的batchId进行提交,避免误操作

Canal环境搭建

准备

  1. 检查binlog功能是否有开启:show variables like ‘log_bin’,如果on则已开启,显示off则未开启。未开启需要先开启 Binlog 写入功能,配置 binlog-fORMat 为ROW 模式,my.cnf (win下为my.ini)中配置如下
log-bin=mysql-bin     #binlog文件名binlog_format=ROW     #选择row模式server_id=1           #mysql实例id,不能和canal的slaveId重复

注意:针对阿里云 RDS for MySQL , 默认打开了 binlog , 并且账号默认具有 binlog dump 权限 , 不需要任何权限或者 binlog 设置,可以直接跳过这一步

MySQL的binLog

  • STATEMENT 记录的是执行的sql语句
  • ROW 记录的是真实的行数据记录
  • MIXED 记录的是1+2,优先按照1的模式记录
  1. 授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant
CREATE USER canal IDENTIFIED BY 'canal';  GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;FLUSH PRIVILEGES;

启动

  1. 下载 canal, 访问 release 页面 , 选择需要的包下载, 如以 1.0.17 版本为例

在这里插入图片描述

  • canal-adapter(canal-client)
    相当于canal的客户端,会从canal-server中获取数据(需要配置为tcp方式),然后对数据进行同步,可以同步到MySQL、elasticsearchHBase等存储中去。相较于canal-server自带的canal.serverMode,canal-adapter提供的下游数据接受更为广泛。

  • canal-admin
    为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WEBUI操作界面,方便更多用户快速和安全的操作。

  • canal-deployer(canal-server)
    可以直接监听MySQL的binlog,把自己伪装成MySQL的从库,只负责接收数据,并不做处理。接收到MySQL的binlog数据后可以通过配置canal.serverMode:tcp, kafka, RocketMQ, RabbitMQ连接方式发送到对应的下游。其中tcp方式可以自定义canal客户端进行接受数据,较为灵活。

  1. 配置修改,修改conf/example/instance.properties配置文件
################################################### mySQL ServerId , v1.0.26+ will autoGen# mysql 集群配置中的serverId概念,需要保证和当前mysql集群中id唯一 (v1.1.x版本之后canal会自动生成,不需要手工指定)canal.instance.mysql.slaveId=1212# enable gtid use true/false# 是否启用mysql gtid的订阅模式canal.instance.gtidon=false# position info# mysql 主库链接地址canal.instance.master.address=127.0.0.1:3306# mysql 主库链接时起始的binlog文件canal.instance.master.journal.name=# mysql 主库链接时起始的binlog偏移量canal.instance.master.position=# mysql 主库链接时起始的binlog的时间戳canal.instance.master.timestamp=# mysql 主库链接时对应的gtid位点canal.instance.master.gtid=# rds oss binloGCanal.instance.rds.accesskey=canal.instance.rds.secreTKEy=# aliyun rds 对应的实例id信息(如果不需要在本地binlog超过18小时被清理后自动下载oss上的binlog,可以忽略该值)canal.instance.rds.instanceId=# table meta tsdb infocanal.instance.tsdb.enable=true#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb#canal.instance.tsdb.dbUsername=canal#canal.instance.tsdb.dbPassWord=canal#canal.instance.standby.address =#canal.instance.standby.journal.name =#canal.instance.standby.position =#canal.instance.standby.timestamp =#canal.instance.standby.gtid=# username/password# mysql 数据库帐号canal.instance.dbUsername=canal# mysql 数据库密码canal.instance.dbPassword=canal# mysql 数据解析编码,代表数据库的编码方式对应到 java 中的编码类型,比如 UTF-8GBK , ISO-8859-1canal.instance.connectionCharset = UTF-8# enable druid Decrypt database passwordcanal.instance.enableDruid=false#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==# table regex# mysql 数据解析关注的表,Perl正则表达式,多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\)# 注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤) canal.instance.filter.regex=.*\\..*# table black regex# mysql 数据解析表的黑名单,表达式规则见白名单的规则canal.instance.filter.black.regex=mysql\\.slave_.*# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch# MQ configcanal.mq.topic=yang# dynamic topic route by schema or table regex#canal.mq.dynamicTopic=mytest1.user,topic2:mytest2\\..*,.*\\..*canal.mq.partition=0# hash partition config#canal.mq.enableDynamicQueuePartition=false#canal.mq.partitionsNum=3#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6#canal.mq.partitionHash=test.table:id^name,.*\\..*##################################################如果系统是1个 cpu,需要将 canal.instance.parser.parallel 设置为 false

常见的匹配规则:

  • 所有表:.* or .\…

  • canal schema下所有表: canal\…*

  • canal下的以canal打头的表:canal.canal.*

  • canal schema下的一张表:canal.test1

  • 多个规则组合使用:canal\…*,mysql.test1,mysql.test2 (逗号分隔)

  • 进入bin目录下启动虚拟机的mysql

  1. sh bin/startup.sh(win下是运行 startup.bat)

工程搭建

  1. 修改pom.xml,添加依赖
# 服务端口server.port=10000# 服务名spring.application.name=canal-client# 环境设置:dev、test、prodspring.profiles.active=dev# mysql数据库连接spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/yang?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=root# 监听样例使用# canal.client.instances.example.host=127.0.0.1# canal.client.instances.example.port=11111

canal 依赖

<dependency>    <groupId>com.alibaba.otter</groupId>    <artifactId>canal.client</artifactId>    <version>1.1.0</version></dependency>

其他依赖(用则添加)

    commons-dbutils    commons-dbutils    1.7    org.springframework.boot    spring-boot-starter-jdbc    mysql    mysql-connector-java    8.0.17
  1. 编写canal自定义客户端类(也可以使用canal-adapter)

官网样例

package com.example.canal.yang;import com.alibaba.otter.canal.client.CanalConnector;import com.alibaba.otter.canal.client.CanalConnectors;import com.alibaba.otter.canal.protocol.CanalEntry.*;import com.alibaba.otter.canal.protocol.Message;import org.springframework.stereotype.Component;import java.net.InetSocketAddress;import java.util.List;@Componentpublic class CanalClient {    private final static int BATCH_SIZE = 1000;        public void run() throws Exception {        // 创建链接        CanalConnector connector =            CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "canal", "canal");        try {            // 打开连接            connector.connect();            // 订阅数据库表,来覆盖服务端初始化时的设置            connector.subscribe(".*\..*");            // 回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿            connector.rollback();            while (true) {                // 获取指定数量的数据                Message message = connector.getWithoutAck(BATCH_SIZE);                // 获取批量ID                long batchId = message.getId();                // 获取批量的数量                int size = message.getEntries().size();                // 如果没有数据                if (batchId == -1 || size == 0) {                    try {                        // 线程休眠2秒                        Thread.sleep(2000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                } else {                    // 如果有数据,处理数据                    printEntry(message.getEntries());                }                // 进行 batch id 的确认                connector.ack(batchId);            }        } catch (Exception e) {            e.printStackTrace();        } finally {            connector.disconnect();        }    }        private static void printEntry(List<Entry> entrys) {        for (Entry entry : entrys) {            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN                || entry.getEntryType() == EntryType.TRANSACTIONEND) {                // 开启/关闭事务的实体类型,跳过                continue;            }            // RowChange对象,包含了一行数据变化的所有特征            RowChange rowChage;            try {                rowChage = RowChange.parseFrom(entry.getStoreValue());            } catch (Exception e) {                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),                    e);            }            // 获取操作类型:insert/update/delete类型            EventType eventType = rowChage.getEventType();            // 打印Header信息            System.out.println(String.format("================》; binlog[%s:%s] , name[%s,%s] , eventType : %s",                entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),                entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));            // 判断是否是DDL语句            if (rowChage.getIsDdl()) {                System.out.println("================》;isDdl: true,sql:" + rowChage.getSql());            }            // 获取RowChange对象里的每一行数据,打印出来            for (RowData rowData : rowChage.getRowDatasList()) {                // 如果是删除语句                if (eventType == EventType.DELETE) {                    printColumn(rowData.getBeforeColumnsList());                    // 如果是新增语句                } else if (eventType == EventType.INSERT) {                    printColumn(rowData.getAfterColumnsList());                    // 如果是更新的语句                } else {                    // 变更前的数据                    System.out.println("------->; before");                    printColumn(rowData.getBeforeColumnsList());                    // 变更后的数据                    System.out.println("------->; after");                    printColumn(rowData.getAfterColumnsList());                }            }        }    }    private static void printColumn(List<Column> columns) {        for (Column column : columns) {            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());        }    }}

表数据同步样例

package com.example.canal.yang;import com.alibaba.otter.canal.client.CanalConnector;import com.alibaba.otter.canal.client.CanalConnectors;import com.alibaba.otter.canal.protocol.CanalEntry.*;import com.alibaba.otter.canal.protocol.Message;import com.Google.protobuf.InvalidProtocolBufferException;import org.apache.commons.dbutils.DbUtils;import org.apache.commons.dbutils.QueryRunner;import org.springframework.stereotype.Component;import javax.annotation.Resource;import javax.sql.DataSource;import java.net.InetSocketAddress;import java.sql.Connection;import java.sql.SQLException;import java.util.List;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;@Componentpublic class CanalClient {    private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();    @Resource    private DataSource dataSource;    public void run() {        CanalConnector connector =            CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");        int batchSize = 1000;        try {            connector.connect();            connector.subscribe("canal.canal_test");            connector.rollback();            try {                while (true) {                    Message message = connector.getWithoutAck(batchSize);                    long batchId = message.getId();                    int size = message.getEntries().size();                    if (batchId == -1 || size == 0) {                        Thread.sleep(1000);                    } else {                        dataHandle(message.getEntries());                    }                    connector.ack(batchId);                    if (SQL_QUEUE.size() >= 1) {                        executeQueueSql();                    }                }            } catch (InterruptedException e) {                e.printStackTrace();            } catch (InvalidProtocolBufferException e) {                e.printStackTrace();            }        } finally {            connector.disconnect();        }    }    private void dataHandle(List<Entry> entrys) throws InvalidProtocolBufferException {        for (Entry entry : entrys) {            if (EntryType.ROWDATA == entry.getEntryType()) {                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());                EventType eventType = rowChange.getEventType();                if (eventType == EventType.DELETE) {                    saveDeleteSql(entry);                } else if (eventType == EventType.UPDATE) {                    saveUpdateSql(entry);                } else if (eventType == EventType.INSERT) {                    saveInsertSql(entry);                }            }        }    }    private void saveDeleteSql(Entry entry) {        try {            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());            List<RowData> rowDatasList = rowChange.getRowDatasList();            for (RowData rowData : rowDatasList) {                List<Column> columnList = rowData.getBeforeColumnsList();                StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");                for (Column column : columnList) {                    if (column.getIsKey()) {                        // 暂时只支持单一主键                        sql.append(column.getName() + "=" + column.getValue());                        break;                    }                }                SQL_QUEUE.add(sql.toString());            }        } catch (InvalidProtocolBufferException e) {            e.printStackTrace();        }    }    private void saveUpdateSql(Entry entry) {        try {            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());            List<RowData> rowDatasList = rowChange.getRowDatasList();            for (RowData rowData : rowDatasList) {                List<Column> newColumnList = rowData.getAfterColumnsList();                StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set ");                for (int i = 0; i < newColumnList.size(); i++) {                    sql.append(" " + newColumnList.get(i).getName() + " = '" + newColumnList.get(i).getValue() + "'");                    if (i != newColumnList.size() - 1) {                        sql.append(",");                    }                }                sql.append(" where ");                List<Column> oldColumnList = rowData.getBeforeColumnsList();                for (Column column : oldColumnList) {                    if (column.getIsKey()) {                        // 暂时只支持单一主键                        sql.append(column.getName() + "=" + column.getValue());                        break;                    }                }                SQL_QUEUE.add(sql.toString());            }        } catch (InvalidProtocolBufferException e) {            e.printStackTrace();        }    }    private void saveInsertSql(Entry entry) {        try {            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());            List<RowData> rowDatasList = rowChange.getRowDatasList();            for (RowData rowData : rowDatasList) {                List<Column> columnList = rowData.getAfterColumnsList();                StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " (");                for (int i = 0; i < columnList.size(); i++) {                    sql.append(columnList.get(i).getName());                    if (i != columnList.size() - 1) {                        sql.append(",");                    }                }                sql.append(") VALUES (");                for (int i = 0; i < columnList.size(); i++) {                    sql.append("'" + columnList.get(i).getValue() + "'");                    if (i != columnList.size() - 1) {                        sql.append(",");                    }                }                sql.append(")");                SQL_QUEUE.add(sql.toString());            }        } catch (InvalidProtocolBufferException e) {            e.printStackTrace();        }    }    public void executeQueueSql() {        int size = SQL_QUEUE.size();        for (int i = 0; i < size; i++) {            String sql = SQL_QUEUE.poll();            System.out.println("[sql]----> " + sql);            this.execute(sql.toString());        }    }    public void execute(String sql) {        Connection con = null;        try {            if (null == sql)                return;            con = dataSource.getConnection();            QueryRunner qr = new QueryRunner();            int row = qr.execute(con, sql);            System.out.println("update: " + row);        } catch (SQLException e) {            e.printStackTrace();        } finally {            DbUtils.closeQuietly(con);        }    }}

注解监听样例(依赖下载不下来用这个导入到项目)

    com.xpand    starter-canal    0.0.1-SNAPSHOT
package com.example.canal.yang;import com.alibaba.otter.canal.protocol.CanalEntry;import com.xpand.starter.canal.annotation.*;@CanalEventListenerpublic class CanalDataEventListener {        @InsertListenPoint    public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {        rowData.getAfterColumnsList()            .forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));    }        @UpdateListenPoint    public void onEventUpdate(CanalEntry.RowData rowData) {        System.out.println("UpdateListenPoint");        rowData.getAfterColumnsList()            .forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));    }        @DeleteListenPoint    public void onEventDelete(CanalEntry.EventType eventType) {        System.out.println("DeleteListenPoint");    }        @ListenPoint(destination = "example", schema = "canal", table = {"canal_test", "tb_order"},        eventType = CanalEntry.EventType.UPDATE)    public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {        System.err.println("DeleteListenPoint");        rowData.getAfterColumnsList()            .forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));    }    @ListenPoint(destination = "example", schema = "canal", // 所要监听的数据库名        table = {"canal_test"}, // 所要监听的数据库表名        eventType = {CanalEntry.EventType.UPDATE, CanalEntry.EventType.INSERT, CanalEntry.EventType.DELETE})    public void onEventCustomUpdateForTbUser(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {        getChangeValue(eventType, rowData);    }    public static void getChangeValue(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {        if (eventType == CanalEntry.EventType.DELETE) {            rowData.getBeforeColumnsList().forEach(column -> {                // 获取删除前的数据                System.out.println(column.getName() + " == " + column.getValue());            });        } else {            rowData.getBeforeColumnsList().forEach(column -> {                // 打印改变前的字段名和值                System.out.println(column.getName() + " == " + column.getValue());            });            rowData.getAfterColumnsList().forEach(column -> {                // 打印改变后的字段名和值                System.out.println(column.getName() + " == " + column.getValue());            });        }    }}
  1. 触发数据库变更

开始测试,首先启动MySQL、Canal Server,还有刚刚写的Spring Boot项目。然后创建表:

DROP TABLE IF EXISTS `canal_test`;CREATE TABLE `canal_test`  (  `id` int NOT NULL,  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,  `age` int NOT NULL,  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

如果新增一条数据到表中:

INSERT INTO `yang`.`canal_test` (`id`, `name`, `age`) VALUES (1, '1', 1);

在这里插入图片描述

总结

canal的好处在于对业务代码没有侵入,因为是基于监听binlog日志去进行同步数据的。实时性也能做到准实时,其实是很多企业一种比较常见的数据同步的方案。

通过上面的学习之后,我们应该都明白canal是什么,它的原理,还有用法。实际上这仅仅只是入门,实际项目我们是配置MQ模式,配合RocketMQ或者Kafka,canal会把数据发送到MQ的topic中,然后通过消息队列的消费者进行处理。

在这里插入图片描述

Canal的部署也是支持集群的,需要配合ZooKeeper进行集群管理。

Canal还有一个简单的Web管理界面。

来源地址:https://blog.csdn.net/yy139926/article/details/127768446

您可能感兴趣的文档:

--结束END--

本文标题: Spring Boot 整合 Canal

本文链接: https://www.lsjlt.com/news/390478.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Spring Boot 整合 Canal
    前言 canal 是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB)。 canal [kə’næl],译意...
    99+
    2023-09-02
    java 数据库 mysql
  • Spring boot 整合redis
    ...
    99+
    2021-11-16
    Spring boot 整合redis
  • Spring Boot整合阿里开源中间件Canal实现数据增量同步
    目录前言Canal是什么?Canal数据如何传输?数据同步还有其他中间件吗?Canal服务端安装1、打开MySQL的binlog日志2、设置MySQL的配置3、设置RabbitMQ的...
    99+
    2022-11-13
  • Spring Boot整合WebFlux + R2DBC+Mysql
    Spring Boot整合WebFlux + R2DBC+Mysql 1、R2DBC介绍 R2DBC 基于 Reactive Streams 反应流规范,它是一个开放的规范,为驱动程序供应商和使用方提...
    99+
    2023-09-18
    mysql spring boot java
  • Spring Boot怎么整合Kafka
    这篇文章主要介绍了Spring Boot怎么整合Kafka的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Spring Boot怎么整合Kafka文章都会有所收获,下面我们一起来看看吧。步骤一...
    99+
    2023-07-05
  • spring boot怎么整合activiti
    这篇文章主要介绍了spring boot怎么整合activiti的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇spring boot怎么整合activiti文章都会有所收获,下面我们一起来看...
    99+
    2023-06-29
  • 【Spring Boot整合MyBatis教程】
    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力...
    99+
    2023-08-18
    mybatis spring boot java
  • Spring + Spring Boot + MyBatis + MongoDB的整合教程
    前言我之前是学Spring MVC的,后面听同学说Spring Boot挺好用,极力推荐我学这个鬼。一开始,在网上找Spring Boot的学习资料,他们博文写得不是说不好,而是不太详细。我就在想我要自己写一篇尽可能详细的文章出来,下面话不...
    99+
    2023-05-30
    springboot mybatis mongodb
  • Spring Boot Reactor 整合 Resilience4j详析
    目录1 引入 pom 包2 配置说明2.1 限流 ratelimiter2.2 重试 retry2.3 超时 TimeLimiter2.4 断路器 circuitbreaker2.5...
    99+
    2022-11-13
  • spring boot整合MySQL数据库
    spring boot整合MySQL数据库 spring boot整合MySQL数据库可以说很简单,只需要添加MySQL依赖和在配置文件中添加数据库配置信息,利用spring-boot-starter...
    99+
    2023-08-31
    数据库 mysql spring boot
  • spring boot整合hessian的示例
    首先添加hessian依赖<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifac...
    99+
    2023-05-31
    spring boot hessian
  • Spring Boot中怎么整合elasticsearch
    今天小编给大家分享一下Spring Boot中怎么整合elasticsearch的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧...
    99+
    2023-06-05
  • Spring Boot整合Kafka教程详解
    目录正文步骤一:添加依赖项步骤二:配置 Kafka步骤三:创建一个生产者步骤四:创建一个消费者正文 本教程将介绍如何在 Spring Boot 应用程序中使用 Kafka。Kaf...
    99+
    2023-03-10
    Spring Boot整合Kafka Spring Boot Kafka
  • Spring Boot 功能整合的实现
    目录前言数据库持久化支持Swagger 文档支持参数校验 JSR303跨域解决整合MongoDB实现文件上传下载删除前言 如果根据之前做的 Nest.js 后端项目功能为标准的话,...
    99+
    2022-11-12
  • Spring boot项目整合WebSocket方法
    WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocke...
    99+
    2022-11-12
  • Spring Boot 整合 Reactor实例详解
    目录引言1 创建项目2 集成 H2 数据库3 创建测试类3.1 user 实体3.2 UserRepository3.3 UserService3.4 UserController3...
    99+
    2022-11-13
  • Spring Boot 整合 Thymeleaf 实例分享
    目录一、什么是 Thymeleaf二、整合过程准备过程添加 Thymeleaf 依赖编写实体类和 Controller创建Thymeleaf 模板三、测试一、什么是 Thymelea...
    99+
    2022-11-13
  • Spring Boot 整合 FreeMarker 实例分享
    目录一、前言二、FreeMarker 简介三、准备工作环境准备添加 FreeMarker 依赖添加 FreeMarker 相关配置四、编写实体类和 Controller编写实体类编写...
    99+
    2022-11-13
  • Spring Cloud整合Spring Boot Admin方法是什么
    这篇文章主要介绍“Spring Cloud整合Spring Boot Admin方法是什么”,在日常操作中,相信很多人在Spring Cloud整合Spring Boot Admin方法是什么问题上存在疑惑,小编查阅了各...
    99+
    2023-06-22
  • MyBatis-Plus详细讲解(整合spring Boot)
    哈喽,大家好,今天带大家了解的是MyBatis-Plus(简称 MP),是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 首先说一下MyBatis-Plus的愿景是什么? My...
    99+
    2023-10-20
    spring boot 数据库 mysql MyBatis-Plus 框架整合 Powered by 金山文档
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作