25

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?

Sana
  • 360
  • 3
  • 13
keysersoze
  • 2,562
  • 4
  • 21
  • 22

6 Answers6

61
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.

Ilya Ovesnov
  • 4,079
  • 1
  • 27
  • 31
  • @OneToMany(cascade = CascadeType.ALL, .... works for me. Also i understood the reason for that from your explanation; thanks! – maris Aug 03 '21 at 08:09
25

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

borchvm
  • 3,533
  • 16
  • 44
  • 45
  • 1
    Thats the perfext answer, make sure id has no value. – Michael Hegner Sep 17 '16 at 21:03
  • 2
    Oh, so objects with an id that has a `@GeneratedValue` can't even be saved if that id is already set? I thought that annotation would just be some kind of fallback. Good to know it's not! – Froxx Sep 30 '17 at 20:17
  • 1
    Make sure your id is not set to 0 if your datatype is `Integer` and it is okay if id datatype is `int`. – Ram Oct 21 '17 at 06:32
  • 1
    This was the simplest answer as well as the key for me: the id had a generation strategy and I was setting it! – Carlos López Marí Jun 10 '21 at 09:54
6

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

VadimB
  • 5,533
  • 2
  • 34
  • 48
4

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.

Pubudu
  • 93
  • 3
1

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.

Ojonugwa Jude Ochalifu
  • 26,627
  • 26
  • 120
  • 132
1

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");
Filip Kubala
  • 81
  • 1
  • 5