0

I'm having an odd issue during record saving to database.

It sometimes create exactly duplicate record except the UUID part even though the function is only run once.

For the last 6 months I only see this happen twice and I got no clue what is happening.

User Entity:

@Entity
@Table(name = "table_user")
public class User extends Deletable {

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

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

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(name = "job_title")
    private String jobTitle;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    @Column(name = "auth_key")
    private String authKey;

}

Role Entity:

@Data
@Entity
@Table(name = "table_role")
public class Role extends Deletable {

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

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

    @Column(name = "type")
    private String type;

}

User Role Entity

@Data
@Entity
@Table(name = "table_user_role")
public class UserRole {

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "role_id")
    private Role role;
}

Creating User Function on Service:

    @Override
    public User save(UserDto userDto) {

        String emailRegex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9]+";
        if (!StringUtils.hasValue(userDto.getEmail())) {
            throw new RuntimeException("Please enter Email");
        }

        if (userRepository.findByEmail(userDto.getEmail()).isPresent()) {
            throw new RuntimeException("Email already registered");
        }

        User user = new User();
        user.setProfilePictureUrl(user.getProfilePictureUrl());
        user.setName(userDto.getName());
        user.setAutoRefresh(userDto.getAutoRefresh());
        user.setJobTitle(userDto.getJobTitle());
        user.setPhoneNumber(userDto.getPhoneNumber());

        if (!Pattern.matches(emailRegex, userDto.getEmail())) {
            throw new RuntimeException("Invalid Email Format");
        }

        user.setEmail(userDto.getEmail());

        if (StringUtils.hasValue(userDto.getPassword())) {
            user.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword()));
        } else {
            user.setPassword(bCryptPasswordEncoder.encode(RandomStringUtils.randomAlphanumeric(12)));
        }
        String authKey = StringUtils.randomString();
        while (userRepository.findByAuthKeyEquals(authKey).isPresent()) {
            authKey = StringUtils.randomString();
        }
        user.setAuthKey(authKey);
        user.setCreatedAt(OffsetDateTime.now());
        if (null != UserContext.getUserId()) {
            user.setCreatedBy(UserContext.getUserId());
            user.setCreatorUsername(UserContext.getUserName());
        }
        userRepository.save(user);

        if (StringUtils.hasValueListOfString(userDto.getRoleList())) {
            for (String role : userDto.getRoleList()) {
                UserRole userRole = new UserRole();
                Role roleCd = roleService.findById(role).orElseThrow(() -> new RuntimeException("No Role found with Id : " + role));
                userRole.setUser(user);
                userRole.setRole(roleCd);
                userRoleService.save(userRole);
            }
        } else {
            throw new RuntimeException("Please assign proper Role for new User");
        }

        return user;
    }
3
  • how do you know this runs only once? Commented May 12, 2021 at 5:17
  • I was checking it on the log, and if the function keep getting called twice any data should be duplicated. The worse part I forgot to pull out the log to my PC before the log retention triggered. Also it really rare to happen since it only used as a service for User Creation request via API, and it barely impossible to request twice at the same exact miliseconds Commented May 12, 2021 at 7:06
  • 1
    That method certainly doesn't run in a millisecond, at it is sufficient that two calls happen before the transaction of the first gets committed. This sound like exactly the kind of thing that happens once every few months. Commented May 12, 2021 at 7:34

1 Answer 1

1

To be honest I doubt the statement that the method wasn't invoked twice. If it runs with the same arguments twice while the first transaction isn't committed you get the duplicate rows you are seeing.

Either way: Put a unique constraint on the email since it seems to be supposed to be unique anyway.

Sign up to request clarification or add additional context in comments.

2 Comments

As for now this is really rare case it also happening on my PHP side of code. And database is either MySQL or MariaDB. Tried to recreate both issue and it doesn't happen. I guess i just unique constraint email as for now since the delete User rarely used and was planning to make it as Soft Delete later on
@HerindraSetiawan You can make your soft delete a nullable "delete date" column and then put the constraint on, e.g., email+delete date. Then you can prevent duplicate active rows but still allow multiple deleted rows for the same person. (Since there can only be one active row per e-mail, there shouldn't ever be more than one deleted at the same time so delete date should always be unique per email.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.