1. 本地服务器实现方案
1.1 创建触发器
BEGIN
IF((SELECT stcd from st_stbprp_b where stcd = NEW.stcd) IS NOT NULL) THEN
CALL s_insert_total(NEW.stcd, NEW.tm , NEW.intv);
END IF;
END
触发器创建在需要监听数据的表上面。具体代码由自己的业务去变动。
统一步骤,大概都是先判断是否符合条件,符合的话调用编写好的存储过程。
1.2 创建存储过程
CREATE DEFINER=`root`@`localhost` PROCEDURE `s_insert_total`(
# 函数入参
IN s_stcd VARCHAR(50),
IN s_tm DATETIME,
IN s_intv VARCHAR(10)
)
BEGIN
# 声明变量 data_num
DECLARE data_stcd VARCHAR(50) DEFAULT NULL;
DECLARE thirty_num, one_num, three_num DOUBLE DEFAULT 0;
# 输入执行的SQL
# 查询是否有该条数据
SELECT
stcd INTO data_stcd
FROM
st_data_s
WHERE
stcd = s_stcd
AND
intv = s_intv;
# 查询指定范围内的统计值
SELECT SUM(drp) as drp INTO one_num
FROM
pptn_r
WHERE
stcd = s_stcd
AND
tm > DATE_ADD(s_tm, INTERVAL -1 hour)
AND
tm <= s_tm
AND
intv = s_intv;
SELECT SUM(drp) as drp INTO three_num
FROM
pptn_r
WHERE
stcd = s_stcd
AND
tm > DATE_ADD(s_tm, INTERVAL -3 hour)
AND
tm <= s_tm
AND
intv = s_intv;
SELECT SUM(drp) as drp INTO thirty_num
FROM
pptn_r
WHERE
stcd = s_stcd
AND
tm > DATE_ADD(s_tm, INTERVAL -30 MINUTE)
AND
tm <= s_tm
AND
intv = s_intv;
# 根据data_stcd判断是否存在统计值,存在就更新,否则就插入
IF data_stcd IS NULL THEN
INSERT INTO st_data_s (
stcd,
stnm,
update_time,
thirty_min,
one_hour,
three_hour,
intv
)
VALUES
(s_stcd, '', s_tm, thirty_num, one_num, three_num, s_intv);
ELSE
UPDATE
st_data_s
SET
update_time = st_tm,
thirty_min = thirty_num,
one_hour = one_num,
three_hour = three_num
WHERE
stcd = s_stcd
AND
intv = s_intv;
END IF;
END
存储过程也需要和自己的业务相关,我也贴出自己的。我的业务是统计该条数据最近的半小时,一小时,三小时的统计数据。然后更新到指定数据库。
2. 跨服务器实现方案
2.1 下载canal
1. canal主要是替代触发器,用于监听远程数据库更新。canal下载太慢,我已经上传百度网盘。
链接:https://pan.baidu.com/s/1GGmucVZ6KPSn9XVPDToYgQ?pwd=mpal
提取码:mpal
--来自百度网盘超级会员V2的分享
2.1 安装配置canal
安装解压后找到“\conf\example”这个路径下的,这个选中的文件打开。
canal.instance.master.address=127.0.0.1:3306 // 这个配置数据库的地址和端口,根据实际情况修改。
canal.instance.dbUsername=root // 数据库账户
canal.instance.dbPassword=123456 // 数据库密码其他的不用修改。
启动canal,双击“\bin”目录下的“startup.bat”文件。
2.2 创建桥接表
2.2.1检查federated引擎是否开启
Mysql执行这个语句show engines。
FEDERATED字段为YES说明已开启。如果未开启,需要修改mysql的my.ini文件。在最后一行添加“federated”。然后重启Mysql服务。
方式一:
DROP TABLE IF EXISTS `pptn_r`;
CREATE TABLE `pptn_r` (
“这里是具体的字段,需要和你远程需要监听的表结构一致就行了。”
) ENGINE=InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '[启用]雨量监测表_热表' ROW_FORMAT = Compact
ENGINE=FEDERATED CONNECTION = 'mysql://root:123456@127.0.0.1:3306/test/pptn_r';
方式一可能出现的问题是,密码如果包含特殊符号是会报错的。本人本地测试是没问题。但是实际生产环境,用到的账户密码包含了@。导致创建失败。如果密码为正常密码。可以使用方式一创建桥接表。
方式二
CREATE SERVER pptnlink
FOREIGN DATA WRAPPER mysql
OPTIONS (USER 'root', PASSWORD '123456',HOST '127.0.0.1', PORT 3307, DATABASE 'test');
CREATE TABLE `pptn_r` (
同样的,这里为远程数据表的结构。
) ENGINE=InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '[启用]雨量监测表_热表' ROW_FORMAT = Compact
ENGINE=FEDERATED CONNECTION = 'pptnlink/pptn_r';
2.3 创建存储过程
这里和本地服务器的一致。我就不重复编写了。
2.4 编写canal客户端
本人使用的Springboot编写的canal客户端。pom引入以下依赖。
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version>
</dependency>
package com.hi.canal;
import com.alibaba.fastjson.JSONObject;
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.hi.common.core.redis.RedisCache;
import com.hi.common.utils.spring.SpringUtils;
import com.hi.system.domain.StStbprpB;
import com.hi.system.mapper.StoredProceduresMapper;
import com.hi.system.service.IStStbprpBService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
/**
* @Author: 胡天霸
* @Date: 2022/10/06/16:17
* @Description:
*/
@Component
public class CanalUtil {
@Value("${canal-config.hostname}")
private String host;
@Value("${canal-config.port}")
private Integer port;
@Value("${canal-config.tableName}")
private String tableName;
@Value("${canal-config.destination}")
private String destination;
private final static int BATCH_SIZE = 10000;
// 开始日志监控
public void startMonitorSql() {
// 创建链接
CanalConnector connector = CanalConnectors
.newSingleConnector(new InetSocketAddress(host, port), destination, "", "");
int batchSize = 1000;
try {
connector.connect();
connector.subscribe(tableName);
connector.rollback();
System.out.println("开始监听");
while (true) {
// 获取指定数量的数据
Message message = connector.getWithoutAck(batchSize);
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
try {
System.out.println("无变化");
Thread.sleep(15000);
} catch (InterruptedException e) {
}
} else {
printEntry(message.getEntries());
}
// 提交确认
connector.ack(batchId);
}
} finally {
connector.disconnect();
}
}
private static void printEntry(List<CanalEntry.Entry> entrys) {
for (CanalEntry.Entry entry : entrys) {
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
continue;
}
CanalEntry.RowChange rowChage = null;
try {
rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
CanalEntry.EventType eventType = rowChage.getEventType();
for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
if (eventType == CanalEntry.EventType.DELETE) {
// 删除,获取删除前的数据
redisDelete(rowData.getBeforeColumnsList(),entry.getHeader().getTableName());
} else if (eventType == CanalEntry.EventType.INSERT) {
// 新增,获取新增后的数据
redisSet(rowData.getAfterColumnsList(),entry.getHeader().getTableName());
} else {
// 修改,获取修改后的数据
// printColumn(rowData.getBeforeColumnsList());
redisSet(rowData.getAfterColumnsList(),entry.getHeader().getTableName());
}
}
}
}
private static void redisSet(List<CanalEntry.Column> columns, String tableName) {
HashMap<String, Object> map = new HashMap<>();
JSONObject json = new JSONObject();
// 字段和值的列表,放入json,后续作为redis的值
for (CanalEntry.Column column : columns) {
json.put(column.getName(), column.getValue());
}
RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
List<StStbprpB> stStbprpBS = redisCache.getCacheObject("stcList");
if (null == stStbprpBS || stStbprpBS.size() < 1){
stStbprpBS = SpringUtils.getBean(IStStbprpBService.class).selectStStbprpBList(new StStbprpB());
redisCache.setCacheObject("stcList", stStbprpBS);
}
for (StStbprpB stStbprpB :stStbprpBS){
if (stStbprpB.getStcd().equals(columns.get(0).getValue()) && columns.size() > 0){
System.out.println("set key: " + columns.get(0).getValue() + ", value: " + json.toJSONString());
map.put("stcd", json.get("stcd"));
map.put("tm", json.get("tm"));
map.put("intv", json.get("intv"));
map.put("stnm", stStbprpB.getStnm());
// 调用存储过程
SpringUtils.getBean(StoredProceduresMapper.class).insertTotal(map);
map.clear();
}
}
}
private static void redisDelete(List<CanalEntry.Column> columns, String tableName) {
JSONObject json = new JSONObject();
for (CanalEntry.Column column : columns) {
json.put(column.getName(), column.getValue());
}
if (columns.size() > 0) {
System.out.println("delete key: " + columns.get(0).getValue());
}
}
}
可以监听到插入、删除、更新等数据行。具体业务逻辑自己修改。
package com.hi.canal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
/**
* @Author: 胡天霸
* @Date: 2022/10/06/16:26
* @Description:
*/
@Configuration
public class InitCanalConfig implements CommandLineRunner {
@Autowired
private CanalUtil canalUtil;
@Override
public void run(String... args) throws Exception {
canalUtil.startMonitorSql();
}
}
2.5 Java调用存储过程
Mapper层
package com.hi.system.mapper;
import java.util.HashMap;
/**
* @Author: 胡天霸
* @Date: 2022/10/07/9:50
* @Description:
*/
public interface StoredProceduresMapper {
void insertTotal(HashMap<String, Object> map);
}
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hi.system.mapper.StoredProceduresMapper">
<select id="insertTotal" parameterType="map" statementType="CALLABLE">
{
call s_insert_total(
#{stcd, mode=IN, jdbcType=VARCHAR},
#{tm, mode=IN, jdbcType=VARCHAR},
#{intv, mode=IN, jdbcType=VARCHAR},
#{stnm, mode=IN, jdbcType=VARCHAR}
)
}
</select>
</mapper>
调用代码
map.put("stcd", json.get("stcd"));
map.put("tm", json.get("tm"));
map.put("intv", json.get("intv"));
map.put("stnm", stStbprpB.getStnm());
// 说明一下,这个是因为调用代码在static方法里面,不能试用注入,所以我用的Spring工具类,去获取的StoredProceduresMapper调用里面实现的方法。正常情况直接注入调用就行。
SpringUtils.getBean(StoredProceduresMapper.class).insertTotal(map);