springboot + mybatis启动时执行sql脚本

2023-11-03

目录

1. 创建数据版本表,结构如下:

 2. 创建HdVersion对象

 3. 创建执行sql的dao

4. 创建dao对应的xml

5.创建sql执行器,实现ApplicationRunner

6. 结语


背景:项目开发或发布阶段修改表结构,项目更新时需要手动执行脚本,需要优化为项目启动时自动检查版本并执行sql语句。

开发环境:jdk1.8

开发工具:IDEA

框架:springboot+mybatisplus

数据库:mysql 5.7

SpringBoot本身提供了丰富的组件供开发者调用,本次优化通过ApplicationRunner类实现。

在SpringBoot中,提供了一个接口:ApplicationRunner
该接口中,只有一个run方法,他执行的时机是:spring容器启动完成之后,就会紧接着执行这个接口实现类的run方法。

mybaits默认不能批量执行sql,yml配置文件中连接数据库url配置添加以下参数:

allowMultiQueries=true

如:

url: jdbc:mysql://192.168.100.xx:3306/xxx?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8

让我们开始吧!

1. 创建数据版本表,结构如下:

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='数据版本';

 2. 创建HdVersion对象

@Data
public class HdVersionEntity {

	private String id;
	private String version;
	private String remark;
	private Date created;

}

 3. 创建执行sql的dao

@Mapper
public interface HdCommonDao  {

    //查询版本号是否存在
    int selectVersion(@Param("version") String version);

    //查询版本表是否存在
    int selectTableExist(@Param("tableName") String tableName);

    //新增版本
    int insertVersion(HdVersionEntity entity);

    //执行sql
    @Update("${sql}")
    void updateSql(@Param("sql") String sql);

}

4. 创建dao对应的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.hdkj.hdiot.configure.dao.HdCommonDao">

    <select id="selectVersion" resultType="int">
        selecT count(1) from hd_version
        where 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>

5.创建sql执行器,实现ApplicationRunner

@Order(1)
@Component
@Slf4j
public class HdSchemaExecutor implements ApplicationRunner{

	@Override
	public void run(ApplicationArguments applicationArguments) throws Exception {
		//do something
	}

}

  约定一个存放sql文件的目录:

sql文件命名规则不进行约束

HdSchemaExecutor 新建一个全局变量,存放脚本列表:

private List<SchemaData> schema = new ArrayList<>();

SchemaData对象如下:


@Data
public class SchemaData {
    /**
     * 版本号
     */
    public String version;

    /**
     * 文件名
     */
    public String fileName;

    public SchemaData(String version, String fileName) {
        this.version = version;
        this.fileName = fileName;
    }
}

HdSchemaExecutor 新增方法,给schema赋值:

public void buildSchemas(){
        schema.add(new SchemaData("v2.1","schema_v2.1.sql"));
        schema.add(new SchemaData("v2.2","schema_v2.2.sql"));
        schema.add(new SchemaData("v2.3","schema_v2.3.sql"));
    }

run方法内容如下:


    @Override
    public void run(ApplicationArguments args) throws Exception {
        //初始版本列表
        buildSchemas();
        //定义sql文件路径
        String basePath = "schemas/";
        //非版本控制,初始化脚本
        ClassLoader loader = this.getClass().getClassLoader();
        //通过流的方式获取项目路径下的文件
        InputStream inputStream = loader.getResourceAsStream(basePath + "init.sql");
        //获取文件内容
        String sql = IoUtil.readUtf8(inputStream);
        try {
            //判断版本表是否存在
            int count = hdCommonDao.selectTableExist("hd_version");
            if (count == 0) {
                hdCommonDao.updateSql(sql);
            }
            for (SchemaData schemaData : schema) {
                //查询版本记录是否存在
                count = hdCommonDao.selectVersion(schemaData.getVersion());
                if (count == 0) {
                    log.info("--------------执行数据脚本,版本:" + schemaData.getVersion());
                    //获取对应sql脚本
                    inputStream = loader.getResourceAsStream(basePath + schemaData.getFileName());
                    sql = IoUtil.readUtf8(inputStream);
                    hdCommonDao.updateSql(sql);
                    HdVersionEntity entity = new HdVersionEntity();
                    entity.setId(UUID.randomUUID().toString());
                    entity.setVersion(schemaData.getVersion());
                    entity.setCreated(new Date());
                    entity.setRemark(schemaData.getFileName());
                    //写入版本记录
                    hdCommonDao.insertVersion(entity);
                }
            }

        } catch (IORuntimeException e) {
            e.printStackTrace();
        } finally {
            //关闭流
            inputStream.close();
        }
    }

完整代码如下:

package com.hdkj.hdiot.configure.config;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import com.hdkj.hdiot.configure.common.SchemaData;
import com.hdkj.hdiot.configure.dao.HdCommonDao;
import com.hdkj.hdiot.configure.entity.HdVersionEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author: 刘成辉
 * @Date: 2022/7/27 8:45
 * @Description:
 */
@Order(1)
@Component
@Slf4j
public class HdSchemaExecutor implements ApplicationRunner {

    @Autowired
    HdCommonDao hdCommonDao;

    private List<SchemaData> schema = new ArrayList<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //初始版本列表
        buildSchemas();
        //定义sql文件路径
        String basePath = "schemas/";
        //非版本控制,初始化脚本
        ClassLoader loader = this.getClass().getClassLoader();
        //通过流的方式获取项目路径下的文件
        InputStream inputStream = loader.getResourceAsStream(basePath + "init.sql");
        //获取文件内容
        String sql = IoUtil.readUtf8(inputStream);
        try {
            //判断版本表是否存在
            int count = hdCommonDao.selectTableExist("hd_version");
            if (count == 0) {
                hdCommonDao.updateSql(sql);
            }
            for (SchemaData schemaData : schema) {
                //查询版本记录是否存在
                count = hdCommonDao.selectVersion(schemaData.getVersion());
                if (count == 0) {
                    log.info("--------------执行数据脚本,版本:" + schemaData.getVersion());
                    //获取对应sql脚本
                    inputStream = loader.getResourceAsStream(basePath + schemaData.getFileName());
                    sql = IoUtil.readUtf8(inputStream);
                    hdCommonDao.updateSql(sql);
                    HdVersionEntity entity = new HdVersionEntity();
                    entity.setId(UUID.randomUUID().toString());
                    entity.setVersion(schemaData.getVersion());
                    entity.setCreated(new Date());
                    entity.setRemark(schemaData.getFileName());
                    //写入版本记录
                    hdCommonDao.insertVersion(entity);
                }
            }

        } catch (IORuntimeException e) {
            e.printStackTrace();
        } finally {
            //关闭流
            inputStream.close();
        }
    }

    public void buildSchemas() {
        schema.add(new SchemaData("v2.1", "schema_v2.1.sql"));
        schema.add(new SchemaData("v2.2", "schema_v2.2.sql"));
        schema.add(new SchemaData("v2.3", "schema_v2.3.sql"));
    }
}

6. 结语

       每次发布版本是放脚本到对应目录下,初始化方法中新增版本对应关系

附初始化脚本文件:


/*==============================================================*/
/* Table: hd_version                                            */
/*==============================================================*/
create table if not exists hd_version
(
   id                   varchar(64) not null,
   version              varchar(64) comment '版本号',
   created              datetime comment '创建时间',
   remark               varchar(500) comment '备注',
   primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据版本';

/* 创建函数Pro_Temp_ColumnWork操作表字段 */

DROP PROCEDURE IF EXISTS Pro_Temp_ColumnWork;
CREATE  PROCEDURE `Pro_Temp_ColumnWork` ( TableName VARCHAR ( 50 ), ColumnName VARCHAR ( 50 ), SqlStr VARCHAR ( 4000 ), CType INT ) BEGIN
    DECLARE
        Rows1 INT;

    SET Rows1 = 0;
    SELECT
        COUNT(*) INTO Rows1
    FROM
        INFORMATION_SCHEMA.COLUMNS
    WHERE
            table_schema = DATABASE ()
      AND upper( table_name )= TableName
      AND upper( column_name )= ColumnName;
    IF
        ( CType = 1 AND Rows1 <= 0 ) THEN

        SET SqlStr := CONCAT( 'ALTER TABLE ', TableName, ' ADD COLUMN ', ColumnName, ' ', SqlStr );

    ELSEIF ( CType = 2 AND Rows1 > 0 ) THEN

        SET SqlStr := CONCAT( 'ALTER TABLE ', TableName, ' MODIFY  ', ColumnName, ' ', SqlStr );

    ELSEIF ( CType = 3 AND Rows1 > 0 ) THEN

        SET SqlStr := CONCAT( 'ALTER TABLE  ', TableName, ' DROP COLUMN  ', ColumnName );
    ELSE
        SET SqlStr := '';

    END IF;
    IF
        ( SqlStr <> '' ) THEN

        SET @SQL1 = SqlStr;
        PREPARE stmt1
            FROM
            @SQL1;
        EXECUTE stmt1;

    END IF;

END;
/** 函数创建结束 **/

/*创建定义普通索引函数*/
DROP PROCEDURE IF EXISTS Modify_index;

CREATE PROCEDURE Modify_index (
    TableName VARCHAR ( 50 ),
    ColumnNames VARCHAR ( 500 ),
    idx_name VARCHAR ( 50 ),
    idx_type VARCHAR ( 50 )) BEGIN
    DECLARE
        Rows1 int;
    DECLARE
        SqlStr VARCHAR(4000);
    DECLARE
        target_database VARCHAR ( 100 );
    SELECT DATABASE
               () INTO target_database;

    SET Rows1 = 0;
    SELECT
        COUNT(*) INTO Rows1
    FROM
        information_schema.statistics
    WHERE
            table_schema = DATABASE ()
      AND upper( table_name )= upper(TableName)
      AND upper( index_name )= upper(idx_name);
    IF Rows1<=0 THEN
        SET SqlStr := CONCAT( 'alter table ', TableName, ' ADD INDEX ', idx_name, '(', ColumnNames, ') USING ', idx_type );
    END IF;
    IF
        ( SqlStr <> '' ) THEN

        SET @SQL1 = SqlStr;
        PREPARE stmt1
            FROM
            @SQL1;
        EXECUTE stmt1;

    END IF;

END;
/*创建定义普通索引函数结束*/
Pro_Temp_ColumnWork:维护表字段

exp:

CALL Pro_Temp_ColumnWork ('table_name','column_name','int(1) ', 1);
Modify_index:维护表索引

exp:

call Modify_index('table_name','column_names','idx_tid_target','BTREE');
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

springboot + mybatis启动时执行sql脚本 的相关文章

随机推荐

  • Pikachu (xss跨站脚本攻击)

    目录 xss概念 一 反射型 xss get 二 反射型 post 三 存储型 四 DOM型 五 xss盲注 六 xss之过滤 七 xss之htmlspecialchars 1 htmlspecialchars作用 flags 八 xss之
  • 基于openswan klips的IPsec实现分析(五)应用层和内核通信(2)

    基于openswan klips的IPsec实现分析 五 应用层和内核通信 内核操作 转载请注明出处 http blog csdn net rosetta 在数据发送一节讲过 加载模块时会执行pfkey init 初始化与用户层通信的PF
  • C++ stack容器详解

    C stack容器 stack容器的基本概念 stack的常用接口 1 构造函数 2 赋值操作 3 数据存取 4 大小操作 测试 stack容器的基本概念 stack是一种先进先出的数据结构 被称为栈 它只有一端可以出入 栈中进入数据称为
  • IO读写实例

    基本类型的读写 import java io public class TestDataStream public static void main String args throws IOException OutputStream o
  • SSE2的一些常用指令集介绍

    开门见山 前段时间学习OpenCV的FAST算法 中间有很多SSE2的指令集 深受其惑 下面我把学习过程中学到的一些指令集介绍给大家 希望能对大家有所帮助 m128i被称为128bits的整数 对其进行赋值时 可以调用 m128i mm s
  • MMKV原理详解

    性能对比 我们将 MMKV 和 SharedPreferences SQLite 进行对比 重复读写操作 1k 次 相关测试代码在Android MMKV mmkvdemo 结果如下图表 单进程性能 可见 MMKV 在写入性能上远远超越 S
  • 三、nginx两种压缩配置[gzip]

    一 nginx压缩 解释 通过配置参数 让nginx压缩指定后缀格式文件 然后发送给用户 但是这样这些压缩文件无法使用sendfile的高效传送 使用其能使得文件传输不经过程序 加载到缓存直接发送 相反off的话 需要在硬盘 缓存 程序 发
  • [Python人工智能] 十四.循环神经网络LSTM RNN回归案例之sin曲线预测

    从本专栏开始 作者正式开始研究Python深度学习 神经网络及人工智能相关知识 前一篇文章详细讲解了如何评价神经网络 绘制训练过程中的loss曲线 并结合图像分类案例讲解精确率 召回率和F值的计算过程 本篇文章将分享循环神经网络LSTM R
  • java文件的上传和下载_java文件上传和下载

    在web项目中上传文件夹现在已经成为了一个主流的需求 在OA 或者企业ERP系统中都有类似的需求 上传文件夹并且保留层级结构能够对用户行成很好的引导 用户使用起来也更方便 能够提供更高级的应用支撑 文件夹数据表结构 CREATETABLEI
  • 注解-宋红康

    目录 一 注解 Annotation 概述 二 常见的注解实例 三 如何自定义注解 四 JDK中的四个元注解 五 Java8注解的新特性 1 可重复注解 2 类型注解 一 注解 Annotation 概述 二 常见的注解实例 三 如何自定义
  • Android开发入门组件(十)——WebView

    今天主要写一下WebView 主要是在安卓应用的页面来加载或者写入网页 是比较常见的一种操作 加载网页 1 加载url 网络或者本地assets文件下的html文件 1 加载网络url webview loadUrl 相应的网址 直接在ac
  • 从外包辞职了,600小时后,我入职了字节跳动

    前言 没有绝对的天才 只有持续不断的付出 对于我们每一个平凡人来说 改变命运只能依靠努力 幸运 但如果你不够幸运 那就只能拉高努力的占比 2022年7月 我有幸成为了字节跳动的一名Java后端开发 相信同行都清楚 从外包进大厂有多难 运气之
  • c# --- 泛型解决输入和输出类型不确定问题

    一 背景 有这样一个需求 一个方法 他的返回值类型不确定 方法参数的类型不做要求 二 思考 返回值类型不确定 从继承的角度 所以类都是object的子类 返回object即可 但是这种方法是类型不安全的 需要进行类型转换 我们可以使用泛型解
  • HTML <small> 标签

    定义和用法
  • CUBLAS矩阵乘法

    include
  • Winsock属性 方法介绍

    本文章已收录于 Winsock是Mcrosoft windows提供的网络编程接口 它供了基于TCP IP协议接口实现方法 通过网络进行的数据通信 需要用地址来表示网络中的主机 TCP IP协议使用IP地址来作为主机的标识 实现的连接方式是
  • 关于connect: network is unreachable 问题的解决

    由于发现原创文章 在未署名作者及出处的情况下被转载 在以后所有的原创文章开头我都会写明作者和出处 希望朋友们以后在转载本博客原创博文时注意标明文章作者及出处 作者 liukun321 咕唧咕唧 原文出处 http blog csdn net
  • 牛客面试题库(9)

    请你说说索引怎么实现的B 树 为什么选这个数据结构 说到B 树 一般和B树做对比 都是多叉树 B 数组叶子结点存储数据 其他节点存储索引 而B树每个节点都存储数据 B 树叶子结点内通过单向链表连接 节点和节点之间通过双向链表连接 从磁盘IO
  • 匿名内部类的定义格式

    匿名内部类 1 使用条件 如果接口的实现类 或者父类的子类只需要使用唯一的一次 那么就可以省略该类的定义 改用匿名内部类 2 定义格式 接口名称 对象名 new 接口名称 覆盖重写所有抽象方法 分号不要忘 3 对匿名内部类的格式 new 接
  • springboot + mybatis启动时执行sql脚本

    目录 1 创建数据版本表 结构如下 2 创建HdVersion对象 3 创建执行sql的dao 4 创建dao对应的xml 5 创建sql执行器 实现ApplicationRunner 6 结语 背景 项目开发或发布阶段修改表结构 项目更新