JPA实体用String作主键,重复键保存数据被覆盖问题解决
Spring Data JPA 的 save() 方法逻辑很明确:判断实体是否处于持久化状态——如果传入的实体主键(这里是nickname)在数据库中已存在,就执行merge操作(更新现有数据);如果主键不存在,才执行persist操作(插入新数据)。
你加的@Column(unique = true)和数据库唯一约束,只负责拦截重复插入的场景,但当前JPA认为你是要更新已存在的实体,所以这些约束不会触发,自然拦不住覆盖操作。
不用手动检查数据是否存在,直接用JPA原生的persist方法替代save()即可:
persist()方法只负责插入新实体,如果传入的主键已存在,会直接抛出EntityExistsException,从框架层面阻止覆盖。具体改造步骤:
- 在Service类中注入
EntityManager:
@Autowired private EntityManager entityManager;
- 修改Service的save方法:
@Override public Player save(Player player) throws Exception { entityManager.persist(player); return player; }
后续可以全局捕获EntityExistsException,给前端返回409 Conflict的响应,明确告知昵称已存在。
用JSR-380校验注解替代手动判断
给实体类字段加校验注解,替换冗长的空值判断逻辑:@Data @Entity @Table(name="players") public class Player { @Id @NotBlank(message = "昵称不能为空") private String nickname; @NotBlank(message = "名字不能为空") @Column(name="name") private String name; @NotBlank(message = "姓氏不能为空") @Column(name="surname") private String surname; // ...其他字段 }Controller参数添加
@Valid注解,让框架自动触发校验:@PostMapping(value="/addPlayer", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces ={MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<Player> save(@Valid @RequestBody Player player) throws Exception { return new ResponseEntity<>(playerService.save(player), HttpStatus.CREATED); }这样就能删掉Service里那一大段空值判断代码,把校验逻辑交给框架处理。
规范HTTP响应状态码
添加新资源时,返回HttpStatus.CREATED(201)比OK(200)更符合RESTful规范,明确告知前端是新增操作成功。统一异常处理
新增全局异常处理器,捕获校验异常和EntityExistsException,返回友好的结构化响应:@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(EntityExistsException.class) public ResponseEntity<String> handleDuplicateNickname(EntityExistsException e) { return new ResponseEntity<>("该昵称已存在", HttpStatus.CONFLICT); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach(error -> { String fieldName = ((FieldError) error).getField(); String errorMsg = error.getDefaultMessage(); errors.put(fieldName, errorMsg); }); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } }Repository接口精简
如果不需要额外查询方法,保持JpaRepository<Player, String>即可,无需冗余代码;后续若要根据昵称查询,直接用findById()即可,因为主键就是nickname。
内容的提问来源于stack exchange,提问作者Ovidio Rossi

