Spring JPA执行插入而不是更新。

huangapple go评论87阅读模式
英文:

Spring JPA instead of update performs insert

问题

我在Spring Data JPA中遇到了这个问题,我从数据库中获取了一个现有的实体,然后更改了一些值并执行了save()操作,但每次都会在数据库中创建新的条目。

我已经创建了一个用于测试这种行为的示例REST控制器,控制器如下:

@GetMapping("/user")
public void test() {
    User byEmail = userRepository.findByEmail("test@test.com").orElse(null);

    if (byEmail != null) {
        userRepository.save(byEmail);
    } else {
        User user = new User();
        user.setEmail("test@test.com");
        userRepository.save(user);
    }
}

每次触发此端点时,都会创建一个新的Users表记录,尽管findByEmail返回一个具有ID和所有其他数据的现有记录。

可能的原因是什么?

实体类如下:

@Getter
@Setter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Table(name = "forum_user")
public class User extends BaseEntityAudit {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid")
    private String id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Email
    @Column(name = "email", unique = true)
    private String email;

    @Column(name = "external_id")
    private String externalId;

    @Column(name = "picture_url")
    private String pictureUrl;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private List<Post> posts;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_group",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "group_id")
    )
    @JsonIgnore
    private List<Group> userGroups;

    @ManyToOne
    @JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false)
    private Role role;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "author_id")
    @JsonIgnore
    private List<NameTag> nameTags;
}

我使用查询方法通过电子邮件查找用户:

Optional<User> findByEmail(String email);

用户表的DDL如下:

create table C##USER2.FORUM_USER
(
    ID          VARCHAR2(32) not null
        constraint SYS_C008383
            primary key,
    FIRST_NAME  VARCHAR2(45),
    LAST_NAME   VARCHAR2(45),
    EMAIL       VARCHAR2(255),
    EXTERNAL_ID VARCHAR2(45),
    PICTURE_URL CLOB,
    ROLE_ID     VARCHAR2(32),
    CREATED_AT  TIMESTAMP(6),
    CREATED_BY  VARCHAR2(32),
    UPDATED_AT  TIMESTAMP(6),
    UPDATED_BY  VARCHAR2(32)
)
/

GROUP表的DDL如下(这里一切正常):

create table C##USER2.FORUM_GROUP
(
    ID                      VARCHAR2(32) not null
        constraint FORUM_GROUP_PK
            primary key,
    NAME                    VARCHAR2(45),
    SHORT_NAME              VARCHAR2(3),
    TITLE                   VARCHAR2(45),
    DESCRIPTION             CLOB,
    LATEST_POST             TIMESTAMP(6),
    GROUP_COLOR             VARCHAR2(10),
    LOGO_URL                CLOB,
    CREATED_AT              TIMESTAMP(6),
    NUMBER_OF_MEMBERS       LONG,
    CREATED_BY              VARCHAR2(32),
    UPDATED_AT              TIMESTAMP(6),
    UPDATED_BY              VARCHAR2(32),
    ALLOW_CREATING_MESSAGES NUMBER(1) default 1,
    ALLOW_REPLAYING_TO_POST NUMBER(1) default 1,
    ALLOW_EDIT_DELETE_POSTS NUMBER(1) default 1,
    ALLOW_URL_LINK          NUMBER(1) default 1,
    ALLOW_FLAGGING          NUMBER(1) default 1
)
/
英文:

I have this issue with Spring Data JPA, I fetch an existing entity from DB, I change some values and I perform save() on it, and instead of updating new entry gets created every time in the DB.

I have created a sample REST controller to test this behaviour, contoller looks like:

    @GetMapping(&quot;/user&quot;)
public void test() {
User byEmail = userRepository.findByEmail(&quot;test@test.com&quot;).orElse(null);
if (byEmail != null) {
userRepository.save(byEmail);
} else {
User user = new User();
user.setEmail(&quot;test@test.com&quot;);
userRepository.save(user);
}
}

Each time I trigger this endpoint, a new record in the Users table is created even though findByEmail returns an existing one with ID and all the other data.

What could be the reason for this?

Entity class is:

@Getter
@Setter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Table(name = &quot;forum_user&quot;)
public class User extends BaseEntityAudit {
@Id
@GeneratedValue(generator = &quot;uuid&quot;)
@GenericGenerator(name = &quot;uuid&quot;, strategy = &quot;uuid&quot;)
private String id;
@Column(name = &quot;first_name&quot;)
private String firstName;
@Column(name = &quot;last_name&quot;)
private String lastName;
@Email
@Column(name = &quot;email&quot;, unique = true)
private String email;
@Column(name = &quot;external_id&quot;)
private String externalId;
@Column(name = &quot;picture_url&quot;)
private String pictureUrl;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = &quot;user_id&quot;)
@JsonIgnore
private List&lt;Post&gt; posts;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = &quot;user_group&quot;,
joinColumns = @JoinColumn(name = &quot;user_id&quot;),
inverseJoinColumns = @JoinColumn(name = &quot;group_id&quot;)
)
@JsonIgnore
private List&lt;Group&gt; userGroups;
@ManyToOne
@JoinColumn(name = &quot;role_id&quot;, referencedColumnName = &quot;id&quot;, nullable = false)
private Role role;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = &quot;author_id&quot;)
@JsonIgnore
private List&lt;NameTag&gt; nameTags;
}

I use query method to fetch user by email:

Optional&lt;User&gt; findByEmail(String email);

DDL for user table is:

create table C##USER2.FORUM_USER
(
ID          VARCHAR2(32) not null
constraint SYS_C008383
primary key,
FIRST_NAME  VARCHAR2(45),
LAST_NAME   VARCHAR2(45),
EMAIL       VARCHAR2(255),
EXTERNAL_ID VARCHAR2(45),
PICTURE_URL CLOB,
ROLE_ID     VARCHAR2(32),
CREATED_AT  TIMESTAMP(6),
CREATED_BY  VARCHAR2(32),
UPDATED_AT  TIMESTAMP(6),
UPDATED_BY  VARCHAR2(32)
)
/

And DDL for GROUP table (here everything works fine) is:

create table C##USER2.FORUM_GROUP
(
ID                      VARCHAR2(32) not null
constraint FORUM_GROUP_PK
primary key,
NAME                    VARCHAR2(45),
SHORT_NAME              VARCHAR2(3),
TITLE                   VARCHAR2(45),
DESCRIPTION             CLOB,
LATEST_POST             TIMESTAMP(6),
GROUP_COLOR             VARCHAR2(10),
LOGO_URL                CLOB,
CREATED_AT              TIMESTAMP(6),
NUMBER_OF_MEMBERS       LONG,
CREATED_BY              VARCHAR2(32),
UPDATED_AT              TIMESTAMP(6),
UPDATED_BY              VARCHAR2(32),
ALLOW_CREATING_MESSAGES NUMBER(1) default 1,
ALLOW_REPLAYING_TO_POST NUMBER(1) default 1,
ALLOW_EDIT_DELETE_POSTS NUMBER(1) default 1,
ALLOW_URL_LINK          NUMBER(1) default 1,
ALLOW_FLAGGING          NUMBER(1) default 1
)
/

答案1

得分: 1

我怀疑这是因为以下注释之一引起的:

@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid")

你能否移除它们,然后通过默认值添加ID?

private String id = UUID.random().toString();

另外,你可以检查一下是否用以下方式替代保存操作:

log.info("Before: " + byEmail.getId());
var result = userRepository.save(byEmail);
log.info("After result: " + result.getId());
log.info("After byEmail: " + byEmail.getId());

并且在这里检查ID。如果 "result" 有不同的ID,那么就是一个ID的问题。还要检查在执行保存方法之后,"byEmail" 是否有不同的ID。

另一种解决方法是使用 @PrePersist 来在缺少ID时动态添加ID。基本上,你正在自己编写ID生成器:

@PrePersist
void idGenerator(){
    if(this.id == null){
        this.id = UUID.random().toString();
   }
}
英文:

I suspect this happens because of (one of) these annotations:

@GeneratedValue(generator = &quot;uuid&quot;)
@GenericGenerator(name = &quot;uuid&quot;, strategy = &quot;uuid&quot;)

Can you remove them and just add the ID via default value?

private String id = UUID.random().toString();

Also what you can check is if you replace the saving with this:

log.info(&quot;Before: &quot; + byEmail.getId());
var result = userRepository.save(byEmail);
log.info(&quot;After result: &quot; + result.getId());
log.info(&quot;After byEmail: &quot; + byEmail.getId());

and introspect the IDs here. If "result" has a different ID, then its an ID issue. Also check if "byEmail" AFTER executing the save method has a different ID then before.

A solution which i also use is to use @PrePersist to add the ID "dynamically" incase its missing. You are basically writing the id generator yourself:

@PrePersist
void idGenerator(){
if(this.id == null){
this.id = UUID.random().toString();
}
} 

huangapple
  • 本文由 发表于 2023年7月6日 19:48:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76628519.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定