2
提示:本文中,我们只给了部分示例代码。
如果你需要完整的代码,请点击:https://github.com/mengyunzhi/sampleUpdateTableWithJpa/tree/1

why to do

在版本的迭代中,我们毕然会面临数据表更新的问题。而这些更新,有些是可以通过spring jpa进行自动更新的,有些更新spring jpa则表式无能无力,所以只能采用手动的方法。

本文将实现以下功能:
假设当前共有3个发布的版本。分别为1.1,1.2,1.3,每个版本都有对应的应用程序及数据库。

实现功能1:1.2版本的程序运行在1.1版本的数据库上时,自动将其更新为1.2版本所对应的数据库结构。
实现功能2:1.3版本的程序运行在1.2版本的数据库时,自动将其更新为1.3版本的数据库。
实现功能3:1.3版本的程序运行在1.1版本的数据库时,自动将期更新为1.3版本的数据库。
实现功能4:开发时,我们使用的为H2数据库,生产环境使用mysql数据库。当使用h2数据库时,不做任何数据库版本的更新。

准备知识

spring boot在系统启动时,对数据表的操作顺序如下(实际顺序并不见得如此,只以下流程图只为实现本文功能)。

clipboard.png

先想明白

由上面的图,我们得知,如果想实现我们的功能。需要从以下几方面入手。

  • 需要设置几个属性。
  • 需要单独为mysql来定制脚本。
  • 需要一张记录数据库版本的表。
  • 需要通过sql脚本,来判断数据库版本,然后执行不同的语句。
  • 当数据库为H2时,不增加任何脚本。

好了,想明白了上面的问题。其它的事情就显得简单了。

实施

准备

准备好mysql,并建立相应的数据库。在pom.xml中加入适应的依赖。


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mengyunzhi</groupId>
    <artifactId>sample-update-table-with-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>sample-update-table-with-jpa</name>
    <description>进行版本迭代过程中,使用spring jpa来完美解决数据表更新的问题</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置spring boot

spring:
  profiles:
    active: mysql

---
# h2环境
spring:
  profiles: h2
---
# mysql环境
spring:
  profiles: mysql
  datasource:
    url: jdbc:mysql://localhost/sampleUpdateJpa
    username: root
    password:
    platform: mysql
    separator: //
    initialization-mode: always
  jpa:
    hibernate:
      ddl-auto: update

建立测试文件

resources文件夹下,建立schema-mysql.sql文件。并随便写两行错误的语句,然后启动应用,得到报错信息,说明该文件并成功执行。比如,我随便写了如下几行。

-- 先于hibernate执行
sdfsdfsdf

组织sql语句

先判断,是否存在记录版本的数据表,如果没有,则创建一个数据表,并加入一条数据,将版本设置为1.1。

-- 重写 ; 为 // ,在spring中,注释掉下面一行,应该我们在配置文件中的 separator: // 便是起的该作用
-- DELIMITER // 
-- 如果存在函数,则先删除
DROP PROCEDURE IF EXISTS `FUN20180628` //
-- 定义函数FUN20180628
CREATE PROCEDURE `FUN20180628` ()
    BEGIN
        DECLARE hasDataTable INT;
        SELECT count(*) INTO hasDataTable FROM information_schema.tables WHERE (table_schema = 'sampleUpdateJpa') AND (table_name= 'version');
        IF hasDataTable = 0 THEN
            CREATE TABLE `sampleUpdateJpa`.`version` (
                `version` float NOT NULL COMMENT '版本号',
                PRIMARY KEY (`version`)
            ) COMMENT='版本号';
            insert into `sampleUpdateJpa`.`version` ( `version`) values ( '1.1');
        END IF;
    END
//

-- 调用函数
CALL FUN20180628() //
-- 恢复重写的;,以免影响其它的function
-- DELIMITER ;

我们在上面只所以要先删除原来的同名函数,主要目的是为了这个函数的随时升级。
相信有了上面的语句基础,实现当版本为1时,升级为2;为2时,升级为3,就简单了。

如果你只在意解决方案,那么本节内容到此结束。

我是如何做到的?

前面,我们已经给出了实现的方法,如果你还对“我是如何做到的”感兴趣,请继续阅读以下内容。

阅读官方文档

spring官方网站,我们找到共当家花旦spring-boot , 然后在介绍的下方,点击参考手册

然后我们将得到一个很长很长,当然也是很有用很有用的手册,我期待自己能够有充分的时间,可以在假期的时候尝试从头到尾的读一遍。来到第82章 -- Database Initialization

按我们的需求,简单的记录下,我们所要的资料:
82.1 说,有两种方式控制开与关,分别是:spring.jpa.generate-ddl(boolean),及spring.jpa.hibernate.ddl-auto(enum)

结论:我们并不需要在程序启动时,执行相关的import语句。

82.2介绍了使用Hibernate初始化数据库的过程,并对spring.jpa.hibernate.ddl-auto的几种属性和默认属性做了说明。

然后接着又说:在系统启动时,如果ddl-auto设置的为createcreate-drop,那么一个名存在于classpath的名为import.sql,将会被自动执行。并指定,这对我们提供DOME非常有帮助,谁说不是呢?我们完成在系统完成后,添加dome数据,并将其导出为import.sql文件,以使得在程序部署其它dome程序时被执行,不是吗?

结论:上面的信息,对我们本次的任务没有帮助。

82.3 Initialize a Database初始化一个数据库。

大概讲了:
spring boot可以分别由classpath中的schema.sqldata.sql中加载数据。然后又指出,如果存在schema-${platform}.sqldata-${platform}.sql时,spring boot也会执行。其中,platform是指在spring.datasource.platform设置的值,比如:我们将spring.datasource.platform的值设置为:hsqldb, h2, oracle, mysql, postgresql或者其它的。

然后又给出3点,第一点说Spring Boot会自动为我们创建一个内置的数据库,在启动内置数据库时,默认会加载sql脚本(意思是其它的数据库则不会),我们可以使用spring.datasource.initialization-mode来定义它的属性,以使得在使用其它数据库时,也默认加载这些脚。又说:在程序启时,如果执行sql scripts出错,那么程序就报错退出来了,然后我们可以通过更改spring.datasource.continue-on-error的值来改变。最后点说:我们可以选择是让Hiberante为我们自己动生成数据表还是执行schema.sql来生成数据表,但是不能二者全部选择,如果我们想使用schema.sql,那么就要禁用spring.jpa.hibernate.ddl-auto。(在82.2中,指出了设置为create或是create-drop为启动,其它为禁用)

迭代式开发

我们很难一次的将sql的函数写正确,如果你直接在schema.sql中写,无异于自己挖坑。

  1. 在创建sql函数的过程中,我们首先找到navicat,连接数据库后,新建函数,并保存测试,直至该函数可以通过,并且实现我们的功能。
  2. 有了函数,我们再把这个函数的内容复制到查询中,在询中,对;进行转义。比如,我们转义为\\。然后运行查询,也确认查询没有错误。
  3. 查询测试完了,最后我们把sql的脚本才正式的放到schema.sql中,并且注释到转义的部分。

进阶

spring的官方文档还给出了进阶的方法。即使用第三方库来进行更加人性化,简单的版本升级工作。请参阅:82.5 Use a Higher-level Database Migration Tool


潘杰
3.1k 声望238 粉丝