总结0403

1.oauth2
https://www.jianshu.com/p/9d0264d27c3b
oauth1与oauth2
https://blog.csdn.net/w_xuexi666/article/details/54564058

  1. http basic 认证
  • 浏览器发送请求 get 到服务器,服务器检查请求是否包含 Authorization 信息,若没有则返回响应 401,并且加上响应头信息。
1
WWW-Authenticate: Basic realm="请求域"。
  • 浏览器接受到上面传递回来的响应码,会自动弹出输入框让用户输入相关的用户名和密码。之后,浏览器将用户名和密码进行 base64 编码,设置请求头Authorization,去请求服务器,并且每次请求都会将密文附加于请求头,服务器收到请求后,对密文进行验证,从而返回相应的信息,如果验证失败,会返回 401 状态码,执行上一步骤,再次进行验证。
  1. Token
    1
    token 是一串字符串,通常因为作为鉴权凭据,最常用的使用场景是 API 鉴权。

4.bifunction/function
https://www.jianshu.com/p/8dc46a2dc21d

总结0402(异常处理)

1.系统异常(即用户重试都无法解决的,例如网络异常,数据库异常,IO异常等)
AppException
ExceptionCode使用S_XXX开头
httpcode 500
2.业务异常(即可以通过用户重试或者改变参数解决的,例如参数错误,配置缺失,验证错误等)
BizException
ExceptionCode使用B_XXX开头
httpcode 420
3.代码异常(如NPE或SQLException) 和 系统异常(网络异常,环境问题等)都使用httpCode=500, ExceptionCode=S99999抛出,在服务中通过@ExceptionHandler(Throwable.class)兜底并添加code,S99999

异常处理流程:
程序中直接抛出抛出异常,最终由spring的统一异常处理(@ExceptionHandler)来捕获,打印异常信息,返回httpcode和ErrorResult
对象,而系统调用会经过ribbon,根据httpcode的状态判断是否进行重试。

3.@RestControllerAdvice spring统一异常处理
https://www.cnblogs.com/magicalSam/p/7198420.html
https://www.cnblogs.com/topfish/p/9573635.html

4.springboot 重试
spring-retry
https://blog.csdn.net/u012129558/article/details/79016732

5.spring cloud得重试机制
Feign.Retryer
ribbon重试配置

https://www.jb51.net/article/129336.htm
https://www.cnblogs.com/zhangjianbin/p/7228628.html

6.开发测试发布流程

HikariCP

本篇文章主要实现SpringBoot中使用hikariCP;
一 、使用工具

  1. JDK1.8
  2. springToolSuit(STS)
  3. maven
    二、创建项目
    1.首先创建一个SpringBoot项目,勾选web,mysql等具体怎样创建可以参考我的上两个博客;传送门
    2.maven 依赖如下:
    1)Java 8 maven artifact:
1
2
3
4
5
6
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.1</version>
<scope>compile</scope>
</dependency>

2)Java 7 maven artifact:

1
2
3
4
5
6
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
<version>2.4.11</version>
<scope>compile</scope>
</dependency>

我的maven依赖为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<dependencies>
<!-- spring aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- spring data jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- hibernate 依赖 -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<!-- JDBC连接数据库,因为要用HikariCP,所以需要将SpringBoot中的tomcat-jdbc排除 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- HikariCP 连接池依赖,从父依赖获取额版本 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<!-- <scope>runtime</scope> -->
</dependency>

<!-- 因为配置了thymeleaf 模板,可以将此注释
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> -->
<!-- thymeleaf 模板 默认包含spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 连接mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- spring boot 内置tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

<!--@ConfigurationProperties注解-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- net json 这个必须配置jdk的版本号 -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->

<!-- LEGACYHTML5需要搭配一个额外的库NekoHTML才可用,解决严格的html验证问题 -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>

<!-- 单元测试相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

SpringBoot父依赖如下:

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

这里需要注意的是,因为用了最新的SpringBoot版本,HikarICP从SpringBoot继承版本,所以JDK需要配置为1.8,如果不是将会出现错误,错误原因将会在下面展示。

3.数据库连接配置文件如下(我将数据库连接配置单独写在了一个配置文件,这样找起来比较清晰,文件名为 datasource.properties ,后面的配置类中要用到此名字):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#第一个数据源(多数据源将会在后面介绍,primary表示为第一个数据源)
spring.datasource.primary.dataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
spring.datasource.primary.dataSourceProperties.serverName=localhost
spring.datasource.primary.dataSourceProperties.portNumber=3306
spring.datasource.primary.dataSourceProperties.databaseName=newrecruit
spring.datasource.primary.username=root
spring.datasource.primary.password=yourpassword
# 下面为连接池的补充设置,应用到上面所有数据源中
#自动提交
spring.datasource.default-auto-commit=true
#指定updates是否自动提交
spring.datasource.auto-commit=true
spring.jpa.show-sql = true
spring.datasource.maximum-pool-size=100
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.time-between-eviction-runs-millis=18800
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000

# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
#spring.jpa.database=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

数据库配置文件写好以后,开始写配置类 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.zlc.config;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;


/**
* <p>Company: </p>
* @Description:
* @Create Date: 2017年8月13日下午11:59:49
* @Version: V1.00
* @Author: 追到乌云的尽头找太阳
*/
@Configuration
@PropertySource("classpath:datasource.properties")
public class DataSourceConfig {

private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);


@Bean(name = "primaryDataSource")
@Primary
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary" )
public DataSource primaryDataSource() {
logger.info("数据库连接池创建中.......");
return DataSourceBuilder.create().build();
}

}

一个@PropertySource(“classpath:datasource.properties”)注解,就可以免去我们自己写读取配置文件的麻烦
第一个数据源的配置类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.zlc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import java.util.Map;

/**
* <p>Company: 信息技术研究所 </p>
* @Description: 第一个数据源的配置类
* @Create Date: 2017年5月11日下午9:22:12
* @Version: V1.00
* @Author: 追到乌云的尽头找太阳
*/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.zlc.dao" }) //设置Repository所在位置
public class PrimaryDataSouceConfig {

@Autowired @Qualifier("primaryDataSource")
private DataSource primaryDataSource;

@Primary
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}

@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("com.zlc.entity") //设置实体类所在位置
.persistenceUnit("primaryPersistenceUnit")
.build();
}

@Autowired
private JpaProperties jpaProperties;

private Map<String, String> getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
}

@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}

上面有连个需要注意的地方,一个是JPA所在的包名: basePackages= { “com.b505.dao” }) //设置Repository所在位置,一定不能写错,一个JPA实体类所在的位置: .packages(“com.b505.entity”) //设置实体类所在位置。

我们在main方法中我们可以检查一下(一定要注意SpringBoot项目的结构,因为SpringBoot是自动扫描并注册类注册到Spring的上下文中所以main所在的类的包名一定是最大的,这样用其他注解的类才能正常注册,我的项目结构如下):
1

如果你的controller写在了com.zlca.web;那么项目编译不会产生错误,但是此web层的映射全都不能用,因为没有注册到Spring中。SpringBoot是扫描@SpringBootApplication下的类以及此注解的子文件夹下的类;
main方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.zlc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import com.zaxxer.hikari.HikariDataSource;

/**
* <p>Company: 信息技术研究所 </p>
* @Description: 程序的入口
* @Create Date: 2017年9月14日下午1:11:05
* @Version: V1.00
* @Author: 追到乌云的尽头找太阳
*/
@SpringBootApplication
public class RecruitmentApp {

public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(
RecruitmentApp.class, args);
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println("datasource is :" + dataSource);
//检查数据库是否是hikar数据库连接池
if (!(dataSource instanceof HikariDataSource)) {
System.err.println(" Wrong datasource type :"
+ dataSource.getClass().getCanonicalName());
System.exit(-1);
}
try {
Connection connection = dataSource.getConnection();
ResultSet rs = connection.createStatement()
.executeQuery("SELECT 1");
if (rs.first()) {

System.out.println("Connection OK!");
} else {
System.out.println("Something is wrong");
}
// connection.close();
// System.exit(0);

} catch (SQLException e) {
System.out.println("FAILED");
e.printStackTrace();
System.exit(-2);
// TODO: handle exception
}

}

}

HIkariCP已经配置好了,启动main;
2

完工;这里需要注意的一点,如果是用jdk1.7,则会出现如下错误:
3
4

这个就是本博客一开始中说的jdk版本和Hikari的版本要对应上。此错误只需要将JDK换成1.8即可。

参考:
https://blog.csdn.net/chen15369337607/article/details/78142751
https://blog.csdn.net/clementad/article/details/46928621

Flyway

什么是Flyway?

Flyway is an open-source database migration tool. It strongly favors simplicity and convention over configuration.

Flyway是一款开源的数据库版本管理工具,它更倾向于规约优于配置的方式。Flyway可以独立于应用实现管理并跟踪数据库变更,支持数据库版本自动升级,并且有一套默认的规约,不需要复杂的配置,Migrations可以写成SQL脚本,也可以写在Java代码中,不仅支持Command Line和Java API,还支持Build构建工具和Spring Boot等,同时在分布式环境下能够安全可靠地升级数据库,同时也支持失败恢复等。

Flyway主要基于6种基本命令:Migrate, Clean, Info, Validate, Baseline and Repair,稍候会逐一分析讲解。目前支持的数据库主要有:Oracle, SQL Server, SQL Azure, DB2, DB2 z/OS, MySQL(including Amazon RDS), MariaDB, Google Cloud SQL, PostgreSQL(including Amazon RDS and Heroku), Redshift, Vertica, H2, Hsql, Derby, SQLite, SAP HANA, solidDB, Sybase ASE and Phoenix.

关于Flyway的优势,支持的数据库以及与其他数据库版本工具的对比,可以阅读Flyway官网介绍

为什么使用Flyway?

通常在项目开始时会针对数据库进行全局设计,但在开发产品新特性过程中,难免会遇到需要更新数据库Schema的情况,比如:添加新表,添加新字段和约束等,这种情况在实际项目中也经常发生。那么,当开发人员完成了对数据库更的SQL脚本后,如何快速地在其他开发者机器上同步?并且如何在测试服务器上快速同步?以及如何保证集成测试能够顺利执行并通过呢?

假设以Spring Boot技术栈项目为例,可能有人会说,本地使用Hibernate自动更新数据库Schema模式,然后让QA或DEV到测试服务器上手动执行SQL脚本,同时可以写一个Gradle任务自动执行更新。

个人觉得,对于Hibernate自动更新数据库,感觉不靠谱,不透明,控制自由度不高,而且有时很容易就会犯错,比如:用SQL创建的某个字段为VARCHAR类型,而在Entity中配置的为CHAR类型,那么在运行集成测试时,自动创建的数据库表中的字段为CHAR类型,而实际SQL脚本期望的是VARCHAR类型,虽然测试通过了,但不是期望的行为,并且在本地bootRun或服务器上运行Service时都会失败。另外,到各测试服务器上手动执行SQL脚本费时费神费力的,干嘛不自动化呢,当然,对于高级别和PROD环境,还是需要DBA手动执行的。最后,写一段自动化程序来自动执行更新,想法是很好的,那如果已经有了一些插件或库可以帮助你更好地实现这样的功能,为何不好好利用一下呢,当然,如果是为了学习目的,重复造轮子是无可厚非的。

其实,以上问题可以通过Flyway工具来解决,Flyway可以实现自动化的数据库版本管理,并且能够记录数据库版本更新记录,Flyway官网对Why database migrations结合示例进行了详细的阐述,有兴趣可以参阅一下。

Flyway如何工作的?

Flyway对数据库进行版本管理主要由Metadata表和6种命令完成,Metadata主要用于记录元数据,每种命令功能和解决的问题范围不一样,以下分别对metadata表和这些命令进行阐述,其中的示意图都来自Flyway的官方文档。

Metadata Table

Flyway中最核心的就是用于记录所有版本演化和状态的Metadata表,在Flyway首次启动时会创建默认名为SCHEMA_VERSION的元数据表,其表结构为(以MySQL为例):

Field Type Null Key Default
version_rank int(11) NO MUL NULL
installed_rank int(11) NO MUL NULL
version varchar(50) NO PRI NULL
description varchar(200) NO NULL
type varchar(20) NO NULL
script varchar(1000) NO NULL
checksum int(11) YES NULL
installed_by varchar(100) NO NULL
installed_on timestamp NO CURRENT_TIMESTAMP
execution_time int(11) NO NULL
success tinyint(1) NO MUL NULL

Flyway官网上提供了一个很清晰的示例How Flyway works,可以参阅一下。

Migrate

Migrate是指把数据库Schema迁移到最新版本,是Flyway工作流的核心功能,Flyway在Migrate时会检查Metadata(元数据)表,如果不存在会创建Metadata表,Metadata表主要用于记录版本变更历史以及Checksum之类的。
1
Migrate时会扫描指定文件系统或Classpath下的Migrations(可以理解为数据库的版本脚本),并且会逐一比对Metadata表中的已存在的版本记录,如果有未应用的Migrations,Flyway会获取这些Migrations并按次序Apply到数据库中,否则不需要做任何事情。另外,通常在应用程序启动时应默认执行Migrate操作,从而避免程序和数据库的不一致性。

Clean

Clean相对比较容易理解,即清除掉对应数据库Schema中的所有对象,包括表结构,视图,存储过程,函数以及所有的数据等都会被清除。
2
Clean操作在开发和测试阶段是非常有用的,它能够帮助快速有效地更新和重新生成数据库表结构,但特别注意的是:不应在Production的数据库上使用!

Info

Info用于打印所有Migrations的详细和状态信息,其实也是通过Metadata表和Migrations完成的,下图很好地示意了Info打印出来的信息。
3

Info能够帮助快速定位当前的数据库版本,以及查看执行成功和失败的Migrations。

Validate

Validate是指验证已经Apply的Migrations是否有变更,Flyway是默认是开启验证的。
4
Validate原理是对比Metadata表与本地Migrations的Checksum值,如果值相同则验证通过,否则验证失败,从而可以防止对已经Apply到数据库的本地Migrations的无意修改。

Baseline

Baseline针对已经存在Schema结构的数据库的一种解决方案,即实现在非空数据库中新建Metadata表,并把Migrations应用到该数据库。
5
Baseline可以应用到特定的版本,这样在已有表结构的数据库中也可以实现添加Metadata表,从而利用Flyway进行新Migrations的管理了。

Repair

Repair操作能够修复Metadata表,该操作在Metadata表出现错误时是非常有用的。
6

Repair会修复Metadata表的错误,通常有两种用途:

  • 移除失败的Migration记录,该问题只是针对不支持DDL事务的数据库。
  • 重新调整已经应用的Migratons的Checksums值,比如:某个Migratinon已经被应用,但本地进行了修改,又期望重新应用并调整Checksum值,不过尽量不要这样操作,否则可能造成其它环境失败。

如何使用Flyway?

这里将主要关注在Gradle和Spring Boot中集成并使用Flyway,数据库通常会采用MySQL、PostgreSQL、H2或Hsql等。

正确创建Migrations

Migrations是指Flyway在更新数据库时是使用的版本脚本,比如:一个基于Sql的Migration命名为V1__init_tables.sql,内容即是创建所有表的sql语句,另外,Flyway也支持基于Java的Migration。Flyway加载Migrations的默认Locations为classpath:db/migration,也可以指定filesystem:/project/folder,其加载是在Runtime自动递归地执行的。
8

除了需要指定Location外,Flyway对Migrations的扫描还必须遵从一定的命名模式,Migration主要分为两类:Versioned和Repeatable。

  • Versioned migrations
    一般常用的是Versioned类型,用于版本升级,每一个版本都有一个唯一的标识并且只能被应用一次,并且不能再修改已经加载过的Migrations,因为Metadata表会记录其Checksum值。其中的version标识版本号,由一个或多个数字构成,数字之间的分隔符可以采用点或下划线,在运行时下划线其实也是被替换成点了,每一部分的前导零会被自动忽略。

  • Repeatable migrations
    Repeatable是指可重复加载的Migrations,其每一次的更新会影响Checksum值,然后都会被重新加载,并不用于版本升级。对于管理不稳定的数据库对象的更新时非常有用。Repeatable的Migrations总是在Versioned之后按顺序执行,但开发者必须自己维护脚本并且确保可以重复执行,通常会在sql语句中使用CREATE OR REPLACE来保证可重复执行。

默认情况下基于Sql的Migration文件的命令规则如下图所示:
9

其中的文件名由以下部分组成,除了使用默认配置外,某些部分还可自定义规则。

  • prefix: 可配置,前缀标识,默认值V表示Versioned,R表示Repeatable
  • version: 标识版本号,由一个或多个数字构成,数字之间的分隔符可用点.或下划线_
  • separator: 可配置,用于分隔版本标识与描述信息,默认为两个下划线__
  • description: 描述信息,文字之间可以用下划线或空格分隔
  • suffix: 可配置,后续标识,默认为.sql

另外,关于如何使用基于Java的Migrations,有兴趣可以参考Java-based migrations

支持的数据库

目前Flyway支持的数据库还是挺多的,包括:Oracle, SQL Server, SQL Azure, DB2, DB2 z/OS, MySQL(including Amazon RDS), MariaDB, Google Cloud SQL, PostgreSQL(including Amazon RDS and Heroku), Redshift, Vertica, H2, Hsql, Derby, SQLite, SAP HANA, solidDB, Sybase ASE and Phoenix。
目前来说,个人用得比较多的数据库是PostgreSQLMySQLH2Hsql,针对每种数据库的flyway.url示例配置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PostgreSQL

flyway.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=myschema

# MySQL

flyway.url = jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC&useSSL=true

# H2

flyway.url = jdbc:h2:./.tmp/testdb

# Hsql

flyway.url = jdbc:hsqldb:hsql//localhost:1476/testdb

Flyway命令行

Flyway的命令行工具支持直接在命令行中运行Migrate, Clean, Info, Validate, BaselineRepair6种命令,不需要借助其他Build工具,不需要应用程序运行在JVM中,只需要单纯的命令行即可,但需要根据不同的操作系统下载并安装该命令行工具。Flyway会依次搜索以下配置文件,越靠后的配置会覆盖靠前的配置:

  • /conf/flyway.conf
  • /flyway.conf
  • /flyway.conf

一个典型Flyway项目示例目录结构如下:
10

更多关于Flyway命令行使用可以参考Flyway Command-line

在Gradle中的应用

首先需要在Gradle中引入Flyway插件,通常有两种方式:

  • 方式一:采用buildscript依赖方式。

    buildscript {
    repositories {
    mavenCentral()
    }
    dependencies {
    classpath(“org.flywaydb:flyway-gradle-plugin:4.0.3”)
    }
    }
    apply plugin: ‘org.flywaydb.flyway’

  • 方式二(推荐):采用DSL方式引用Plugins。

    plugins {
    id “org.flywaydb.flyway” version “4.0.3”
    }

而在Gradle中配置Flyway Properties有两种方式:

  • 方式一:在build.gradle中配置Flyway Properties。

    flyway {
    url = jdbc:h2:./.tmp/testdb
    user = sa
    password =
    }

    或者写成:

    project.ext[‘flyway.url’] = ‘jdbc:h2:./.tmp/testdb’
    project.ext[‘flyway.user’] = ‘sa’
    project.ext[‘flyway.password’] = ‘’

  • 方式二:在gradle.properties中配置Flyway Properties。

    flyway.url = jdbc:h2:./.tmp/testdb
    flyway.user = sa
    flyway.password =

如果期望在运行Gradle Clean/Build Tasks时自动执行Flyway的某些任务,可以设置dependsOn,若不期望隐式执行Flyway任务,可以不配置。

1
2
clean.dependsOn flywayRepair # To repair the Flyway metadata table
build.dependsOn flywayMigrate # To migrate the schema to the latest version

另外,其它Tasks:flywayInfo, flywayValidate, flywayBaseline分别对应到Flyway的命令。在使用Spring Boot时,运行./gradlew bootRun会自动检查并加载最新的db.migration脚本。

特别注意:在Production环境中不应执行./gradlew flywayClean,除非你知道自己的行为和目的,因为该命令会清除所有的数据库对象,相当危险。

更多关于Flyway在Gradle中的使用请参阅Flyway Gradle Plugin

与Spring Boot集成

在Spring Boot中,如果加入Flyway的依赖,则会自动引用Flyway并使用默认值,但可以修改并配置FlywayProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
flyway.baseline-description= # The description to tag an existing schema with when executing baseline.

flyway.baseline-version=1 # Version to start migration.

flyway.baseline-on-migrate=false # Whether to execute migration against a non-empty schema with no metadata table

flyway.check-location=false # Check that migration scripts location exists.

flyway.clean-on-validation-error=false # will clean all objects. Warning! Do NOT enable in production!

flyway.enabled=true # Enable flyway.

flyway.encoding=UTF-8 # The encoding of migrations.

flyway.ignore-failed-future-migration=true # Ignore future migrations when reading the metadata table.

flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it.

flyway.locations=classpath:db/migration # locations of migrations scripts.

flyway.out-of-order=false # Allows migrations to be run "out of order".

flyway.placeholder-prefix= # The prefix of every placeholder.

flyway.placeholder-replacement=true # Whether placeholders should be replaced.

flyway.placeholder-suffix=} # The suffix of every placeholder.

flyway.placeholders.*= # Placeholders to replace in Sql migrations.

flyway.schemas= # Default schema of the connection and updating

flyway.sql-migration-prefix=V # The file name prefix for Sql migrations

flyway.sql-migration-separator=__ # The file name separator for Sql migrations

flyway.sql-migration-suffix=.sql # The file name suffix for Sql migrations

flyway.table=schema_version # The name of Flyway's metadata table.

flyway.url= # JDBC url of the database to migrate. If not set, the primary configured data source is used.

flyway.user= # Login user of the database to migrate. If not set, use spring.datasource.username value.

flyway.password= # JDBC password if you want Flyway to create its own DataSource.

flyway.validate-on-migrate=true # Validate sql migration CRC32 checksum in classpath.

若使用Gradle,通常在build.gradle引入org.flywaydb:flyway-core:4.0.3依赖后即可使用。可能会有以下几种需求:

  • 在本地Run和Tests都会使用内存数据库,其中的spring.jpa.hibernate.ddl-auto都设置为validate,Schema不需要Hibernate自动生成,并期望使用Flyway,而在线上环境会使用真实数据库,并不期望使用Flyway,如何实现呢?
    解决方案:可以在common.properties中配置flyway.enabled=false,然后在local或dev的配置中启用Flyway即可。通常推荐使用此模式,毕竟可以对不同的环境进行控制,另外本地Run不会依赖真实数据库,又能保证数据库Schema是按脚本创建的。

  • 在运行Tests会使用内存数据库,有单独的配置文件,不使用Flyway,而在本地bootRun时会使用真实数据库,使用Flyway,毕竟不想每次Schema改后都在本地手动去执行脚本,如何实现?
    解决方案:设置bootRun.dependsOn动态添加Flyway的依赖即可:

    addFlywayDenpendency {
    doLast {
    dependencies {
    compile(‘org.flywaydb:flyway-core:4.0.3’)
    }
    }
    }
    bootRun.dependsOn=addFlywayDenpendency

  • 若项目有多个团队同时开发不同的功能,需要新建多个分支,并且都会涉及到数据库Schema更改,当后期Merge时,Migration的版本如何控制并且不会产生数据库更改的冲突呢?
    解决方案:如果两个分支的数据库更改有冲突,要么最初数据库设计不合理,要么目前数据库更改不合理,所以需要团队进行全局考虑和协调。而针对数据库在同一段时间有修改,但不会造成冲突的情况,通常实际项目中主要存在这样的情况,那可以设置flyway.out-of-order=true,这样允许当v1和v3已经被应用后,v2出现时同样也可以被应用。其实在本地使用内存数据库不会存在该问题,因为数据库所有对象会自动清除掉,而在local或dev中使用真实数据库时可遇到这样的问题,因此需要注意一下了。
    另外,值得一提的是Flyway的参数ignore-failed-future-migration默认为true,使用情形为:当Rollback数据库更改到旧版本,而metadata表中已存在了新版本时,Flyway会忽略此错误,只会显示警告信息。

结束语

总得来说,Flyway可以有效改善数据库版本管理方式,如果项目中还未使用,不防尝试一下。如果有兴趣,也可以关注MyBatis Migration,功能支持没有Flyway多,属于更轻量级的数据库版本管理工具。如果在使用过程中遇到了问题或坑,欢迎留言一起交流讨论。


References

参考:https://blog.csdn.net/u014091123/article/details/78133522
https://www.jianshu.com/p/5b3ee67e3598

总结0331

1.activiti引擎相关的流程
2.flow项目的运行流程和代码结构
3.代码的整洁,规范
4.HikariCP数据库连接池
5.flyway数据库版本控制

总结0331

1.activiti引擎相关的流程
2.flow项目的运行流程和代码结构
3.代码的整洁,规范
4.HikariCP数据库连接池
5.flyway数据库版本控制

activiti

1、工作流引擎:

ProcessEngine对象,它是Activiti的核心类,由该类可以获取其他服务实例(历史服务、创库服务、任务服务、用户参与者服务);

2、BPMN:

业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram);

3、数据库(先学后看)

Activiti的后台是有数据库来支持的,所有的表都是由ACT开头的。第二部分是表示表的用途的两个字母标识。
用途也和服务的API对应。Activiti工作流的数据库有23张表。

ACT_RE_*: ‘RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。

ACT_RU_*: ‘RU’表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

ACT_ID_*: ‘ID’表示identity。 这些表包含身份信息,比如用户,组等等。

ACT_HI_*: ‘HI’表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。

ACT_GE_*: 通用数据, 用于不同场景下,如存放资源文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
3.3.1:资源库流程规则表
1) act_re_deployment 部署信息表
2) act_re_model 流程设计模型部署表
3) act_re_procdef 流程定义数据表
3.3.2:运行时数据库表
1) act_ru_execution 运行时流程执行实例表
2) act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
3) act_ru_task 运行时任务节点表
4) act_ru_variable 运行时流程变量数据表
3.3.3:历史数据库表
1) act_hi_actinst 历史节点表
2) act_hi_attachment 历史附件表
3) act_hi_comment 历史意见表
4) act_hi_identitylink 历史流程人员表
5) act_hi_detail 历史详情表,提供历史变量的查询
6) act_hi_procinst 历史流程实例表
7) act_hi_taskinst 历史任务实例表
8) act_hi_varinst 历史变量表
3.3.4:组织机构表
1) act_id_group 用户组信息表
2) act_id_info 用户扩展信息表
3) act_id_membership 用户与用户组对应信息表
4) act_id_user 用户信息表
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足
3.3.5:通用数据表
1) act_ge_bytearray 二进制数据表
2) act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录,

4、Activiti项目

1、LeaveBill.png

1

2、LeaveBill.bpmn

LeaveBill.bpmn是上面LeaveBill.png工作流的配置文件
Start event:开始事件
End entit:结束事件
User task:用户任务活动
Service task:服务任务活动
Exclusive gateway:独家网关,排它网关通道,只能有一条分支执行,如if else
Parallel gateway:并行网关,并行网关通道,所有分支一块执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="leaveBill" name="leaveProcess" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<!--开启了三个工作流-->
<userTask id="usertask1" name="请假申请" activiti:assignee="张三"></userTask>
<userTask id="usertask2" name="审批[主管]" activiti:assignee="李四"></userTask>
<userTask id="usertask3" name="审批[经理]" activiti:assignee="王五"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="leaveBill" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leaveBill">
<bpmndi:BPMNPlane bpmnElement="leaveBill" id="BPMNPlane_leaveBill">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="295.0" y="20.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="260.0" y="80.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="260.0" y="160.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="260.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="290.0" y="350.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="312.0" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="312.0" y="80.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="312.0" y="135.0"></omgdi:waypoint>
<omgdi:waypoint x="312.0" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="312.0" y="215.0"></omgdi:waypoint>
<omgdi:waypoint x="312.0" y="250.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="leaveBill" id="BPMNEdge_leaveBill">
<omgdi:waypoint x="312.0" y="305.0"></omgdi:waypoint>
<omgdi:waypoint x="307.0" y="350.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

5 核心API

5.1:ProcessEngine

说明:
1) 在Activiti中最核心的类,其他的类都是由他而来。
2) 产生方式:
2

在前面看到了两种创建ProcessEngine(流程引擎)的方式,而这里要简化很多,调用ProcessEngines的getDefaultProceeEngine方法时会自动加载classpath下名为activiti.cfg.xml文件。

3) 可以产生RepositoryService
3

4) 可以产生RuntimeService
4

5) 可以产生TaskService
5

各个Service的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RepositoryService
管理流程定义

RuntimeService
执行管理,包括启动、推进、删除流程实例等操作

TaskService
任务管理

HistoryService
历史管理(执行完的数据的管理)

IdentityService
组织机构管理

FormService
一个可选服务,任务表单管理

ManagerService

5.2:RepositoryService

是Activiti的仓库服务类。所谓的仓库指流程定义文档的两个文件:bpmn文件和流程图片。
1) 产生方式
6

2) 可以产生DeploymentBuilder,用来定义流程部署的相关参数
7

3) 删除流程定义
8

5.3:RuntimeService

是activiti的流程执行服务类。可以从这个服务类中获取很多关于流程执行相关的信息。

5.4:TaskService

是activiti的任务服务类。可以从这个类中获取任务的信息。

5.5:HistoryService

是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。

5.6:ProcessDefinition

流程定义类。可以从这里获得资源文件等。

5.7:ProcessInstance

代表流程定义的执行实例。如范冰冰请了一天的假,她就必须发出一个流程实例的申请。一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。

5.8:Execution

Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,Execution就是同ProcessInstance。流程按照流程定义的规则执行一次的过程,就可以表示执行对象Execution。
如图为ProcessInstance的源代码:
9

从源代码中可以看出ProcessInstance就是Execution。但在现实意义上有所区别:
10
在单线流程中,如上图的贷款流程,ProcessInstance与Execution是一致的。
11
这个例子有一个特点:wire money(汇钱)和archive(存档)是并发执行的。这个时候,总线路代表ProcessInstance,而分线路中每个活动代表Execution。
总结:

* 一个流程中,执行对象可以存在多个,但是流程实例只能有一个。

* 当流程按照规则只执行一次的时候,那么流程实例就是执行对象。

Activiti中的activiti:expression和activiti:delegateExpression有什么区别?

以serviceTask为例,delegateExpression是引用一个JavaDelegate实现bean,具体的操作在这个bean中定义;而expression则可以写成#{loggerHandler.log()} 这样的,表达式本身就是要做的操作。
参考:
https://blog.csdn.net/weixin_43220261/article/details/85059106
https://blog.csdn.net/qq877507054/article/details/60143099

flow(TriggerJob)流程

  1. customerJourneyTriggerJob.doWork(message)
    根据routingKey得出eventType和actionType根据名称拼接处QueueWorker的实现bean
  2. 调用QueueWorker.work方法
    根据body得出EventDTO,根据dto的event得出EventTriggerCallback接口的实现,
  3. 调用EventTriggerCallback.onTrigger
    根据contactId和eventid 生成CustomerJourneyContext,并调用
  4. workflowTrigger.trigger
    根据eventdto的event查出所有customerJourney,验证其condition是否满足,满足则发送p2PMessageProducer.send(EXECUTOR_QUEUE, context) mq消息进行处理

flow(active)流程

active相关旅程。
CustomerJourneyController.activate方法:
1先保存customerJourney
2然后调用customerJourneyService.activate(triggerId):
2.1查询出customerJourney,根据uimodel转换出LinkedJourney,
2.2再根据LinkedJourney得到相关得properties加入customerJourney
2.3根据customerJourney和linkedJourney进行deploy

1
2
3
4
5
deploy(
[1].bpmnGenerator.generateBPMN->converter出bpmnModel,[2].bpmnGenerator.getTriggerCondition(triggerNode)->根据triggerNode得到comditionstring,此处根据根节点workflowEvent得出beanname 从而得到对应得TriggerConditionGenerator接口得具体实现,核心方法generateInternal主要用来返回生成的condition字符串。(所有得trigger generator在此处使用)
[3].customerJourney设置得到的bpmn,triggerType,workflowEvent,condition
[4].根据customerJourneyname和bpmn进行deploy并返回ProcessDefinitionId,并设置到customerJourney中
[5].customerJourney中每个properties都设置processDedfId和triggerId)

2.4 deploy结束返回customerJourney,设置ACTIVE状态,并更新表数据
2.5最后保存所有properties节点信息