当前位置: 首页 > news >正文

自动化同步多服务器数据库表结构

当项目每次进行版本升级的时候,如果在这次迭代中涉及表结构变更,需要将不同的生产环境下,都需要同步表结构的DDL语句,比较麻烦,而且还有可能忘记同步脚本,导致生产环境报错....

该方案采用SpringBoot+Mybatis/MybatisPlus框架,完成在项目启动时,自动化执行sql脚本,并且同时支持版本号【如果当前版本号高于该sql文件,则不执行】。

1、先创建一张表,专门用来记录已经同步过的sql脚本文件名、对应的版本号。

CREATE TABLE `hd_version` (`id` varchar(64) NOT NULL,`version` varchar(64) DEFAULT NULL COMMENT '版本号',`created` datetime DEFAULT NULL COMMENT '创建时间',`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据版本';
import java.util.Date;
import lombok.Data;
@Data
public class HdVersionEntity {/*** 主键id*/private String id;/*** 版本号(一般是文件名去掉文件后缀)*/private String version;/*** 文件名*/private String remark;/*** 创建时间*/private Date created;
}
import lombok.Data;@Data
public class SchemaData {/*** 版本号*/public String version;/*** 文件名*/public String fileName;public SchemaData(String version, String fileName) {this.version = version;this.fileName = fileName;}
}

 2、接着编写dao层

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;@Mapper
public interface HdCommonDao  {/*** 查询表中是否存在当前版本号* @param version* @return*/int selectVersion(@Param("version") String version);/*** 插入版本* @param entity* @return*/int insertVersion(HdVersionEntity entity);/*** 执行sql,可以是DML、DDL* @param sql*/@Update("${sql}")void updateSql(@Param("sql") String sql);
}

3、以及对应的Mapper文件

<?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.xxx.DatabaseAutoFill.HdCommonDao"><select id="selectVersion" resultType="int">selecT count(1) from hd_versionwhere version = #{version}</select><select id="selectTableExist" resultType="int">select count(*) count  from information_schema.TABLES where TABLE_NAME = #{tableName} and  table_schema = (select database())</select><insert id="insertVersion">insert into hd_version(id,version, remark, created) values (uuid(),#{version}, #{remark}, #{created})</insert></mapper>

4、 编写实现类

注意,这里是将整段逻辑放在ApplicationRunner接口下执行,即当Spring容器加载完之后,会立即执行该方法。

@Order(1)
@Component
@Slf4j
public class HdSchemaExecutor implements ApplicationRunner {@AutowiredHdCommonDao hdCommonDao;// 数据库脚本文件列表private static final String PREFIX = "--v";@Override@Transactionalpublic void run(ApplicationArguments args) throws IOException {String basePath = "/dbVersion/MySQL.sql";InputStream inputStream = this.getClass().getResourceAsStream(basePath);String sqlScript = IoUtil.readUtf8(inputStream);assert inputStream != null;inputStream.close();/*** 一次至多只会执行一个版本,其实我们可以拿到所有的版本并执行最后一个版本即可*/List<String> versionList = new ArrayList<>();String[] lines = sqlScript.split("\n");for (String line : lines) {if(line.toLowerCase().contains(PREFIX)){versionList.add(line);}}// 得到版本号整串String latestVersion = versionList.get(versionList.size()-1);// 写入数据库的版本号前缀String version = latestVersion.substring(latestVersion.lastIndexOf("-")+1).trim().toLowerCase();int index = sqlScript.lastIndexOf(latestVersion); // 查找s2在s1中的起始位置String result = "";if (index != -1) {// 截取s2在s1中结束位置之后的部分result = sqlScript.substring(index + latestVersion.length());} else {log.info("current version exception:{}",version);LogUtil.info(version, "current version exception");}//String[] resultList = result.split("\n");String[] resultList = result.split(";");int cnt = hdCommonDao.selectVersion(version);boolean successInsert = false;// 说明不需要写入库if(cnt ==1 )return;for (String line : resultList) {if(!line.toLowerCase().contains("drop") && !line.toLowerCase().contains("delete") && line.length() > 25 && !line.contains("--")) {//开始执行插入操作try {hdCommonDao.updateSql(line.trim());successInsert = true;log.info("version:{},start sql script:{}",version,line.trim());LogUtil.info("version, sql script:",version,line.trim());} catch (Exception e) {log.info("version:{},sql执行异常:{}",version,line.trim());LogUtil.info("sql执行异常",line.trim());}}}if(successInsert){HdVersionEntity entity = new HdVersionEntity();entity.setVersion(version);entity.setCreated(new Date());hdCommonDao.insertVersion(entity);}log.info("auto deploying sql finished...");}
}

这里主要干三件事:

读取指定路径下的文件夹中的所有文件
根据这些文件的文件名去表里查,是否插入过,没有说明需要被插入,即需要执行的sql脚本
执行sql脚本

我这里的路径是resources下的相对路径,因为我这个代码是要打包放到线上环境的,用绝对路径可能会报(FILE NOT FOUND ERROR)FNFE。 

PS

以上方法对于Spring容器加载时,没有强依赖的表,是可以通用的 (可能有点拗口)。

即,如果Spring容器启动时,如果需要依赖某张表,否则启动失败的话怎么办,还能用我们上述方法吗?

理论上是不行的,我这里将容器启动时,必须强依赖的表(Quartz框架)删去,启动时报错。
那对应这种情况,该怎么解决呢?

 其实这种框架,都会提供注解,如:

表明,在项目启动的时候,会自动完成jdbc的初始化,即如果你没有表,会先给你执行表的创建,因此不需要我们去考虑。

spring.quartz.jdbc.initialize-schema=always

Quartz也起来了。 

写在最后

由于这个工程是临时突加的,我也不好随便就测试环境的库来删删改改,因此我在本地windows上用docker部署了mysql,来测试的。以下是在windows上的docker部署mysql步骤:

docker pull mysql:8.0

在c盘用户目录下,创建conf、data、logs三个文件夹

 在conf目录下,创建my.cnf文件,里面编写如下内容。

[mysql]
#设置mysql客户端默认字符集
default-character-set=UTF8MB4
[mysqld]
#设置3306端口
port=3306
#允许最大连接数
max_connections=200
#允许连接失败的次数
max_connect_errors=10
#默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
#服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=UTF8MB4
#开启查询缓存
explicit_defaults_for_timestamp=true
#创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
#等待超时时间秒
wait_timeout=60
#交互式连接超时时间秒
interactive-timeout=600
# 对数据库表大小写不敏感设置,默认设置为小写,比较也全部设置为小写在比较
lower-case-table-names=1
# 设置默认时区
default-time_zone='+8:00'

启动容器,注意在windows下 需要把每行后面的 `\`删去,否在windows下会启动失败

 docker run --name mysql8.0 \
-v D:\docker\data\mysql8.0\config\my.cnf:/etc/mysql/my.cnf \
-v D:\docker\data\mysql8.0\data:/var/lib/mysql \
-v D:\docker\data\mysql8.0\logs:/logs -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \

-e TZ=Asia/Shanghai \
-d mysql:8.0 \
--lower-case-table-names=1

这样,理论上就能启动成功了。

分享几个常用的命令:
docker exec -it 容器名称/容器id  bash  #进入容器

docker logs 容器名称/容器id -f -n=100 查看容器最后一百行日志

相关文章:

自动化同步多服务器数据库表结构

当项目每次进行版本升级的时候&#xff0c;如果在这次迭代中涉及表结构变更&#xff0c;需要将不同的生产环境下&#xff0c;都需要同步表结构的DDL语句&#xff0c;比较麻烦&#xff0c;而且还有可能忘记同步脚本&#xff0c;导致生产环境报错.... 该方案采用SpringBootMybat…...

深入理解 HTML 元素:构建网页的基础

在网页开发的领域中&#xff0c;HTML&#xff08;超文本标记语言&#xff09;犹如一座大厦的基石&#xff0c;支撑起整个网页的结构与内容呈现。而 HTML 元素&#xff0c;则是构成这座基石的基本单位。今天&#xff0c;就让我们一同深入探索 HTML 元素的奥秘。 HTML 元素的构成…...

黄昏时间户外街拍人像Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色介绍 黄昏时分有着独特而迷人的光线&#xff0c;使此时拍摄的人像自带一种浪漫、朦胧的氛围 。通过 Lr 调色&#xff0c;可以进一步强化这种特质并根据不同的风格需求进行创作。Lr&#xff08;Lightroom&#xff09;作为专业的图像后期处理软件&#xff0c;提供了丰富的调色…...

OCPP扩展机制与自定义功能开发:协议灵活性设计与实践 - 慧知开源充电桩平台

OCPP扩展机制与自定义功能开发&#xff1a;协议灵活性设计与实践 引言 OCPP作为开放协议&#xff0c;其核心价值在于平衡标准化与可扩展性。面对不同充电桩厂商的硬件差异、区域能源政策及定制化业务需求&#xff0c;OCPP通过**扩展点&#xff08;Extension Points&#xff09…...

哈希查找与深度优先遍历深度解析

一、算法基础概念对比 1.1 哈希查找的本质特征 哈希查找是一种基于哈希函数直接访问数据结构的查找技术&#xff0c;其核心在于通过数学映射建立键值与存储位置的直接关联。理想情况下时间复杂度可达O(1)&#xff0c;实际应用中通过冲突处理机制实现近似常数时间的查找效率。…...

【powerjob】 powerjobserver注册服务IP错误

1、问题&#xff1a;powerjobserver 4.3.6 的服务器上有多个网卡对应多个ip,示例 eth0 :IP1 &#xff0c;docker0:IP2 和worker 进行通信时 正确的应该时IP1 但是注册显示获取的确实IP2,导致 worker 通过ip2和server通信&#xff0c;网络不通&#xff0c;注册不上 2、解决方案 …...

Flutter底层实现

1. Dart 语言 Dart 是 Flutter 的主要编程语言。Dart 设计之初就是为了与 JavaScript 兼容&#xff0c;并且可以编译为机器代码运行。Dart 提供了一些特性&#xff0c;如异步支持&#xff08;通过 async 和 await&#xff09;&#xff0c;这使得编写高效的网络请求和复杂动画变…...

亚信安全发布2024威胁年报和2025威胁预测

在当今数字化时代&#xff0c;网络空间已成为全球经济、社会和国家安全的核心基础设施。随着信息技术的飞速发展&#xff0c;网络连接了全球数十亿用户&#xff0c;推动了数字经济的蓬勃发展&#xff0c;同时也带来了前所未有的安全挑战。2024年&#xff0c;网络安全形势愈发复…...

【YOLOv12改进trick】StarBlock引入YOLOv12,创新涨点优化,含创新点Python代码,方便发论文

🍋改进模块🍋:StarBlock 🍋解决问题🍋:采用StarBlock将输入数据映射到一个极高维的非线性特征空间,生成丰富的特征表示,使得模型在处理复杂数据时更加有效。 🍋改进优势🍋:简单粗暴的星型乘法涨点却很明显 🍋适用场景🍋:目标检测、语义分割、自然语言处理…...

Android MVI架构模式详解

MVI概念 MVI&#xff08;Model-View-Intent&#xff09;是一种Android应用架构模式&#xff0c;旨在通过单向数据流和不可变性来简化应用的状态管理。MVI的核心思想是将用户操作、状态更新和界面渲染分离&#xff0c;确保应用的状态可预测且易于调试。 MVI的核心组件 Model&a…...

Spring AI Alibaba + Ollama:国产大模型DeepSeek LLM的低成本AI应用开发认知

写在前面 官方文档很详细&#xff0c;有开发需求可以直接看文档https://java2ai.com/docs/1.0.0-M5.1/get-started/博文内容为一个开发Demo&#xff0c;以及API简单认知理解不足小伙伴帮忙指正 &#x1f603;,生活加油 我看远山&#xff0c;远山悲悯 持续分享技术干货&#xf…...

《2025软件测试工程师面试》功能测试篇

什么是功能测试? 功能测试是通过验证产品功能是否满足用户需求的过程,主要关注软件的功能是否符合需求规格说明,包括软件的各种功能、特性、性能、安全性和易用性等。 功能测试的流程包括哪些步骤? 需求分析:明确软件需求,确定测试范围。测试计划:制定详细的测试计划,…...

蓝桥杯2024年第十五届省赛真题-传送阵

题目描述 小蓝在环球旅行时来到了一座古代遗迹&#xff0c;里面并排放置了 n 个传送阵&#xff0c;进入第 i 个传送阵会被传送到第 ai 个传送阵前&#xff0c;并且可以随时选择退出或者继续进入当前传送阵。小蓝为了探寻传送阵中的宝物&#xff0c;需要选择一个传送阵进入&…...

非线性优化--NLopt算法(Android版本和Python示例)

通俗一点来说 非线性优化就是求函数的极值。我们想求一个 函数的极值问题的时候,线性函数是最简单的,因为是线性的嘛,单调增或者单调减,那么找到边界就可以求到极值。例如 f(x)=ax+b。 简单的非线性函数也是很容易求得极值的,例如f(x)=x*x.可以通过求导得到极值点,然后求…...

2025-03-06 ffmpeg提取SPS/PPS/SEI ( extradata )

一、需求 在某些情况下&#xff0c;可能需要直接使用H264/H265等原始数据流进行解码&#xff0c;比较常用的udp下的h264/h265。这时需要 av_parser_parse2 来组AVPacket,但对于视频的信息&#xff1a;宽高、格式等&#xff0c;可以根据 AVCodecParserContext 来获取&#xff0…...

海量数据融合互通丨TiDB 在安徽省住房公积金监管服务平台的应用实践

导读 安徽省住房公积金监管服务平台通过整合全省 17 家公积金中心的数据&#xff0c;致力于实现数据共享、规范化管理与高效数据分析。为了应对海量数据处理需求&#xff0c;安徽省选择 TiDB 作为底层数据库&#xff0c;利用其分布式架构和 HTAP 能力&#xff0c;实现了快速的…...

深入解析 supervision 库:功能、用法与应用案例

1. 引言 在计算机视觉任务中&#xff0c;数据的后处理和可视化是至关重要的环节&#xff0c;尤其是在目标检测、分割、跟踪等任务中。supervision 是一个专门为这些任务提供高效数据处理和可视化支持的 Python 库。本文将深入介绍 supervision 的功能、使用方法&#xff0c;并…...

【DeepSeek问答】访问QStandardItemModel::index(r,c)获取的空索引导致程序崩溃

好的&#xff0c;我现在来仔细思考一下用户的问题。用户在使用QStandardItemModel的setItem方法时&#xff0c;调用了setItem(4,6,item)&#xff0c;也就是在第4行第6列的位置设置了一个item。然后他们尝试通过index(3,6)来获取这个位置的项目&#xff0c;想知道会有什么后果。…...

从开源大模型工具Ollama存在安全隐患思考企业级大模型应用如何严守安全红线

近日&#xff0c;国家网络安全通报中心通报大模型工具Ollama默认配置存在未授权访问与模型窃取等安全隐患&#xff0c;引发了广泛关注。Ollama作为一款开源的大模型管理工具&#xff0c;在为用户提供便捷的同时&#xff0c;却因缺乏有效的安全管控机制&#xff0c;存在数据泄露…...

Aws batch task 无法拉取ECR 镜像unable to pull secrets or registry auth 问题排查

AWS batch task使用了自定义镜像&#xff0c;在提作业后出现错误 具体错误是ResourceInitializationError: unable to pull secrets or registry auth: The task cannot pull registry auth from Amazon ECR: There is a connection issue between the task and Amazon ECR. C…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...