自動化同步多服務器數據庫表結構

當項目每次進行版本升級的時候,如果在這次迭代中涉及表結構變更,需要將不同的生產環境下,都需要同步表結構的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 查看容器最后一百行日志

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72683.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72683.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72683.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DeepSeek安全:AI網絡安全評估與防護策略

&#x1f345; 點擊文末小卡片 &#xff0c;免費獲取網絡安全全套資料&#xff0c;資料在手&#xff0c;漲薪更快 本文基于現有的公開資料&#xff0c;從企業資深網絡安全專家的視角&#xff0c;系統梳理DeepSeek技術在網絡安全領域的潛在貢獻與核心風險&#xff0c;并結合中國…

【論文筆記】Attentive Eraser

標題&#xff1a;Attentive Eraser: Unleashing Diffusion Model’s Object Removal Potential via Self-Attention Redirection Guidance Source&#xff1a;https://arxiv.org/pdf/2412.12974 收錄&#xff1a;AAAI 25 作者單位&#xff1a;浙工商&#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、解決方案 …

視頻錄像機視頻通道是指什么

視頻錄像機的視頻通道是指攝像機在監控矩陣或硬盤錄像機設備上的視頻輸入的物理位置。 與攝像頭數量關系&#xff1a;在視頻監控系統中&#xff0c;有多少個攝像頭就需要多少路視頻通道&#xff0c;通道數量決定了視頻錄像機可接入攝像頭的數量&#xff0c;一般硬盤錄像機有4路…

面試150,數組 / 字符串

27. 移除元素 class Solution:def removeElement(self, nums: List[int], val: int) -> int:# 把不等于 val 的值移動到前面n len(nums)left 0for right in range(n):if nums[right] ! val:nums[left] nums[right]left 1return left26. 刪除有序數組中的重復項 只保留 1…

【江科大STM32】TIM輸入捕獲模式PWMI模式測頻率

一、輸入捕獲測頻率 接線圖&#xff1a; 測信號的輸入引腳為PA6&#xff0c;信號從PA6進來&#xff0c;待測的PWM信號也是STM32自己生成的&#xff0c;輸出引腳是PA0&#xff0c;所以接線這里直接用一根線將PA0引到PA6就可以了。 如果有信號發生器的話&#xff0c;也可以設置成…

湖倉一體化及冷、熱、實時三級存儲

一、湖倉一體化&#xff08;Lakehouse&#xff09; 湖倉一體化&#xff08;Lakehouse&#xff09;是數據湖&#xff08;Data Lake&#xff09;與數據倉庫&#xff08;Data Warehouse&#xff09;的結合&#xff0c;旨在解決傳統數據架構中數據孤島、存儲冗余、計算性能不足等問…

go切片定義和初始化

1.簡介 切片是數組的一個引用&#xff0c;因此切片是引用類型&#xff0c;在進行傳遞時&#xff0c;遵守引用傳遞的機制。切片的使用和數組類似&#xff0c;遍歷切片、訪問切片的元素和切片的長度都一樣。。切片的長度是可以變化的&#xff0c;因此切片是一個可以動態變化的數…

游戲引擎學習第138天

倉庫:https://gitee.com/mrxiao_com/2d_game_3 資產&#xff1a;game_hero_test_assets_003.zip 發布 我們的目標是展示游戲運行時的完整過程&#xff0c;從像素渲染到不使用GPU的方式&#xff0c;我們自己編寫了渲染器并完成了所有的工作。今天我們開始了一些新的內容&#…

畢業項目推薦:基于yolov8/yolov5/yolo11的暴力行為檢測識別系統(python+卷積神經網絡)

文章目錄 概要一、整體資源介紹技術要點功能展示&#xff1a;功能1 支持單張圖片識別功能2 支持遍歷文件夾識別功能3 支持識別視頻文件功能4 支持攝像頭識別功能5 支持結果文件導出&#xff08;xls格式&#xff09;功能6 支持切換檢測到的目標查看 二、數據集三、算法介紹1. YO…

docker中kibana啟動后,通過瀏覽器訪問,出現server is not ready yet

問題&#xff1a;當我在瀏覽器訪問kibana時&#xff0c;瀏覽器給我報了server is not ready yet. 在網上試了很多方法&#xff0c;都未能解決&#xff0c;下面是我的方法&#xff1a; 查看kibana日志&#xff1a; docker logs -f kibana從控制臺打印的日志可以發現&#xff…

在 Docker 中,無法直接將外部多個端口映射到容器內部的同一個端口

Docker 的端口映射是一對一的&#xff0c;即一個外部端口只能映射到容器內部的一個端口。 1. 為什么不能多對一映射&#xff1f; 端口沖突&#xff1a; 如果外部多個端口映射到容器內部的同一個端口&#xff0c;Docker 無法區分外部請求應該轉發到哪個內部端口&#xff0c;會…

游戲引擎學習第120天

倉庫:https://gitee.com/mrxiao_com/2d_game_3 上次回顧&#xff1a;周期計數代碼 我們正在進行一個項目的代碼優化工作&#xff0c;目標是提高性能。當前正在優化某個特定的代碼片段&#xff0c;已經將其執行周期減少到48個周期。為了實現這一目標&#xff0c;我們設計了一個…

C++中的.h文件一般是干什么的?

在C中&#xff0c;.h 文件通常是 頭文件&#xff08;Header File&#xff09;&#xff0c;它們的主要作用是聲明類、函數、常量、宏以及其他在多個源文件&#xff08;.cpp文件&#xff09;之間共享的元素。頭文件提供了一個接口&#xff0c;使得不同的源文件能夠訪問這些共享的…

基礎算法總結

基礎算法總結 1、模擬1.1 什么是模擬算法1.2 算法題1.2.1 多項式輸出1.2.2 蛇形方陣 2 高精度算法2.1 什么是高精度算法2.2 算法題2.2.1 高精度加法 2.2.2 高精度乘法 3 普通枚舉3.1 算法題3.1.1 鋪地毯 3.1.2 回文日期 4 前綴和算法4.1 什么是前綴和4.2 算法題4.2.1 最大子段和…

密碼學(哈希函數)

4.1 Hash函數與數據完整性 數據完整性&#xff1a; 檢測傳輸消息&#xff08;加密或未加密&#xff09;的修改。 密碼學Hash函數&#xff1a; 構建某些數據的簡短“指紋”&#xff1b;如果數據被篡改&#xff0c;則該指紋&#xff08;以高概率&#xff09;不再有效。Hash函數…

游戲引擎學習第135天

倉庫:https://gitee.com/mrxiao_com/2d_game_3 回顧 game_asset.cpp 的創建 在開發過程中&#xff0c;不使用任何現成的游戲引擎或第三方庫&#xff0c;而是直接基于 Windows 進行開發&#xff0c;因為 Windows 目前仍然是游戲的標準平臺&#xff0c;因此首先在這個環境中進行…

Linux:文件描述符與重定向

目錄 一、文件描述符 1.文件內核對象 2.文件描述符分配原則 二、文件重定向 1.重定向的現象 輸出重定向 輸入重定向 dup2 2.重定向的使用 三、標準輸出和標準錯誤 繼上篇文章中&#xff0c;我們了解了fd打印的值為文件描述符&#xff0c;那么它還有什么作用呢&…

白盒測試(3):PCB阻抗測試方法

PCB阻抗測試是確保信號完整性的關鍵&#xff0c;通過測量走線的特性阻抗&#xff0c;驗證其是否符合設計目標。常用方法包括時域反射法&#xff08;TDR&#xff09;、網絡分析儀法和仿真軟件法。TDR通過分析反射信號定位阻抗異常&#xff0c;網絡分析儀通過S參數計算阻抗&#…

CentOS 7 安裝Nginx-1.26.3

無論安裝啥工具、首先認準了就是官網。Nginx Nginx官網下載安裝包 Windows下載&#xff1a; http://nginx.org/download/nginx-1.26.3.zipLinxu下載 wget http://nginx.org/download/nginx-1.26.3.tar.gzLinux安裝Nginx-1.26.3 安裝之前先安裝Nginx依賴包、自行選擇 yum -y i…