Spring boot 在Spring数据REST中发布重复条目不会导致PK冲突
根据,POST方法从给定的请求主体创建一个新实体。但是,我发现它还可以更新现有的实体。在某些情况下,这可能会有问题。以下是一个例子: DemoApplication.javaSpring boot 在Spring数据REST中发布重复条目不会导致PK冲突,spring-boot,spring-data-rest,Spring Boot,Spring Data Rest,根据,POST方法从给定的请求主体创建一个新实体。但是,我发现它还可以更新现有的实体。在某些情况下,这可能会有问题。以下是一个例子: DemoApplication.java package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplicat
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> {}
pom.xml(在项目标记内)
我假设上面的POST请求在第一次得到HTTP 201,并且只有一次。然而,我能够多次发送上面的POST请求,并且一直得到HTTP201。此外,我还可以使用POST请求更改数据库中的密码
我认为这是一个安全问题。例如,我可能允许通过POST请求进行匿名用户注册。但是,在上述情况下,现有用户可能会被覆盖
问题:如果已存在具有相同id的旧实体,如何防止通过POST请求创建新实体?或者,我没有解释Spring数据REST文档吗
补充说明:
此问题的原因是Spring数据REST背后的设计。因为SpringDataREST是基于SpringDataJPA构建的,而JPA并没有用于直接向“外部”公开。因此,它“信任”输入的数据。中的“方法是新的”显示了如何确定数据是否为新数据
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));
}
public boolean isNew(T实体){
ID=getId(实体);
类idType=getIdType();
如果(!idType.isPrimitive()){
返回id==null;
}
if(id instanceof Number){
返回((数字)id).longValue()==0L;
}
抛出新的IllegalArgumentException(String.format(“不支持的基元id类型%s!”,idType));
}
新方法的结果将最终影响保存方法
public的保存(S实体){
if(entityInformation.isNew(实体)){
em.persist(实体);
返回实体;
}否则{
返回em.merge(实体);
}
}
在本问题中提到的情况下,用户名字段(也是用户实体的id)将始终包含创建新用户所需的数据。因此,当它转到isNew时,id==null将始终返回false。然后save方法将始终执行合并操作
以上是我所能提供的全部提示。尽管如此,我不知道是否有解决这个问题的办法
URL链接只是引用。它们可能与我使用的版本不完全相同。要使实体与Spring数据REST(以及Spring数据JPA)正常工作,实体类需要实现持久化。要重写的更值得注意的方法是isNew()。将调用此方法,而不是问题中提到的AbstractEntityInformation中的方法。为了使实体了解自己的状态(新的或旧的),还需要一个版本变量。通过在显式字段上注释@Version,Spring数据JPA将更新此字段。因此,一旦第一次构造实体,字段就是它的默认值(null或0,取决于它使用的数据类型)。此外,由于SpringDataREST旨在向外界公开,为了防止版本被误用,在版本字段中使用@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;
}
}
package.com.example;
导入javax.persistence.*;
导入org.springframework.data.domain.Persistable;
导入com.fasterxml.jackson.annotation.JsonIgnore;
@实体
公共类用户实现持久化{
/**
*
*/
私有静态最终长serialVersionUID=7509971300023426574L;
@身份证
私有字符串用户名;
私有字符串密码;
@版本
@杰索尼奥雷
私人长版;
公共字符串getUsername(){
返回用户名;
}
public void setUsername(字符串用户名){
this.username=用户名;
}
公共字符串getPassword(){
返回密码;
}
public void setPassword(字符串密码){
this.password=密码;
}
公共长getVersion(){
返回版本;
}
公共版本(长版本){
this.version=版本;
}
@凌驾
公共字符串getId(){
返回用户名;
}
@凌驾
public boolean isNew(){
返回版本==null;
}
}
正如@Alan Hay所提到的,还应该对传入数据进行验证。拥有它肯定很好。即使它不是PK/ID,您仍然需要在添加/编辑之前进行验证以捕获重复项。对吗?所以写一个验证器。
{"username":"user","password":"password"}
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));
}
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
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;
}
}