POST重复条目不会导致Spring Data REST中的PK冲突

时间:2017-02-14 02:45:16

标签: spring-boot spring-data-rest

根据Spring Data REST Documentation,POST方法从给定的请求主体创建一个新实体。但是,我发现它还可以更新现有实体。在某些情况下,这可能会有问题。这是一个例子:

DemoApplication.java

package com.example;

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

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

UserRepository.java

package com.example;

import org.springframework.data.repository.PagingAndSortingRepository;

public interface UserRepository extends PagingAndSortingRepository<User, String> {}

User.java

package com.example;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private String username;
    private String password;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

pom.xml(在项目标签内)

<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>demo</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.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-data-rest</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</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>

application.properties:empty

网址:http://localhost:8080/users

方法:POST

JSON内容:

{"username":"user","password":"password"}

我假设上面的POST请求是第一次获得HTTP 201,而且只有一次。但是,我能够多次发送上述POST请求,并且始终获得HTTP 201。此外,我还可以使用POST请求更改数据库中的密码。

我认为这是一个安全问题。例如,我可能允许通过POST请求进行匿名用户注册。但是,在上述情况下,现有用户可能会被覆盖。

  

问题:如果旧实体已存在且具有相同的ID,如何阻止从POST请求创建新实体?或者,我是否错过了解释Spring Data REST文档?

补充说明:

此问题的原因是Spring Data REST背后的设计。因为Spring Data REST是基于Spring Data JPA构建的,它不会直接暴露给“外部”。所以它“信任”进来的数据。org.springframework.data.repository.core.support.AbstractEntityInformation中的新方法显示了如何将数据确定为新数据还是新数据。

public boolean isNew(T entity) {

    ID id = getId(entity);
    Class<ID> idType = getIdType();

    if (!idType.isPrimitive()) {
        return id == null;
    }

    if (id instanceof Number) {
        return ((Number) id).longValue() == 0L;
    }

    throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
}

isNew方法的结果最终会影响org.springframework.data.jpa.repository.support.SimpleJpaRepository中的保存方法。

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

在此问题中提到的情况中,用户名字段(也是用户实体的ID)将始终包含数据以创建新用户。因此,当它转到isNew时,id == null将始终返回false。然后save方法将始终执行合并操作。

以上提示是我能提供的。即便如此,我不知道是否有解决方案来解决这个问题。

网址链接只是引用。它们可能与我使用的版本不完全相同。

1 个答案:

答案 0 :(得分:0)

为了使实体与Spring Data REST(以及Spring Data JPA)一起正常工作,实体类需要实现Persistable。要重写的更明显的方法是isNew()。将调用此方法,而不是问题中提到的AbstractEntityInformation中的方法。要使实体知道自己的状态(新的或旧的),还需要版本变量。通过在显式字段上注释@Version,Spring Data JPA将更新此字段。因此,一旦首次构造实体,该字段就是它的默认值(null或0,具体取决于它使用的数据类型)。此外,由于Spring Data REST旨在向外界公开,为了保护版本不被滥用,@ JsonIgnore用于版本字段。

对于这个特定问题,需要将User.java类更改为:

package com.example;

import javax.persistence.*;
import org.springframework.data.domain.Persistable;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
public class User implements Persistable<String> {

    /**
     * 
     */
    private static final long serialVersionUID = 7509971300023426574L;
    @Id
    private String username;

    private String password;

    @Version
    @JsonIgnore
    private Long version;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

    @Override
    public String getId() {
        return username;
    }

    @Override
    public boolean isNew() {
        return version == null;
    }
}

正如@Alan Hay所提到的,还应对传入数据进行验证。这绝对是件好事。