首页 > 解决方案 > 多次更新父项时,JPA 在子项上重复条目

问题描述

我正在使用 Java、Spring Boot、Hibernate 和 JPA 构建 Rest API。我有 2 个表用户和朋友,其中用户将有一个朋友数组。我的客户将在我的用户表上发送几个更新请求。这是 API 将收到的请求:

{
    "username": "bertrand",
    "email": "bertrand@gmail.com",
    "birthdate": "11/05/1984",
    "lang": "fr",
    "secretKey": "qefieqjnhfoipqnhefipqnfp",
    "updatedOn": "mise a jour date",
    "createdOn": "creation date",
    "friends": [
        {
            "username": "philippe",
            "userId": 2,
            "email": "philipppe@gmail.com"
        }
    ]
}

如果我的客户发送此请求,我的 API 会返回:

 {
    "id": 1,
    "username": "bertrand",
    "password": "$2a$10$zGpK7/SOceA1xZnphrItpuYRkViBa7pNNgt9DsKDH1Q7HY50FE9hm",
    "email": "bertrandpetit10@gmail.com",
    "birthdate": "11 mai 1984",
    "lang": "fr",
    "secretKey": "8138124aff2b9226",
    "updatedOn": "mise a jour date",
    "createdOn": "creation date",
    "friends": [
        {
            "id": 1,
            "userId": 2,
            "username": "philippe",
            "email": "philipppe@gmail.com"
        }
    ]
}

这个答案很好,但是如果我的客户再次发送相同的请求,朋友数组中会填充一个重复的朋友。

{
        "id": 1,
        "username": "bertrand",
        "password": "$2a$10$zGpK7/SOceA1xZnphrItpuYRkViBa7pNNgt9DsKDH1Q7HY50FE9hm",
        "email": "bertrandpetit10@gmail.com",
        "birthdate": "11 mai 1984",
        "lang": "fr",
        "secretKey": "8138124aff2b9226",
        "updatedOn": "mise a jour date",
        "createdOn": "creation date",
        "friends": [
            {
                "id": 1,
                "userId": 2,
                "username": "philippe",
                "email": "philipppe@gmail.com"
            },
            {
                "id": 2,
                "userId": 2,
                "username": "philippe",
                "email": "philipppe@gmail.com"
            }
        ]
    }

我希望每个朋友都是独一无二的,如果已经存在,不要在 Friend 表中创建新条目。

这是我的用户实体:

Entity
@Table(name = "users")
public class User {

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

@Column(name = "username", nullable = false, length = 20, unique = true)
private String username;

@Column(name = "password", nullable = false, length = 100)
private String password;

@Column(name = "email", nullable = false, length = 50, unique = true)
private String email;

@Column(name = "birthdate", nullable = false, length = 100)
private String birthdate;

@Column(name = "language", nullable = false, length = 2)
private String lang;

@Column(name = "secret_key", nullable = false, length = 200)
private String secretKey;

@Column(name = "updated_on", nullable = false, length = 100)
private String updatedOn;

@Column(name = "created_on", nullable = false, length = 100)
private String createdOn;

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "owner_id", nullable = false)
private Set<Friend> friends = new HashSet<>();

public User() {
}

public User(String username, String email, String birthdate, String lang, String secretKey, String updatedOn,
        String createdOn, Set<Friend> friends, String password) {
    this.username = username;
    this.email = email;
    this.birthdate = birthdate;
    this.lang = lang;
    this.secretKey = secretKey;
    this.updatedOn = updatedOn;
    this.createdOn = createdOn;
    this.friends = friends;
    this.password = password;
}
+ getters ans setters...

这是我的朋友实体:

    @Entity
    @Table(name = "friends")
    public class Friend {

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

    @Column(name = "user_id", nullable = false, length = 20)
    private long userId;

    @Column(name = "username", nullable = false, length = 20)
    private String username;

    @Column(name = "email", nullable = false, length = 50)
    private String email;

    public Friend(long id, long userId, String username, String email) {
        this.id = id;
        this.userId = userId;
        this.username = username;
        this.email = email;
    }

请问最好的方法是什么?

标签: javaspring-boothibernatespring-data-jpa

解决方案


那是因为当您发送对该子信息的请求时

          { "username": "philippe",
            "userId": 2,
            "email": "philipppe@gmail.com"
           }

您的孩子被认为是休眠的新实体。您的密钥在此处为空。它无法知道它是否重复。

第一种解决方案

不要发送这样的更新信息。首先从您的 Api 获取信息,进行更改并将其发回以进行更新。这样你的孩子就会有身份证。

所以客户端点击你的api来检索用户1并收回

userRepository.findById(1L);

客户对用户进行更改

客户端调用 API 来更新调用的用户

userRepository.save(user)

但现在用户将拥有孩子

          { "id": 1         
            "username": "philippe",
            "userId": 2,
            "email": "philipppe@gmail.com"
           }

第二种解决方案

更改密钥,使您的孩子的身份不是基于某些数据库长值,而是基于某些业务密钥例如,您可以使用用户名将 userId标记为复合键

这样,即使在数据库中持久化某些内容之前,密钥也将始终存在。因此,hibernate 甚至在它被持久化之前就会知道什么时候是重复的。

这是一篇关于您面临的问题的非常受欢迎的文章

不要让hibernate窃取你的身份

第三个解决方案

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "owner_id", nullable = false)
private Set<Friend> friends = new HashSet<>();

cascade = CascadeType.ALL从用户中删除。

然后让该 API 调用的客户端仅更新用户字段,而不是与其子项有关的内容。如果客户想要更新某个朋友,他必须为该特定朋友调用相关的 API,就像现在为用户所做的那样。

用户更新调用中包含的任何好友信息都将被忽略。


推荐阅读