提问者:小点点

Spring数据Rest-PUT或PATCH时主键违规


通过Spring Data Rest PUTing或PATCHING我的Profile实体时遇到问题。当我为“技能”或“作业”属性放置或PATCH相同或新数据时,会发生错误。

这就是curl返回的内容:

{"cause":{"cause":{"cause":null,"message":"Unique index or primary key violation: \"PRIMARY_KEY_6E ON PUBLIC.TRAINER_PROFILE_JOBS(TRAINER_PROFILE_ID, JOBS_ID) VALUES (77, 79, 2)\"; SQL statement:\ninsert into trainer_profile_jobs (trainer_profile_id, jobs_id) values (?, ?) [23505-197]"},"message":"could not execute statement"},"message":"could not execute statement; SQL [n/a]; constraint [\"PRIMARY_KEY_6E ON PUBLIC.TRAINER_PROFILE_JOBS(TRAINER_PROFILE_ID, JOBS_ID) VALUES (77, 79, 2)\"; SQL statement:\ninsert into trainer_profile_jobs (trainer_profile_id, jobs_id) values (?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"}

该应用程序产生以下输出:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY_KEY_6E ON PUBLIC.TRAINER_PROFILE_JOBS(TRAINER_PROFILE_ID, JOBS_ID) VALUES (77, 79, 2)"; SQL statement:
insert into trainer_profile_jobs (trainer_profile_id, jobs_id) values (?, ?) ...
Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY_KEY_6E ON PUBLIC.TRAINER_PROFILE_JOBS(TRAINER_PROFILE_ID, JOBS_ID) VALUES (77, 79, 2)"; SQL statement:
insert into trainer_profile_jobs (trainer_profile_id, jobs_id) values (?, ?) [23505-197]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:357) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.message.DbException.get(DbException.java:179) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.message.DbException.get(DbException.java:155) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:101) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.mvstore.db.MVSecondaryIndex.requireUnique(MVSecondaryIndex.java:236) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:202) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.mvstore.db.MVTable.addRow(MVTable.java:732) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.command.dml.Insert.insertRows(Insert.java:182) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.command.dml.Insert.update(Insert.java:134) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.command.CommandContainer.update(CommandContainer.java:102) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.command.Command.executeUpdate(Command.java:261) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:199) ~[h2-1.4.197.jar:1.4.197]
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:153) ~[h2-1.4.197.jar:1.4.197]
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-2.7.9.jar:na]
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-2.7.9.jar:na]
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
    ... 149 common frames omitted

目前只有一种方法可以使其工作:1.使用“作业”和“技能”的空数组进行PUT或PATCH 2。然后再次使用新数据进行PUT或PATCH

这是在我尝试PUT或PATCH数据之前实体的样子:

curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainerProfiles/66
{
  ...
  "jobs" : [ {
    "period" : {
      "start" : "2015-01-02",
      "end" : "2017-01-25"
    },
    "company" : "Lorem ipsum dolor sit amet",
    "sector" : "Lorem ipsum dolor sit amet",
    "position" : "Lorem ipsum dolor sit amet",
    "description" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."
  } ],
  "skills" : [ {
    "topic" : "java",
    "level" : "EXPERT"
  }, {
    "topic" : "spring",
    "level" : "EXPERT"
  } ],
  "_links" : {
    ...
  }
}%

如您所见,技能和工作属性嵌入到配置文件实体中,因为我没有为它们创建导出的JPA存储库。这是有意的。

为了让您全面了解我的实体,我需要介绍一些继承自以下内容的基类:

public interface BaseEntity {
    public Long getId();    
    public void setId(Long id); 
}

@Data
@MappedSuperclass
public class AbstractBaseEntity implements BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

@Data
@EqualsAndHashCode(callSuper=true)
@MappedSuperclass
public abstract class AbstractOwnedEntity extends AbstractBaseEntity implements OwnedEntity {
    @NotNull
    @ManyToOne
    private Company owner;  
}

这是通过Spring Data Rest暴露的实体,这让我很头疼:

@NoArgsConstructor
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
public class Profile extends AbstractOwnedEntity {
    ...

    @NotNull
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<JobEntry> jobs = new HashSet<JobEntry>();

    @NotNull
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<SkillEntry> skills = new HashSet<SkillEntry>();

    public Profile(...) {
        ...
    }

    public void addJob(JobEntry job) {
        this.jobs.add(job);
    }

    public void addSkill(SkillEntry skill) {
        this.skills.add(skill);
    }
    ...
}

JPE Repository是一个非常基本的CrudRepository,有一些SPEL形式的@PreAuthorize,@PostFilter等来实现权限改造(此处省略)

@RepositoryRestResource
public interface TrainerProfileRepository extends CrudRepository<TrainerProfile, Long> {
    ...
}

共1个答案

匿名用户

如果我没记错的话,我认为你需要公开那些没有通过Rest存储库公开的实体的ID——在你的例子中,是技能和工作:否则,框架怎么知道你的PUT/PATCH请求是否引用了一个现有的实体。

您可以在配置中公开这2个类的ID,如下所述:

如何使用Spring Data Rest公开resourceId