javaspringhibernatecascadepersistent

Detached entity passed to persist when save the child data


I'm getting this error when submitting the form:

org.hibernate.PersistentObjectException: detached entity passed to persist: com.project.pmet.model.Account; nested exception is javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.project.pmet.model.Account

Here are my entities:

Account:

@Entity
@DynamicInsert
@DynamicUpdate
public class Account {

    @Id
    @GeneratedValue
    private Integer id;

    @Column(nullable = false)
    private String login;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String email;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
    private List<Team> ownedTeams;

    ...

Team:

@Entity
@DynamicInsert
@DynamicUpdate
public class Team {

    @Id
    @GeneratedValue
    private Integer id;

    @Column(nullable = false)
    private String name;

    @ManyToOne
    @JoinColumn(name = "owner_id", nullable = false)
    private Account owner;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "team")
    private List<Account> members;

    ...

Here's a part of the Controller:

    @ModelAttribute("team")
    public Team createTeamObject() {
        return new Team();
    }

    @RequestMapping(value = "/teams/create-team", method = RequestMethod.GET)
    public String getCreateTeam(@ModelAttribute("team") Team team, Principal principal) {
        logger.info("Welcome to the create team page!");

        Account owner = accountService.findOneByLogin(principal.getName());
        team.setOwner(owner);
        team.setMembers(new AutoPopulatingList<Account>(Account.class));

        return "teams";
    }

    @RequestMapping(value = "/teams/create-team", method = RequestMethod.POST)
    public String postCreateTeam(@ModelAttribute("team") Team team) {
        logger.info("Team created!");

        teamService.save(team);

        return "redirect:/teams.html";
    }

And the form:

<form:form commandName="team" id="teamForm">
      <div class="form-group">
          <label>Name</label>
          <form:input path="name" cssClass="form-control" />
      </div>
      <div class="form-group" id="row-template">
          <label>Members</label>
          <form:select path="members[0].id" cssClass="form-control" data-live-search="true" >
             <form:options items="${accounts}" itemValue="id" />
          </form:select>
          ...
      </div>
   <form:hidden path="owner.id" />
</form:form>

What am I doing wrong?


Solution

  • teamService.save(team);
    

    Save method accepts only transient objects. What is the transient object you can find here

    Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. Transient instances will be destroyed by the garbage collector if the application does not hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition).

    You are getting the Team object and you are trying to persist it to the DB but that object has Account object in it and that Account object is detached (means that instance of that object has saved into the DB but that object is not in the session). Hibernate is trying to save it because of you have specified:

    @OneToMany(cascade = CascadeType.ALL, ....
    

    So, there are few ways how you can fix it:

    1) do not use CascadeType.ALL configuration. Account object can be used for number of Teams (at least domain structure allows it) and update operation might update Account for ALL Teams -- it means that this operation should not be initiated with Team update. I would remove cascade parameter from there (default value is no cascade operations), of if you really need use MERGE/DELETE configuration. But if you really need to persist it then see option #2

    2) use 'saveOrUpdate()' method instead of 'save()'. 'saveOrUpdate()' method accepts transient and detached objects. But the problem with this approach is in design: do you really need to insert/update account when you are saving Team object? I would split it in two operations and prevent updating Account from the Team.

    Hope this helps.