Spring Boot OneToOne save():具有相同标识符值的不同对象已与会话关联

Adr*_*zyk 3 java spring hibernate spring-boot

我认为我正在推动 Spring Boot 和 Hibernate 使用的极其简单的情况。我的目的是将有关班级的信息存储在班级Cyclist内部的关系UserOneToOne

在注册期间,User创建类的实例并将其存储到数据库中。当用户刷新/home页面时,点击计数器会增加并保存(更新)实体。这是一个简单的会话测试和更新数据库,其工作原理如下:

@Controller
@SessionAttributes("user")
@RequestMapping("/home")
public class HomeController {
    @Autowired private UserSession userSession;
    @Autowired private UserDAO userDAO;

    @GetMapping
    public String home(Model model) {
        User user = userSession.getUser();
        user.setClicks(user.getClicks() + 1);

        userDAO.save(user); 

        return "home";
    }
}
Run Code Online (Sandbox Code Playgroud)

当用户填写表单时就会出现问题/createCyclist

@Autowired private UserDAO userDAO;

@PostMapping
public String createCyclist(@Valid CyclistCreationForm cyclistCreationForm, BindingResult bindingResult, Model model) {
    /* Form handling */
    User user = userSession.getUser();

    Cyclist cyclist = new Cyclist(user, cyclistCreationForm.getHeight(), cyclistCreationForm.getGender());
    user.setCyclist(cyclist);

    userDAO.save(user);
    return "redirect:/home";
}
Run Code Online (Sandbox Code Playgroud)

上面的执行没有异常,但强制用户浏览器加载 /home 页面。从这一刻起,调用userDAO.save(user)/home产生异常:

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session [<package>.Cyclist#1]
Run Code Online (Sandbox Code Playgroud)

其余的异常是无用的堆栈跟踪。

这就是我设计Cyclist课程的方式:

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name="cyclists")
public class Cyclist {
    private int id;
    private User user;

    /* Some float fields */

    @Id
    public int getId() {
        return id;
    }

    @MapsId
    @OneToOne
    @PrimaryKeyJoinColumn
    public User getUser() {
        return user;
    }
}
Run Code Online (Sandbox Code Playgroud)

User班级:

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name="users")
public class User implements Serializable {
    private Integer uid;
    private String name;
    private String password;

    private Cyclist cyclist;

    private int clicks;

    @Id
    @GeneratedValue
    @NotNull
    @Column(unique = true)
    public Integer getUid() {
        return uid;
    }

    @NotNull
    public String getName() {
        return name;
    }

    @NotNull
    public String getPassword() {
        return password;
    }

    @OneToOne(cascade = CascadeType.ALL)
    public Cyclist getCyclist() {
        return cyclist;
    }

    public int getClicks() {
        return clicks;
    }
}
Run Code Online (Sandbox Code Playgroud)

我读了 SO/google 上发布的许多问题,但不适合我的问题。

User 和 Cyclist DAO 是CrudRepository实现。我正在使用 lombok 生成样板代码。@Data我对lombok 的hashCode和生成有点了解,toString所以我尝试仅使用@Getter@Setter。它没有解决问题。

Adr*_*zyk 6

该问题是由 引起的@MapsId。简单的@OneToOne映射只需要:

用户.java:

@OneToOne(cascade = CascadeType.ALL)
public Cyclist getCyclist() {
    return cyclist;
}
Run Code Online (Sandbox Code Playgroud)

骑自行车者.java:

@OneToOne
@PrimaryKeyJoinColumn
public User getUser() {
    return user;
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过调用setCyclistuser 和来保留所有内容userDao.save(user)

过去我想让 Cyclist 的主键也成为等于用户 uid 的外键。这不需要users表中的附加字段。所以我尝试使用 @MapsId 并忘记它。我将尝试以本教程描述的方式实现双向共享主键:

http://websystique.com/hibernate/hibernate-one-to-one-bidirection-with-shared-primary-key-annotation-example/

编辑:我按照我说的做了。与骑行者的主键与用户的 id 相同的一对一映射:

用户.java:

@OneToOne(mappedBy="user", cascade = CascadeType.ALL)
public Cyclist getCyclist() {
    return cyclist;
}
Run Code Online (Sandbox Code Playgroud)

骑自行车者.java:

@Id
@GeneratedValue(generator="gen")
@GenericGenerator(name="gen", strategy="foreign",parameters=@Parameter(name="property", value="user"))
public int getId() {
    return id;
}

@OneToOne
@PrimaryKeyJoinColumn
public User getUser() {
    return user;
}
Run Code Online (Sandbox Code Playgroud)