You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免同一用户创建同名Wallet?校验逻辑失效求助

同一用户创建同名钱包校验失效问题

我有两张表User和Wallet,User包含字段id,Wallet包含字段userId、walletName。想要实现禁止同一用户创建同名钱包,最初的全局校验逻辑不符合需求,修改为按userId+walletName校验后仍失效,用户依然能创建同名钱包。


最初的校验代码

if (walletRepository.existsByWalletName(walletRequest.getWalletName())) {
        return ResponseEntity.badRequest().body(new MessageResponse("You already have wallet with that name, choose another!"));
    }

修改后的接口代码

@PostMapping("/user/{user_id}/wallets")
public ResponseEntity<?> createWallet(@PathVariable(value = "user_id") Long user_id,
                                      @RequestBody Wallet walletRequest, User user) {

    if (walletRepository.existsByUserIdAndWalletName(user.getId(), walletRequest.getWalletName())) {
        return ResponseEntity.badRequest()
                .body(new MessageResponse("You already have wallet with that name, choose another!"));
    }

相关实体类及仓库代码

User 实体类

@Entity
@Table(name = "users",
       uniqueConstraints = {
           @UniqueConstraint(columnNames = "username"),
           @UniqueConstraint(columnNames = "email")
       })
public class User {
  
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @NotEmpty
  @Size(min = 3, max = 20)
  private String username;

  @NotEmpty
  @Size(max = 50)
  @Email
  private String email;

  @NotEmpty
  @Size(min = 6)
  private String password;

  @NotEmpty(message = "Please, insert a first name")
  private String firstName;

  @NotEmpty(message = "Please, insert a last name")
  private String lastName;

  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(name = "user_roles", 
             joinColumns = @JoinColumn(name = "user_id"),
             inverseJoinColumns = @JoinColumn(name = "role_id"))
  private Set<Role> roles = new HashSet<>();

  public User() {
  }

  public User(String username, String email, String password, String firstName, String lastName) {
    this.username = username;
    this.email = email;
    this.password = password;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public Set<Role> getRoles() {
    return roles;
  }

  public void setRoles(Set<Role> roles) {
    this.roles = roles;
  }
}

Wallet 实体类

@Entity
@Table(name = "wallet")
public class Wallet {

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

    @NotEmpty(message = "Please, insert a wallet name")
    private String walletName;

    private double initialBalance;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "user_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonIgnore
    private User user;

    public Wallet() {
    }

    public Wallet(String walletName, double initialBalance) {
        this.walletName = walletName;
        this.initialBalance = initialBalance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getWalletName() {
        return walletName;
    }

    public void setWalletName(String walletName) {
        this.walletName = walletName;
    }

    public double getInitialBalance() {
        return initialBalance;
    }

    public void setInitialBalance(double initialBalance) {
        this.initialBalance = initialBalance;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

Wallet Repository

boolean existsByUserIdAndWalletName(Long userId, String walletName);

问题排查与修复方案

1. 修复接口参数绑定问题

当前接口直接注入User user,Spring无法自动将路径中的user_id绑定到User对象,导致user.getId()可能为空或不是当前请求的用户ID。需通过user_id从数据库查询对应用户:

@PostMapping("/user/{user_id}/wallets")
public ResponseEntity<?> createWallet(@PathVariable(value = "user_id") Long user_id,
                                      @RequestBody Wallet walletRequest,
                                      @Autowired UserRepository userRepository) {
    // 查询当前用户
    User user = userRepository.findById(user_id)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
    
    if (walletRepository.existsByUserIdAndWalletName(user.getId(), walletRequest.getWalletName())) {
        return ResponseEntity.badRequest()
                .body(new MessageResponse("该名称的钱包已存在,请更换其他名称!"));
    }
    // 后续创建钱包逻辑
    Wallet wallet = new Wallet(walletRequest.getWalletName(), walletRequest.getInitialBalance());
    wallet.setUser(user);
    walletRepository.save(wallet);
    return ResponseEntity.ok(new MessageResponse("钱包创建成功"));
}

2. 添加数据库联合唯一约束

仅靠代码校验存在并发风险,需在Wallet表的user_id和walletName字段添加联合唯一约束,从数据库层面拦截重复数据:

@Entity
@Table(name = "wallet",
       uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "walletName"}))
public class Wallet {
    // 原有代码不变
}

3. 确保Repository方法正确性

可自定义查询语句,避免Spring Data JPA自动生成SQL出错:

@Repository
public interface WalletRepository extends JpaRepository<Wallet, Long> {
    @Query("SELECT COUNT(w) > 0 FROM Wallet w WHERE w.user.id = :userId AND w.walletName = :walletName")
    boolean existsByUserIdAndWalletName(@Param("userId") Long userId, @Param("walletName") String walletName);
}

内容的提问来源于stack exchange,提问作者Stefan Jankovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 01:10:28