POST创建User触发StaleObjectStateException错误排查求助
通过Postman调用getUsers接口(GET请求)一切正常,但调用创建用户接口(POST请求)时抛出异常,无法将User写入数据库。
错误信息
ERROR 7388 --- [myApiPortifolio] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.myapps.myApiPortifolio.entities.User#0eb358b8-93d9-4fe1-a457-8a186d0788aa]] with root cause org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
相关代码
User 实体类
package com.myapps.myApiPortifolio.entities; import jakarta.persistence.*; import java.util.List; import java.util.UUID; @Entity @Table(name = "tb_users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; private String username; private String email; private String password; @OneToMany(mappedBy = "user") private List<Account> accounts; public User() { } public User(UUID id, String username, String email, String password) { this.id = id; this.username = username; this.email = email; this.password = password; } public UUID getId() { return id; } public void setId(UUID 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 List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } }
Account 实体类
package com.myapps.myApiPortifolio.entities; import jakarta.persistence.*; import java.util.UUID; @Entity @Table(name = "tb_accounts") public class Account { @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; private String accountType; @ManyToOne @JoinColumn(name = "user_id") private User user; public Account() { } public Account(UUID id, String accountType) { this.id = id; this.accountType = accountType; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getAccountType() { return accountType; } public void setAccountType(String accountType) { this.accountType = accountType; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
CreateUserDto
package com.myapps.myApiPortifolio.entities.dtos; public record CreateUserDto(String username, String email, String password) { }
UserRepository
package com.myapps.myApiPortifolio.repositories; import com.myapps.myApiPortifolio.entities.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.UUID; @Repository public interface UserRepository extends JpaRepository<User, UUID> { }
UserService
package com.myapps.myApiPortifolio.services; import com.myapps.myApiPortifolio.entities.User; import com.myapps.myApiPortifolio.entities.dtos.CreateUserDto; import com.myapps.myApiPortifolio.repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; @Service public class UserService { @Autowired UserRepository userRepository; public UUID createUser(CreateUserDto createUserDto) { // DTO -> ENTITY User user = new User( UUID.randomUUID(), createUserDto.username(), createUserDto.email(), createUserDto.password() ); var createdUser = userRepository.save(user); return createdUser.getId(); } public List<User> getUsers() { return userRepository.findAll(); } }
UserController
package com.myapps.myApiPortifolio.controllers; import com.myapps.myApiPortifolio.entities.User; import com.myapps.myApiPortifolio.entities.dtos.CreateUserDto; import com.myapps.myApiPortifolio.services.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @RestController @RequestMapping("/users") public class UserController { @Autowired UserService userService; @PostMapping public ResponseEntity<User> createUser(@RequestBody CreateUserDto createUserDto) { var userId = userService.createUser(createUserDto); return ResponseEntity.created(URI.create("/users/" + userId)).build(); } @GetMapping public ResponseEntity<List<User>> getUsers() { var users = userService.getUsers(); return ResponseEntity.ok(users); } }
原因
你在UserService的createUser方法中手动生成UUID作为User的ID,但User实体类的ID配置了@GeneratedValue(strategy = GenerationType.AUTO),这会让Hibernate尝试再次生成ID,或者误以为你传入的ID对应的记录已存在(比如数据库中残留了相同UUID的旧记录),最终触发乐观锁异常。
解决方案
修改UserService的createUser方法,不要手动传入UUID,改用无参构造器创建实例后设置属性:
public UUID createUser(CreateUserDto createUserDto) { // DTO -> ENTITY User user = new User(); user.setUsername(createUserDto.username()); user.setEmail(createUserDto.email()); user.setPassword(createUserDto.password()); var createdUser = userRepository.save(user); return createdUser.getId(); }
或者在User实体类中新增不含ID的构造器,直接调用:
// 先在User类中新增构造器: // public User(String username, String email, String password) { // this.username = username; // this.email = email; // this.password = password; // } // 然后修改service中的代码: User user = new User(createUserDto.username(), createUserDto.email(), createUserDto.password());
另外,建议清空tb_users表的旧数据后再重新测试,避免残留数据干扰。
内容的提问来源于stack exchange,提问作者Ederson Dias

