文档结构  
翻译进度:52%     翻译赏金:0 元 (?)    ¥ 我要打赏

源代码版本控制是一个被广泛讨论过的话题,并且有许多解决方案,比如 Git, Subversion,以及 Mercurial. 但是数据以及模式如何进行版本控制呢,如何与操作这些数据的应用程序保持同步呢?在这篇文章中,我们会学习Flyway,这是一款可以帮助开发人员在Java应用程序上进行数据库版控制的工具。

Flyway是什么?

Flyway是一款工具,由Boxfuse开发。Flyway能使开发人员能够应用版本控制支持Java应用程序的数据库。有了Flyway,我们可以在应用程序的生命周期里轻松集成普通的SQL脚本,并且保证数据库在无人为干预下始终是兼容的。

第 1 段(可获 1.59 积分)

Flyway如何运行?

Flywayt通过检查数据库的当前版本以及在应用程序的其余部分启动前自动执行新集成的方式运作。每当开发人员需要改变数据库的模式或是对数据进行一些修改,他们需要在目录中创建一个SQL脚本,其名称需要遵循Flyway的命名规则。通常目录是classpath:db/migration,不过如果需要,可以修改默认目录。

Flyway中的命名规则包含如下内容:

  • 前缀: 默认是 V.
  • 版本: 圆点或者下划线在你想要添加的地方添加.
  • 分隔符: 默认为 __ (两个下划线),将版本与描述分开。
  • 描述:用下划线或空格隔开的文本。
  • 后缀: 默认是 .sql.
第 2 段(可获 1.56 积分)

举个例子,如下语句都是Flyway有用的脚本:

  • V1.0001__some_description.sql 
  • V1_0001__another_description.sql 
  • V002_1_5__my_new_script.sql 
  • V15__some_other_script.sql 

在内部,Flyway通过数据库一张特别表单的记录控制数据库版本 (即第一次迁移),用如下结构创建了名为schema_version的表格:

# column name  | column type
installed_rank | integer
version        | varchar(50)
description    | varchar(200)
type           | varchar(20)
script         | varchar(1000)
checksum       | integer
installed_by   | varchar(100)
installed_on   | timestamp
execution_time | integer
success        | boolean
第 3 段(可获 0.6 积分)

然后当Flyway识别到第一次代码集成发生时,将其添加到记录中。举个例子,比如名为 V1__users.sql的sql语句。当Flyway用此脚本同步数据库时,将在schema_version中添加如下记录:

installed_rank | version | description | type |     script     |  checksum   | installed_by |     installed_on
---------------+---------+-------------+------+----------------+-------------+--------------+----------------------
             1 | 1       | users       | SQL  | V1__users.sql  |   841518221 | postgres     | 2017-03-25 19:15:59
第 4 段(可获 0.51 积分)

然后开发人员添加第二个脚本,将其命名为V2__another_script.sql, Flyway能查询schema_version 表格当前的数据库版本是什么,并判断出需要运行什么脚本。

大多数Flyway用来控制数据库版本的默认属性可以被改变。 如果需要的话,不管代码集成是否会发生故障,开发人员可以改变Flyway使用的表格名称。 可以参加 Flyway的更多文档.

我们如何整合Flyway?

第 5 段(可获 1.25 积分)

Flyway可以与大多数支持Java环境的构建工具轻松集成。由Boxfuse生产的针对Maven, GradleAnt ,SBT的可用插件,当然不局限于这些。但即使不用或者不想使用上述任意一款构建工具进行集成,通过CLI tool或者直接通过Flyway API仍旧可以使用Flyway。

除了这些选择外,开发者社区静心制定了一些开发人员插件。这些插件使得Flyway和类似 Spring Boot, Grails 以及Play等一些流行框架集成使用更加方便。

在下一节中,我们将看下Flyway如何与Spring Boot框架集成。

第 6 段(可获 1.3 积分)

Flyway的运作

为了更好的理解Flyway以及其他控制数据库版本的解决方案是如何帮助我们的, 我们来做个练习。将空的Spring Boot应用添加 Customer 实体,在其上面直接包含详细的联系方式 (联系人姓名,邮件地址以及电话号码) 。 使用Flyway创建含有一些数据的数据库schema (一个单独表),还创建一个RESTful 端点用来检索所有的持久性客户。

创建第一个应用程序的版本后,我们将它重构,并抽取详细的联系方式到一个空实体。这个实体有可能添加多个联系方式到Customer实体。在这过程中,我们将继续使用Flyway创建第二个表格contact,并迁移原有的客户联系方式。

第 7 段(可获 1.8 积分)

 引导Spring Boot应用

首先在GitHub上关注并复制 repo(包含 Spring Boot 应用)。此应用程序基于Maven,使得在任何Maven兼容的IDE都可以导入它。目前为止,只有两个依赖包spring-boot-starter-web和spring-boot-starter-data-jpa在此应用上。

只需在根目录下运行 mvn spring-boot:run命令或者通过IDE环境就可以运行应用程序。但如果既不添加Customer实体,也不添加RESTful端点,此应用程序在当前不会起太大作用。

 

第 8 段(可获 1.13 积分)

在Spring Boot上集成Flyway

为了在 Spring Boot上集成Flyway, 需要在应用程序上添加 flyway-core 作为依赖。但是由于没有配置任何数据库,我们还需要添加另一个依赖:hsqldb。 HSQLDB是一款由Java编写并能轻松与类似Spring框架集成的内存数据库。 HSQLDB将有助于此实战演练。打开pom.xml文件,将两个依赖都进行添加,见如下文件:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
    <!-- everything else -->

    <dependencies>
        <!-- other dependencies -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.3.3</version>
        </dependency>
    </dependencies>
  <!-- everything else -->

</project>
第 9 段(可获 0.9 积分)

为了保证HSQLDB在应用程序重启后能保持数据持久, 需要编辑src/main/resources/application.properties,并用如下的键值对进行配置。

# defining location for HSQLDB's data
spring.datasource.url=jdbc:hsqldb:file:data/app
# disabling Hibernate's auto schema generation
spring.jpa.hibernate.ddl-auto=none

处理Customer实体

现在已经在应用程序中正确配置了一个数据库, 并用Flyway把控数据量版本。我们来创建Customer实体。com.auth0.samples包中创建实体,内容如下:

第 10 段(可获 0.78 积分)
package com.auth0.samples;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {
    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String contactName;
    private String email;
    private String phone;

    // getters & setters for all properties
}

为了能够持久的从数据库中检索客户,需创建扩展接口继承 JpaRepository类,可以命名为 CustomerRepository接口,并仍旧创建在com.auth0.samples包中。.CustomerRepository接口所含内容如下:

第 11 段(可获 0.45 积分)
package com.auth0.samples;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

如果你不熟悉Spring Data,你可能会认为这个接口不会起太大作用。但事实上它起到了很大的作用:查询所有客户,保存所有客户,并且通过其ID寻找客户。 它还提供了一个叫做 Query Methods的极佳功能,这使得通过定义空方法声明轻松创建查询。举个例子,可以添加一个通过名字寻找客户的查询,只需添加类似List<Customer> findByName(String name)的单行声明。看一下JpaRepository 的更多参考

第 12 段(可获 1.15 积分)

现在可以做到将客户信息实例化,并保存在数据库中,但是没有定义一种方法公开这些客户信息(比如面向浏览器端或者面向任何设备)。所以通过创建一个RESTful端点响应/customers/来解决此问题。 为此在com.auth0.samples包中创建了一个名为CustomerController的类,源代码如下:

package com.auth0.samples;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class CustomerController {

    @Autowired
    private CustomerRepository customerRepository;

    @RequestMapping(path = "/customers/", method = RequestMethod.GET)
    public List<Customer> getCustomers() {
        return customerRepository.findAll();
    }
}
第 13 段(可获 0.83 积分)

正如我们所看到的,CustomerController类使用了CustomerRepository类,它可以自动注入,暴露客户。有了这个类,我们可以用持久性客户来响应 HTTP GET请求。但问题是我们还没有任何持久化,事实上在数据库上没有任何customer表格。我们用Flyway创建并填充表格。

创建一个Flyway脚本

为了创建customer表格并填充一些记录,需要在 src/main/resources/db/migration/ 文件夹下创建SQL脚本。Flyway,正如之前所述,基于命名模式的数字部分构建其脚本。因为这是第一个脚本,需要将其命名为V1__customers.sql. 文件内容为:

第 14 段(可获 1.59 积分)

If we start our application now, Flyway will identify that this script is unapplied and will execute it. This process happens before Spring Boot gets a chance to bootstrap the application. This is important to guarantee that our database will have the customer table available; otherwise, we would face an error when issuing GET requests to the endpoint created.

As our application was properly configured, and Flyway got a chance to run the script, we can now issue GET requests to /customers/ without a problem. The following screenshot shows the result of a GET request issued using Postman—which is a great tool to use when developing RESTful endpoints.

第 15 段(可获 1.3 积分)

Issuing a GET request to retrieve customers

As we can see, the /customers/ endpoint responded properly with the array of customers created in our first script.

Saving Multiple Contacts to Customers

Let's say that the Product Owner of our team has spoken to the users of the application and that they were complaining about not being able to save more than one contact per customer. What can we do to help these users? Well, the first thing we can do is refactor the Customer entity, extracting the contact fields to an entity of its own. This will require us to use JPA to tie both entities, mapping many contacts to one customer, which is accomplished through the @ManyToOne annotation. Lastly, this will require us to create another endpoint to permit the front-end application to retrieve an array of contacts of a specific customer.

第 16 段(可获 1.68 积分)

The refactoring stated above refers only to the source code of our application. Accomplishing all these changes won't be enough as the database will still contain a single table, customer, with contact details embedded. To fix this issue, we will create another Flyway script, which will contain a command to create the contact table and another command to move customers' contact details to this new table.

Refactoring the Source Code

Let's begin the refactoring process by addressing the source code of our application. The first thing we will do is to create the Contact class, which will hold contact details of customers. We will create this class in the com.auth0.samples package, with the following content:

第 17 段(可获 1.41 积分)
package com.auth0.samples;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;

@Entity
public class Contact {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JsonIgnore
    @ManyToOne
    @JoinColumn (name = "customer_id")
    private Customer customer;

    private String name;
    private String email;
    private String phone;

    // getters & setters
}

Note that the Contact entity just created has the exact same fields that refer to the contact details of a customer, plus a property customer that points to the customer itself. This property has three annotations on it:

第 18 段(可获 0.45 积分)
  1. @JsonIgnore keeps the endpoint, that we will create, from serializing the customer details multiple times
  2. @ManyToOne indicates to JPA that Many contacts To One customer can exist
  3. @JoinColumn indicates to JPA which table column will be used as a foreign key

After creating the Contact entity, we will now refactor the Customer entity to remove contact properties from it. The Customer class will now look as:

package com.auth0.samples;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import java.util.List;

@Entity
public class Customer {
    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @JsonIgnore
    @OneToMany (mappedBy = "customer")
    private List<Contact> contacts;

    // getters & setters
}
第 19 段(可获 0.75 积分)

The only property on Customer that refers to contacts now is a List of contacts annotated with @JsonIgnore and @OneToMany. The first annotation keeps the existing endpoint, /customers/, from serializing contacts of customers, which could be expensive as there are no limits to the number of contacts on a customer. The second annotation, @OneToMany, indicates to JPA that can exist One customer To Many contacts.

The last thing that we will do to finish the source code refactoring, is to create the new endpoint which will be responsible for serializing the contacts of a customer. This endpoint will be created in the CustomerController class as follows:

第 20 段(可获 1.24 积分)
package com.auth0.samples;

// other imports

import org.springframework.web.bind.annotation.PathVariable;

@RestController
public class CustomerController {

    // customerRepository definition and getCustomers() method

    @RequestMapping(path = "/customers/{customerId}/contacts/", method = RequestMethod.GET)
    public List<Contact> getCustomerContacts(@PathVariable("customerId") Long customerId) {
        return customerRepository.findOne(customerId).getContacts();
    }
}

Note that to retrieve the contacts of a customer, the request issuer will have to append the customer ID to the URL of the endpoint. That is, to get the contacts of Coca-Cola, the front-end application has to issue a GET request to /customers/1/contacts/. But not before refactoring the database.

第 21 段(可获 0.6 积分)

Refactoring the Database

As already mentioned, running the application after refactoring the source code and before refactoring the database will generate errors. The database doesn't have the contact table yet, and the contact details are still residing in the customer table. To address this issue, we will create a second Flyway script. We will call this script V2__contacts.sql and will create it alongside with V1__customers.sql in the src/main/resources/db/migration/ folder. On it, we will add the following SQL commands:

create table contact (
  id int identity primary key,
  customer_id int not null,
  name varchar (255) not null,
  email varchar (255) not null,
  phone varchar (255) not null,
  constraint contact_customer_fk
    foreign key (customer_id)
    references customer (id)
);

insert into contact (customer_id, name, email, phone)
  select id, contact_name, email, phone from customer;

alter table customer drop column contact_name;
alter table customer drop column email;
alter table customer drop column phone;
第 22 段(可获 0.93 积分)

This script has three responsibilities:

  1. It has to create the contact table.
  2. It has to insert the customers' contact details in this new table.
  3. It has to drop the contact details columns from the customer table.

This is everything we need to do to refactor our database, and it is enough to make it compatible with the refactored source code. Running the application now will make Flyway identify the new script available, V2__contacts.sql, and run it before Spring Boot, making everything work together smoothly.

If we issue a GET request to /customers/1/contacts/ now, the endpoint will properly respond with the contact details of Coca-Cola, as shown below:

第 23 段(可获 1.24 积分)

Retrieving contacts of a customer

Aside: Securing Java Applications With Auth0

Auth0 makes it easy for developers to implement even the most complex identity solutions for their web, mobile, and internal applications. Need proof? Check out how easy it is to secure a RESTful Spring Boot application with Auth0.

For starters, we would need to include a couple of dependencies. Let's say that we are using the Apache Maven build manager. In that case, we would open our pom.xml file and add the following dependencies:

<?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">
  <!--project definitions ...-->
  <dependencies>
    <!--other dependencies ...-->

    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>auth0</artifactId>
      <version>1.0.0</version>
    </dependency>

    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>auth0-spring-security-api</artifactId>
      <version>1.0.0-rc.2</version>
    </dependency>
  </dependencies>   
</project>
第 24 段(可获 1 积分)

After that we would simply create or update our WebSecurityConfigurerAdapterextension, as shown in the following code excerpt:

package com.auth0.samples;

import com.auth0.spring.security.api.JwtWebSecurityConfigurer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Value("${auth0.audience}")
    private String audience;

    @Value("${auth0.issuer}")
    private String issuer;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/api/**").authenticated();

        JwtWebSecurityConfigurer
                .forRS256(audience, issuer)
                .configure(http);
    }
}
第 25 段(可获 0.21 积分)

And then configure three parameters in the Auth0 SDK, which is done by adding the following properties in the application.properties file:

# change this with your own Auth0 client id
auth0.audience=LtiwyfY1Y2ANJerCNTIbT7vVsX5zKBS5
# change this with your own Auth0 domain
auth0.issuer=https://bkrebs.auth0.com/

Making these small changes would give us a high level of security alongside with a very extensible authentication solution. With Auth0, we could easily integrate our authentication mechanism with different social identity providers — like Facebook, Google, Twitter, GitHub, etc. — and also with enterprise solutions like Active Directory and SAML. Besides that, adding Multifactor Authentication to the application would become a piece of cake.

第 26 段(可获 1.04 积分)

Want more? Take a look at Auth0's solutions and at an article that thoroughly describes how to secure a Spring Application with JWTs.

Conclusion

Having a tool like Flyway integrated into our application is a great addition. With it, we can create scripts that will refactor the database to a state that is compatible with the source code, and we can move data around to guarantee that it will reside in the correct tables.

Flyway will also be helpful if we eventually run into an issue where we need to recover the database from a backup. In a case like that, we can rest assured that Flyway will correctly identify if the combination of the source code version and the database version that we are running have any scripts that need to be applied.

第 27 段(可获 1.69 积分)

文章评论

苏州小浮云
总感觉第14段和第15段之间缺少了一张图片