使用Spring Data REST分分钟快速创建API

21-07-07 banq

Spring Data REST 是Spring Data 项目的一部分,可以轻松地在 Spring Data 存储库之上构建超媒体驱动的 REST Web 服务。

依赖项:Spring Boot DevTools、Spring Data JPA、Spring Data Rest、MySQL Driver。请参阅下面的 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hamdibouallegue</groupId>
    <artifactId>data-rest-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>data-rest-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <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-data-rest</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>

添加一些属性:

# The base url of our REST APIs
spring.data.rest.basePath=/api

server.port=8888
## Database Properties
spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=database_user
spring.datasource.password=database_password
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=create-drop

 

创建一些模型:

现在让我们创建一个名为的包,models并在其中创建两个类:Client 和 Address。

package com.hamdibouallegue.datarestdemo.models;

import javax.persistence.*;

@Entity
public class Client {

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

    private String firstName;
    private String lastName;
    private String email;
    private int phoneNumber;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

   // getter and setters
}

package com.hamdibouallegue.datarestdemo.models;

import javax.persistence.*;

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "address_line_1")
    private String addressLine1;
    @Column(name = "address_line_2")
    private String addressLine2;
    private String city;
    private String state;
    private int zipCode;
    @OneToOne(mappedBy = "address")
    private Client client;

    // getters and setters
  
}

 

创建存储库:

现在创建一个名为的新包,repositories并在其中创建两个接口ClientRepository& AddressRepository:

package com.hamdibouallegue.datarestdemo.repositories;

import org.springframework.data.jpa.repository.JpaRepository;
import com.hamdibouallegue.datarestdemo.models.Address;

public interface AddressRepository extends JpaRepository<Address, Long> {
}
package com.hamdibouallegue.datarestdemo.repositories;

import com.hamdibouallegue.datarestdemo.models.Client;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClientRepository extends JpaRepository<Client, Long> {
}

现在打开 Postman :

获取:http://localhost:8888/api

Spring data rest 自动为每个存储库创建端点。

你有没有代码行的CRUD API,因为 spring Data REST在幕后创建控制器和服务

 

让我们测试我们的CRUD API:

  • 1. get:http://localhost:8888/api /clients

您注意到返回的对象包含三个主要部分:

_embedded: 包含所有客户端。

_links: 代表超媒体链接。

page: 表示分页信息(每页大小,页数..)

  • 2. post:http://localhost:8888/api /clients

  

自定义存储库资源:

更改资源 URI :

让我们想象一下,客户端希望您将 URI 从 更改/clients为/clients_objects怎么做

真的很简单,我们需要添加一个注释:

//We add the @RepositoryRestResource annotation and we specified the path
@RepositoryRestResource(path = "clients_objects")
public interface ClientRepository extends JpaRepository<Client, Long> {
}

排除一些资源:

如果我们不排除存储库,我们需要做的就是添加一个简单的注释:

package com.hamdibouallegue.datarestdemo.repositories;

import com.hamdibouallegue.datarestdemo.models.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RestResource;

//Using the @RepositoryRestResource(exported = false) won’t expose this repository.
@RepositoryRestResource(exported = false)
public interface ClientRepository extends JpaRepository<Client, Long> {
}

Spring data rest 很容易排除客户端存储库。

请不要忘记重新加载服务器以应用更改

 

自定义 REST 负载:

创建投影:

投影应该在模型或实体包或它们的子包内。如果您不想将它们放在那里,则需要手动注册它们。

然后让我们在包projections内创建一个包models并创建一个接口,ClientDetail并在其中选择我们想要返回的内容:

package com.hamdibouallegue.datarestdemo.models.projections;

import com.hamdibouallegue.datarestdemo.models.Address;
import com.hamdibouallegue.datarestdemo.models.Client;
import org.springframework.data.rest.core.config.Projection;

@Projection(name = "clientDetail", types = {Client.class})
public interface ClientDetail {
    public String getFirstName();
    public String getLastName();
    public String getEmail();
    public int getPhoneNumber();
    public Address getAddress();
}

获取:http://localhost:8888/api/clients ?projection=clientDetail

虚拟投影:

有时我们想在不更改源代码的情况下更改返回的 JSON 对象,假设我们需要将firstName属性更改为仅name而不更改Client实体。为此,我们需要创建一个虚拟投影。

虚拟投影也称为视图投影。

现在让我们创建一个虚拟投影或视图投影:

package com.hamdibouallegue.datarestdemo.models.projections;

import com.hamdibouallegue.datarestdemo.models.Address;
import com.hamdibouallegue.datarestdemo.models.Client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.config.Projection;
// This is a virtual projection
@Projection(name = "clientDetailView", types = {Client.class})
public interface ClientDetailView {
    // We linked the firstName with this method and now we can name the method what ever we want
    // spring expression language
    @Value("#{target.firstName}")
    public String getName();

    public String getLastName();
    public String getEmail();
    public int getPhoneNumber();
    public Address getAddress();
}

获取:http://localhost:8888/api/clients?projection=clientDetailView

您注意到我们将返回的 JSON 对象firstName更改为name.

 

投影摘录

如果您希望 spring data rest 返回的默认有效负载是投影,我们使用投影摘录。

package com.hamdibouallegue.datarestdemo.repositories;

import com.hamdibouallegue.datarestdemo.models.Client;
import com.hamdibouallegue.datarestdemo.models.projections.ClientDetailView;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

//   We add the excerptProjection = ClientDetailView.class to specifie the default payload   
@RepositoryRestResource(path = "clients_objects",excerptProjection = ClientDetailView.class)
public interface ClientRepository extends JpaRepository<Client, Long> {
}

您注意到我们添加了excerptProjection参数并指定了默认负载。

现在访问http://localhost:8888/api/clients_objects:默认的payload是ClientDetailView

 

添加验证和事件处理:

  • 验证约束:

在我们执行一些验证之前,我们需要在里面添加这个依赖pom.xml:

<!--spring 验证--> 
<dependency> 
   <groupId>org.springframework.boot</groupId> 
   <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

我们可以通过传递像@NotNull , @Max(20),@Min(10),@Size(min=2,max=10).为Client模型添加一些验证:

package com.hamdibouallegue.datarestdemo.models;

import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
public class Client {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // We add the @NotNull  constraints
    @NotNull
    private String firstName;
    @NotNull
    private String lastName;
    private String email;
  
    private int phoneNumber;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;
}

现在尝试创建一个新客户端

post:http://localhost:8888/api/clients_objects

您注意到我们有一个异常,如果我们查看控制台:有一个约束违反规定 firstName 不能为空。

当然,为了让它更好,我们需要创建控制器通知来拦截这个异常并返回 400 代码状态而不是 500。

让我们这样做,创建一个名为的包config,然后添加一个新类ControllerConfig:

package com.hamdibouallegue.datarestdemo.config;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.ConstraintViolationException;

@ControllerAdvice
public class ControllerConfiguration {
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Invalid Data")
    public void notValid() {
        // add some logic here
    }
} 

现在如果你调用Post:http://localhost:8888/api/clients_objects

您将获得 400 而不是 500 的代码状态。

请注意,我们创建了一个简单的异常处理程序,在实际项目中,您必须创建异常并在控制器通知中注册它们。

 

事件处理:

我们使用 spring data rest 事件处理程序进行验证、日志记录、审计……

spring data rest 使用了八个事件:

BeforeCreateEvent:在第一次保存实体之前发出。

AfterCreateEvent:在保存新实体后发出。

BeforeDeleteEvent:在从存储库中删除实体之前发出。

AfterDeleteEvent:从存储库中删除实体后发出。

BeforeSaveEvent:在实体保存到存储库之前发出。

AfterSaveEvent:保存到存储库后发出。

BeforeLinkSaveEvent:在链接对象保存到存储库之前发出。

AfterLinkSaveEvent:在将链接对象保存到存储库中的父对象后发出。

现在让我们看看如何在行动中创建一个事件处理程序

我们将创建一个事件处理程序,在创建客户端之前检查电子邮件是否唯一。

在我们创建事件处理程序之前,让我们在内部添加一个新方法 ClientRepository:

Client findByEmail(String email);

然后在包handlers内创建一个名为的新repositories包

并创建一个新类ClientEventHandler:

package com.hamdibouallegue.datarestdemo.repositories.handlers;

import com.hamdibouallegue.datarestdemo.models.Client;

import com.hamdibouallegue.datarestdemo.repositories.ClientRepository;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.rest.core.annotation.HandleBeforeCreate;

import org.springframework.data.rest.core.annotation.RepositoryEventHandler;

import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolationException;

import java.util.HashSet;

// We must add @component to register this class inside the Spring context (ioc container)

@Component

@RepositoryEventHandler

public class ClientEventHandler {

  // We simple create a clientRepository instance

    @Autowired

    ClientRepository clientRepository;

    @HandleBeforeCreate

    public void handleClientCreate(Client client) {

        Client returnedClient = clientRepository.findByEmail(client.getEmail());

        if(returnedClient !=null){

            throw new ConstraintViolationException("email must  be unique", new HashSet<>());

        }

    }

post: http://localhost:8888/api/clients_objects

 

结论 :

Spring Data Rest 使设置 REST API 变得非常容易,并且使用最少的代码,它是 Spring Data JPA 之上的一个很好的工具。

本文源码

点击标题查看geekculture原文

1
猜你喜欢