技术派数据库表自动初始化(学习)

不需要在db中手动创建或者导入相关的schema、data,项目启动自动创建对应的表,并初始化。实现该过程。

Liquibase数据库版本管理

依赖配置

在paicoding-web模块中,pom.xml 文件中添加

        <dependency><groupId>org.liquibase</groupId><artifactId>liquibase-core</artifactId></dependency>

然后就是核心配置,首先是application.yml配置文件中,有两个关键参数

spring:liquibase:change-log: classpath:liquibase/master.xmlenabled: true # 当实际使用的数据库不支持liquibase,如 mariadb 时,将这个参数设置为false

说明:

  • 对于使用的数据库不支持liquibase,如 mariadb 时,将这个参数设置为false 
  • change-log:对应的是核心的数据库版本变更配置

master.xml文件中的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLogxmlns="http://www.liquibase.org/xml/ns/dbchangelog"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangeloghttp://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"><include file="liquibase/changelog/000_initial_schema.xml" relativeToChangelogFile="false"/></databaseChangeLog>

注意上面这个include,这个就是告诉liqubase,所有的变更记录,都放在

liquibase/changelog/000_initial_schema.xml这个文件中

现在只有一个include标签,但是实际上是可以有多个的,一个好的建议是,项目首次初始化表、初始化数据可以是一个include标签;后续每次大的版本迭代,对应一个新的include。

再看一下000_initial_schema.xml里面的文件内容

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLogxmlns="http://www.liquibase.org/xml/ns/dbchangelog"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"><property name="now" value="now()" dbms="mysql"/><property name="autoIncrement" value="true"/><changeSet id="00000000000001" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_schema_221209.sql"/></changeSet><changeSet id="00000000000002" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_221209.sql"/></changeSet><changeSet id="00000000000003" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_221210.sql"/></changeSet><changeSet id="00000000000005" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_221216.sql"/></changeSet><!--  专栏类型,新增免费开始时间、结束时间  --><changeSet id="00000000000006" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/update_schema_221223.sql"/></changeSet><!-- user_info表添加ip字段,用于记录访问用户所在地   --><changeSet id="00000000000007" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/update_schema_221229.sql"/></changeSet><!-- 配置表新增 extra 字段 --><changeSet id="00000000000008" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/update_schema_230103.sql"/></changeSet><!-- 技术派介绍文章 --><changeSet id="00000000000009" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_230103.sql"/></changeSet><!-- 重新更新标签 --><changeSet id="00000000000012" author="LouZai"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_230105.sql"/></changeSet><!-- 添加审核中 --><changeSet id="00000000000013" author="LouZai"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/init_data_230130.sql"/></changeSet><!-- 添加用户角色 --><changeSet id="00000000000014" author="YiHui"><sqlFile dbms="mysql" endDelimiter=";" encoding="UTF-8" path="liquibase/data/update_schema_230131.sql"/></changeSet>
</databaseChangeLog>

说明:

  • changeSet标签,id必须唯一,不能出现冲突
  • sqlFile里面的path,对应的可以是标准的sql文件,也可以是xml格式的数据库表定义、数据库操作文件
  • 一旦写上去,changeSet的顺徐不要调整

比如库表创建的sql

项目演示

在技术派的项目中,还做了一个事情,就是初始化库,在下面的DataSourceInitializer中有介绍;如果是一个新的项目,接入Liquibase之后,数据库,请注意还是需要自己来创建的

项目启动之后,一切正常的话,直接连接上数据库可以看到库表创建成功,数据也初始化完成,当然也可以是直接观察控制台的输出。

下面红框中的ChangeSet xxx run successfully  in 401ms 就表示对应的sql执行成功了

注意事项

非常重要的一个点是,上面的每个ChangeSet只会执行一次,因此当执行完毕之后发现不对,要回滚怎么办,或者需要修改怎么办?需要Liquibase提供的回滚机制。简单说明。

当Change执行完毕之后,对应的sql文件/xml文件(即path定义的文件)不允许在修改,因为db中会记录这个文件的md5,当修改这个文件之后,这个MD5也会随之发生改变

有两个方案解决:新增一个changeSte

删除DATABASECHANFELOG表中changeSet id对应的记录,然后重新走一遍

DataSourceInitializer首次初始化方案

我们这里主要借助DataSourceInitializer来实现初始化,其核心有两个配置

  • DatabasePopulator:通过addCcripts来指定对应的的sql文件
  • DataSourceInitializer#setEnable;判断是否需要执行初始化

我们主要借助DataSourceInitializer来实现Liquibase的表的创建、数据变更等操作;但是在此之前,我们还做了一个库的初始化

库初始化

接下来重点要看的就是needInit方法,我们在这个方法里面,需要判断数据库是否存在,若不存在时,则创建数据库;然后判断表是否存在,以此来决定是否需要执行初始化方法。

入口在实现ForumDataSourceInitializer

  /*** 检测一下数据库中表是否存在,若存在则不初始化;否则基于 schema-all.sql 进行初始化表** @param dataSource* @return*/private boolean needInit(DataSource dataSource) {if (autoInitDatabase()) {return true;}// 根据是否存在表来判断是否需要执行sql操作JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);List list = jdbcTemplate.queryForList("SELECT table_name FROM information_schema.TABLES where table_name = 'user_info' and table_schema = '" + database + "';");return CollectionUtils.isEmpty(list);}/*** 数据库不存在时,尝试创建数据库*/private boolean autoInitDatabase() {// 查询失败,可能是数据库不存在,尝试创建数据库之后再次测试URI url = URI.create(SpringUtil.getConfig("spring.datasource.url").substring(5));String uname = SpringUtil.getConfig("spring.datasource.username");String pwd = SpringUtil.getConfig("spring.datasource.password");try (Connection connection = DriverManager.getConnection("jdbc:mysql://" + url.getHost() + ":" + url.getPort() +"?useUnicode=true&characterEncoding=UTF-8&useSSL=false", uname, pwd);Statement statement = connection.createStatement()) {ResultSet set = statement.executeQuery("select schema_name from information_schema.schemata where schema_name = '" + database + "'");if (!set.next()) {// 不存在时,创建数据库String createDb = "CREATE DATABASE IF NOT EXISTS " + database;connection.setAutoCommit(false);statement.execute(createDb);connection.commit();log.info("创建数据库({})成功", database);if (set.isClosed()) {set.close();}return true;}set.close();log.info("数据库已存在,无需初始化");return false;} catch (SQLException e2) {throw new RuntimeException(e2);}}

上面的实现比较清晰了,首先是判断数据库是否存在,这里需要注意的就是,我们需要自己额创建db的连接,并执行相关库的判断、初始化sql执行。

为什么不直接使用spring.datasource.url来创建连接?

        因为库不存在时,直接使用下面这个url进行连接会抛出异常

表初始化

表初始化,其实可以理解为项目启动后要执行的一些sql,这时主要借助就是initializer.setDatabasePopulator

核心知识点

虽然技术派新增了一个DbChangeSetLoader类来实现初始化sql的加载,但实际上,若你完全抛开Liquibase,单纯的希望项目启动后执行某些sql,可以非常简单的实现,直接用下面这种就可以了啦。

  • 通过@Value来加载需要初始化的sql文件
  • 直接通过ResourceDatabasePoplulator添加sql资源

Liquibase兼容方案

在技术派中,做了Liquibase的兼容,即找那些sql需要进行初始化,完全在遵循了Liquibase中定义的xml文件

要想要在技术派中使用这种方式进行初始化,如使用marizadb时,需要修改配置参数

spring.liquibase.enable:false

核心的实现如下:

对于liquibase的xml文件解析,核心逻辑在DbChangeSetLoader中,借助sax来进行xml文件的解析(Spring也是用sax解析xml的)

实现看源码,有两个知识点:

1.如何加载xml文件

        下面的传参set是相对路径,如liquibase/data/init_data_221216.sql

2、sax的xml解析

小结

介绍项目启动之后库表的初始化操作,结合实际的代码介绍了两种使用姿势

  • Liquibase:代表的数据库版本管理方式
  • DataSourceInitializer:代表的项目启动之后执行某些初始化方式

更多关注:

DataSourceInitializer方式-数据库初始化方式

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

练习 1 Web EasySQL极客大挑战

CTF Week 1 EasySQL极客大挑战 BUUCTF 典中典复习 Web SQL 先尝试输入&#xff0c;找一找交互页面 check.php 尝试万能语句 a’ or true SQL注入&#xff1a;#和–的作用 get传参只能是url编码&#xff0c;注意修改编码&#xff0c;输入的字符串要改成url格式。 POST请求和…

YOLOv8改进涨点,添加GSConv+Slim Neck,有效提升目标检测效果,代码改进(超详细)

目录 摘要 主要想法 GSConv GSConv代码实现 slim-neck slim-neck代码实现 yaml文件 完整代码分享 总结 摘要 目标检测是计算机视觉中重要的下游任务。对于车载边缘计算平台来说&#xff0c;巨大的模型很难达到实时检测的要求。而且&#xff0c;由大量深度可分离卷积层构…

Python GUI开发库之nicegui使用详解

概要 在 Python 中,创建图形用户界面(GUI)应用程序通常需要大量的代码和时间。然而,随着 Python 生态系统的不断发展,出现了一些简化 GUI 开发过程的工具和库。其中之一就是 NiceGUI 库。本文将深入探讨 NiceGUI 库的功能、用法以及如何利用它来创建漂亮而功能丰富的 GUI…

LabVIEW水下温盐深数据一体化采集与分析

LabVIEW水下温盐深数据一体化采集与分析 开发一个基于LabVIEW的水下温盐深数据一体化采集与分析系统&#xff0c;实现海洋环境监测的自动化和精确化。通过集成温度、盐度和深度传感器&#xff0c;结合USB数据采集卡&#xff0c;利用LabVIEW软件开发的图形化界面&#xff0c;实…

iOS App冷启动优化:二进制重排

原理 二进制文件中方法的加载顺序&#xff0c; 取决于方法在代码文件中的书写顺序&#xff0c;而不是调用顺序。 应用程序启动时会调用到的方法是有限的&#xff0c;但可能分散在很多个。 由于内存是分页管理的&#xff0c;要加载就要 整页加载。 这就导致很多完全还用不到的方…

LeetCode 刷题 [C++] 第148题.排序链表

题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 题目分析 根据题意&#xff0c;可以使用归并排序来对链表进行排序。归并排序是基于分治的思想&#xff0c;比较容易实现的就是自顶向下的递归方式来实现。 先找出链表的中点&#x…

自学Python笔记总结(2——了解)

网络了解 网络调试助手 NetAssist.exe NetAssist.exe 使用方法请自行寻找 UDP协议 &#xff08;只能一来一回的的发消息&#xff0c;不可连续发送&#xff09; UDP 是User Datagram Protocol的简称&#xff0c; 中文名是用户数据报协议。在通信开始之前&#xff0c;不需要建…

如何利用graylog进行容器化日志管理?

Docker日志 当一个容器启动的时候&#xff0c;它其实是docker deamon的一个子进程&#xff0c;docker daemon可以拿到容器里面进程的标准输出&#xff0c;然后通过自身的LogDriver模块来处理&#xff0c;LogDriver支持的方式很多&#xff0c;默认写到本地文件&#xff0c;也可…

JAVA泛型浅析

Java范型generics&#xff0c;是JDK1.5引入的新特性&#xff0c;是一种编译时类型安全检测机制&#xff0c;可以在编译时检测到非法的类型。范型的本质是将类型参数化&#xff0c;将类型指定成一个参数。java中的集合就有使用&#xff0c;并且对外提供的三方库和SDK中使用也极为…

顶会ICLR2024论文Time-LLM:基于大语言模型的时间序列预测

文青松 松鼠AI首席科学家、AI研究院负责人 美国佐治亚理工学院(Georgia Tech)电子与计算机工程博士&#xff0c;人工智能、决策智能和信号处理方向专家&#xff0c;在松鼠AI、阿里、Marvell等公司超10年的技术和管理经验&#xff0c;近100篇文章发表在人工智能相关的顶会与顶刊…

手撕Java集合之简易版Deque(LinkedList)

在目前&#xff0c;许多互联网公司的面试已经要求能手撕集合源码&#xff0c;集合源码本身算是源码里比较简单的一部分&#xff0c;但是要在面试极短的10来分钟内快速写出一个简易版的源码还是比较麻烦的&#xff0c;很容易出现各种小问题。所以在平时就要注重这方面的联系。 以…

React入门之React_渲染基础用法和class实例写法

渲染元素 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>04元素渲染</title><script src&…