keysersoze
keysersoze

Reputation: 2572

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?

Upvotes: 26

Views: 97342

Answers (7)

Vivek
Vivek

Reputation: 599

The primary problem arose because I was including the ID in the URL, as demonstrated below. This caused confusion for Hibernate because, in my model, I had annotated the ID with @GeneratedValue, indicating that Hibernate should generate the ID automatically. Therefore, there was no need to explicitly pass the ID value:

@Controller
@RequestMapping("/course/{id}/section")
public class SectionController {
    //....SOME CODE
}

Replace the {id} with something specific:

@Controller
@RequestMapping("/course/{courseId}/section")
public class SectionController {
    //....SOME CODE
}

Upvotes: 0

Filip Kubala
Filip Kubala

Reputation: 91

Be aware of Lombok .toBuilder() method - it is creating a new instance of the object, which can be pretty misleading, when you are trying to update the part of a child object.

Example:

public class User {

 @OneToOne(...)
 Field x;

}
@Builder(toBuilder = true)
public class Field {
String a;
String b;
}
@Transactional
public class UserService {

public updateUserField(User user) {
...
user.setX(user.getX().toBuilder().a("New value").build());
}

}

This will throw the PersistentObjectException without explicitly calling the userRepo.save method.

You need to do:

var x = user.getX();
x.setA("New Value");

Upvotes: 2

Ojonugwa Jude Ochalifu
Ojonugwa Jude Ochalifu

Reputation: 27255

This error happened for me when I tried to save a child entity and then pass the newly saved entity as parameter to a new parent object.

For instance:

ChildA a = childAService.save(childAObject);

Parent parent = new Parent();

parent.setChildA(a) // <=== Culprit

parentService.save(parent);

Instead, do:

ChildA a = new ChildA();

parent.setChildA(a)

parentService.save(parent)

Hibernate handles the persisting of a for you, you don't have to do it yourself.

Upvotes: 2

Please, change @OneToMany(cascade = CascadeType.ALL,..) to @OneToMany(cascade = CascadeType.REMOVE,...) or another except CascadeType.PERSIST and the problem has been solved

Upvotes: 6

Pubudu
Pubudu

Reputation: 113

Since your id is auto generated value, don't send it from client side. I had a same issue. Make sure that you does't provide a value for auto generated attribute.

Upvotes: 5

Borja
Borja

Reputation: 3610

The error occurs because the id is set. Hibernate distinguishes between transient and detached objects and persist works only with transient objects.

isteamService.save(team);

in this operation can not be loaded id because is @GeneratedValue

Upvotes: 24

Ilya Ovesnov
Ilya Ovesnov

Reputation: 4257

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.

Upvotes: 63

Related Questions