Search code examples
springspring-boothibernatejpa

Spring JPA instead of update performs insert


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("/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);
        }
    }

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 = "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;

}

I use query method to fetch user by email:

Optional<User> 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
)
/

Solution

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

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

    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("Before: " + byEmail.getId());
    var result = userRepository.save(byEmail);
    log.info("After result: " + result.getId());
    log.info("After byEmail: " + 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();
       }
    }