-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 posts ] 
Author Message
 Post subject: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 4:22 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Hi fellow developers,

Probably you'll be able to answer this right off the top of your head, as this error occurs in response to a very routine webapp task: set a newly created user's company to the same company as that of the logged-on user who created him/her.

The USER table has a COMPANY_ID field to reflect the many-to-one relationship, so I expect Hibernate to create a new user with the same company ID as the user I'm logged in as. Instead, Hibernate/Tapestry outputs the following error...

Quote:
org.apache.tapestry5.runtime.ComponentEventException

detached entity passed to persist: com.example.harbour.entities.Company


Code snippet from 'pages/user/CreateUser.java'...

Code:
    User user = new User(firstName, lastName, userName, email, authenticator.encryptPassword(password));

    //Set the new user's company to the same as the creator's company
    user.setCompany(authenticator.getLoggedUser().getCompany());

    crudServiceDAO.create(user);


The User>>Company relationship, as defined in the User entity class...

Code:
    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="COMPANY_ID")
    private Company company;
   
    ...
   
    @Override
    public boolean equals(Object other){
        if(this == other) return true;
        if(!(other instanceof User)) return false;

        final User user = (User)other;

        if(!user.getUserName().equals(getUserName())) return false;
        if(!user.getFirstName().equals(getFirstName())) return false;
        if(!user.getLastName().equals(getLastName())) return false;

        return true;
    }


    @Override
    public int hashCode(){
        int prime = 31;
        int result = 1;
        result = prime * result + ((userName == null) ? 0 : userName.hashCode());
        result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
        result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
        return result;
    }


The Company>>User relationship, as defined in the Company entity class...

Code:
    @OneToMany(mappedBy="company", cascade=CascadeType.ALL)
    private Collection<User> users = new ArrayList<User>();
   
    ...
   
    @Override
    public boolean equals(Object other){
        if(this == other) return true;
        if(!(other instanceof Company)) return false;

        final Company company = (Company)other;

        if(!company.getName().equals(getName())) return false;
        if(!company.getEmail().equals(getEmail())) return false;
        if(!company.getCreated().equals(getCreated())) return false;

        return true;
    }


    @Override
    public int hashCode(){
        int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result + ((created == null) ? 0 : created.hashCode());
        return result;
    }


I included the equals() and hashCode() methods in case they shine any light on the matter.

I'd really appreciate your help.

Thanks,

Chris.


Top
 Profile  
 
 Post subject: Re: Hibernate/Tapestry: "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 4:30 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
Code:
@ManyToOne(cascade=CascadeType.ALL)


This is the problem. You should never cascade from a child entity to a parent. Always cascade from parent entities to children. Otherwise, how could a child entity be created prior to its parent?

Because you have a bidirectional association between Company and User, you also need to set both sides of the association. Use add/remove child utility methods as explained in this article.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 5:30 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Thanks Vlad.

I changed "@ManyToOne(cascade=CascadeType.ALL)" to simply "@ManyToOne", but now get the error:

Quote:
[ERROR] ioc.Registry org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.optomus.harbour.entities.Company
[ERROR] ioc.Registry Operations trace:
[ERROR] ioc.Registry [ 1] Invoking startup method com.optomus.harbour.dal.DataModule.initialize().
2017-11-30 22:01:23.018::WARN: failed app
org.apache.tapestry5.ioc.internal.OperationException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.optomus.harbour.entities.Company
...


I have a DemoDataModule that on application start-up...

(1) Instantiates a couple of demo company objects
(2) Instantiates a couple of demo user objects
(3) Uses setters and getters to set the bidirectional association between them (will change to 'add' and 'remove' utility methods later, as suggested)
(4) Creates these entities via DAO as per the below code snippet.

Code:
        ...
        create(users);
        create(companies);
    }

    private void create(List<?> entities)
    {
        for (Object e : entities)
        {
            dao.create(e);
        }
    }


Just wondering why removing "(cascade=CascadeType.ALL)" would break this and, of course, how to fix it.

Thanks,

Chris.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 5:41 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
Did you add the add/remove child utility methods in the parent entity?

Code:
private List<User> users = new ArrayList<User>();

public void addUser(Comment user) {
   users.add(user);
   user.setCompany(this);
}

public void removeUser(Comment user) {
   user.setCompany(null);
   this.users.remove(user);
}


And call the addUser when creating the Company and the User?


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 5:52 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Yes, further up in the code step (3) looks like this:

Code:
        ...
        //Define the bidirectional user <-> company relationships (demo companies only)
        trueNorthUser.setCompany(trueNorthLtd);
        trueNorthLtd.getUsers().add(trueNorthUser);

        dueSouthUser.setCompany(dueSouthLtd);
        dueSouthLtd.getUsers().add(dueSouthUser);
        ...


As you can see, I've set the bidirectional associations outside of the entity classes themselves (so not yet using utility methods).


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 5:57 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
Ok, but why do you create the Users before the Companies?

Code:
        create(users);
        create(companies);


I suspect a flush being called. More, if you have a User that belongs to an existing Company, you should fetch that from the EntityManager:

Code:
User newUser = ....

Company company = entityManager.find(Company.class, companyId);
company.addUser(newUser);


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 7:11 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Aha! Some progress. You were right about the order mattering...

Changing:

create(users);
create(companies);

...to:

create(companies);
create(users);

...means now I can assign the new user with the same company as the user who created him. Interestingly, the original "authenticator.getLoggedUser().getCompany()" method of extracting the logged on user's company, and then associating that company to the new user appears to work fine. Seems to contradict the original Hibernate error: "detached entity passed to persist".

Code:
        Company company = authenticator.getLoggedUser().getCompany();
        user.setCompany(company);
        //company.getUsers().add(user);


The only remaining matter is that when I uncomment the last line above (to set the bidirectional association), I get a "lazy fetch" error. It is now late, but tomorrow I will add "fetch=fetchType.EAGER" to the Company>>User association...

Code:
    @OneToMany(mappedBy="company", fetch=fetchType.EAGER, cascade=CascadeType.ALL)
    private Collection<User> users = new ArrayList<User>();


Hopefully that does the trick. :-)

Chris.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Thu Nov 30, 2017 7:40 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
Quote:
"fetch=fetchType.EAGER" to the Company>>User association..


Please don't. EAGER fetching, especially for collections, is never the right answer.

If you get a LazyInitializationException, you should read this article first.

Then, you should make sure that:

Code:
create(companies);
create(users);


are done in a @Transactional service, not from the Web Controller.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Fri Dec 01, 2017 3:29 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Ok, so fetchType.EAGER is something to be avoided at all cost.

In my case, I don't actually need the child user collection. I 'get' the user collection from company simply to add one more user to it. As mentioned above, I am defining the bidirectional association within my controller:

Code:
        Company company = authenticator.getLoggedUser().getCompany();
        user.setCompany(company);
        company.getUsers().add(user);  <== The problem line (lazy initialisation)


So, I shall follow your earlier advice about creating adders/removers in the model (entity) class instead.

Chris.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Fri Dec 01, 2017 4:57 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
You can deal with the LazyInitializationException you got in this code:

Code:
Company company = authenticator.getLoggedUser().getCompany();
user.setCompany(company);
company.getUsers().add(user);  <== The problem line (lazy initialisation)


in two ways:

1. You could move this code to a @Transactional service so that you have the same Hibernate Session spanning over all data access operations
2. You could fetch the company.users collections with a JOIN FETCH in the JPQL query that loaded the Company entity


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Mon Dec 04, 2017 4:10 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
I was under the understanding that Apache Tapestry opens a new session on receiving an HTTP Request, and closes the session once the HTTP Response is served. In my case the request would be the logged-in user hitting the "Create User" button, and the response would be the message dialog stating that the new user had been created. Everything in between - including the above bidirectional association assignments - would occur within the one session. At least that's what I expected.

Futhermore, I expected that ORM layers wouldn't automatically fetch child entities on seeing that these entities are called: parent.getChild(). I agree that children should not be 'eagerly' fetched by default. However, forcing developers to produce JPQL code to initiate such fetches hardly seems ideal either.

All a learning process. :-)

Chris.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Mon Dec 04, 2017 4:20 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
Quote:
I was under the understanding that Apache Tapestry opens a new session on receiving an HTTP Request, and closes the session once the HTTP Response is served. In my case the request would be the logged-in user hitting the "Create User" button, and the response would be the message dialog stating that the new user had been created. Everything in between - including the above bidirectional association assignments - would occur within the one session. At least that's what I expected.


I don't know how Tapestry works, but if it did that, I would say they were doing it the wrong way since you are describing the Open Session in View Anti-Pattern.

Quote:
Futhermore, I expected that ORM layers wouldn't automatically fetch child entities on seeing that these entities are called: parent.getChild(). I agree that children should not be 'eagerly' fetched by default. However, forcing developers to produce JPQL code to initiate such fetches hardly seems ideal either.


Getting a lazy Proxy will not hit the database. Calling a method of the underlying Proxy will require a query to be executed. Using JPQL to fetch entities or Entity Graphs is not a problem either. It's all about making sure the underlying SQL statements are efficient and you get the most out of your database. Of course, you don't need to use a custom JPQL, but then, you'd require multiple SQL statements to achieve the same goal of a single SQL statement.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Mon Dec 04, 2017 6:42 am 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
My Tapestry textbook describes it this way...

Quote:
Open Session in View
The Hibernate documentation defines the term unit of work as an atomic set of
operations that are carried out against a database. A common model to define a unit
of work is the session per request pattern. In this model, a new session is opened
for every incoming request. All database interactions in the same request are done
using this Session. At the end of the request the session is closed.

A common issue in web applications using Hibernate is access to detached objects.
Typically the session is already closed when the view is rendered. This results in
LazyInitializationException being thrown. The Open Session In View
pattern is a common solution for detached objects3. Fortunately, you don't need to
know how this pattern is implemented. Tapestry does it for you. When obtaining
Session in your page or component using @Inject annotation, you can be sure
that the session has been opened already and will be closed at the end of the request,
after the markup was rendered.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Mon Dec 04, 2017 10:24 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1630
Location: Romania
I also recommend you this thread from Spring Boot. It's a very good read.

It will help you take a decision about whether IS IN is a good or a bad idea.


Top
 Profile  
 
 Post subject: Re: Hibernate throws "detached entity passed to persist"
PostPosted: Mon Dec 04, 2017 8:17 pm 
Newbie

Joined: Thu Nov 30, 2017 4:01 am
Posts: 8
Hi Vlad, interesting, quite a heated idealistic/pragmatistic debate still going on. :-)

So, in summary, in the entity class you'd prefer to see the below method for fetching child entities...

Code:
    @OneToMany(mappedBy="company", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    private Collection<User> users = new ArrayList<User>();

    ...

//Not refactored for company.users 'Collection'
@Transactional
public List<PostComment> getPostComments(String review) {
   return entityManager.createQuery(
      "select pc " +
      "from PostComment pc " +
      "join fetch pc.post " +
      "where pc.review = :review", PostComment.class)
   .setParameter("review", review)
   .getResultList();
}


Than my existing code below...

Code:
    @OneToMany(mappedBy="company", fetch=FetchType.EAGER, cascade=CascadeType.ALL)
    @Fetch(FetchMode.SELECT)  //Workaround to allow multiple eager fetches
    private Collection<User> users = new ArrayList<User>();

    ...

    public Collection<User> getUsers() {
        return users;
    }


Perhaps you'd be so kind as to check my rewritten/refactored "public Collection<User> getUsers()" method below. In my case I simply need to fetch all child entities where user.status = "active". So, the method signature probably needs to be: public Collection<User> getUsers(String status).

Is swapping out all entity class parent.getChild() getters, in the manner below, all that is required to avoid the OSIV 'anti-pattern' then?

Code:
@Transactional
public Collection<User> getUsers(String status){
   return entityManager.createQuery(
      "select user " +
      "from User user " +
      "join fetch user.company " +
      "where user.status = :status", User.class)
   .setParameter("status", status)
   .getResultList();
}


Thanks,

Chris.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.