【发布时间】:2017-04-05 11:18:57
【问题描述】:
我认为我正在推动使用 Spring Boot 和 Hibernate 的极其简单的案例。我的意图是将有关Cyclist 类的信息存储在User 类中的OneToOne 关系中。
在注册过程中,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";
}
}
当用户填写/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";
}
上面的执行无异常,但强制用户浏览器加载 /home 页面。从此刻开始,在/home 中调用userDAO.save(user) 会产生异常:
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session [<package>.Cyclist#1]
异常的其余部分是无用的堆栈跟踪。
这就是我设计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;
}
}
还有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;
}
}
我阅读了许多发布在 SO/google 上的问题,但无法解决我的问题。
User 和 Cyclist DAO 是 CrudRepository 实现。我正在使用 lombok 生成样板代码。在hashCode 和toString 生成的情况下,我有点了解lombok 的@Data,所以我尝试只使用@Getter 和@Setter。并没有解决问题。
【问题讨论】:
-
你应该考虑使用
userDao.merge(user),或者写一个upsert(...)方法。顺便说一下,不要在每次刷新时插入,你的服务器会超载。 -
/home页面的每次刷新现在都会产生一个选择和一个更新查询。而且Spring的CrudRepository似乎没有提供除save()以外的其他方法 -
你的 DAO 必须提供
merge甚至update方法,按照约定,save表示“保存新实体”,merge表示“更新现有实体”。 -
我调查了 Spring 的 DAO
save()并检查信息是否存在,如果存在,则使用合并,否则持续 -
在我的例子中,使用了来自
javax.persistence.EntityManager接口的merge。
标签: java spring hibernate spring-boot