多次更新父级时,JPA 在子级上重复条目

时间:2021-03-26 07:53:00

标签: java spring-boot hibernate spring-data-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;
    }

请问最好的方法是什么?

2 个答案:

答案 0 :(得分:3)

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

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

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

第一个解决方案

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

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

userRepository.findById(1L);

客户端对用户进行更改

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

userRepository.save(user)

但现在用户将拥有孩子

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

第二种解决方案

更改密钥,以便您的孩子身份不是基于某个数据库长值,而是基于某个业务密钥。 例如,您可以将 userId with username 标记为复合键

这样,即使在将某些内容持久化到数据库中之前,密钥也将始终存在。因此,hibernate 甚至在持久化之前就会知道何时重复或不重复。

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

Don't let hibernate steal your identity

第三种解决方案

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

从用户中删除 cascade = CascadeType.ALL

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

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

答案 1 :(得分:1)

因为您的客户端没有发送 friendId 和类 Friend

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

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

所以你不知道什么朋友需要更新,friendId 是自动递增的所以你得到了重复,让我们像这样更新请求:

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