【问题标题】:Spring Boot OneToOne save(): A different object with the same identifier value was already associated with the sessionSpring Boot OneToOne save():具有相同标识符值的不同对象已与会话关联
【发布时间】: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 生成样板代码。在hashCodetoString 生成的情况下,我有点了解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


【解决方案1】:

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

用户.java:

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

Cyclist.java:

@OneToOne
@PrimaryKeyJoinColumn
public User getUser() {
    return user;
}

然后,您通过在用户上调用 setCyclistuserDao.save(user) 来持久化所有内容。

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

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

编辑:我照我说的做了。 Cyclist主键与用户ID一一对应:

用户.java:

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

Cyclist.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;
}

【讨论】:

    【解决方案2】:

    这对你有帮助!我希望:)

    当它是双向时。

    如果子类共享父类主键,则保存或更新时父类主键和子类主键必须相同。 否则你会得到以下异常!

    different object with the same identifier value was already associated with the session

    解决方案

    如果要更新,则必须将 相同的主键 设置为 parentchild 类! 在点击 save() 之前,不要忘记检查映射实体是否具有相同的主键。 如果没有,您可能需要执行以下操作。从父实体获取key/id并将其设置为父子实体

    @Autowired
    ParentRepo parentRepo;
     
    @Autowired
    ChildRepo childRepo;
     
     
    public Parent saveorupdateJPAStyle(Parent parent) {
    
            parent.getChild().setId(parent.getId()); // on the this ID update should happen to the child when I save the Parent**(important)** 
            parent.getChild().setName("Prince007"); //did some update on child
            parent.getChild().setParent(parent); // setting up the parent to child because it's uni directional
     
            parent.setStatus("saved"); // did some update on parent 
         
          return parentRepo.save(parent);
        }
    

    谢谢!!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-09
      • 1970-01-01
      • 2020-10-12
      • 1970-01-01
      • 2014-04-16
      • 2013-08-30
      • 2021-09-24
      相关资源
      最近更新 更多