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