Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why JPA acts like my Entity is detached if I try removing it, but not if I edit it?

Tags:

jpa

jsf

So I have a basic JSF Datatable, the relavent part is:

<h:dataTable value="#{actorTableBackingBean.allActors}" var="actor">

    <h:column headerText="Actor Name" sortBy="#{actor.firstName}">
        <h:outputText value="#{actor.firstName}" />
    </h:column>

    <h:column>
        <h:form>
            <h:commandButton value="Delete Actor"
                             action="#{actorTableBackingBean.deleteActor(actor)}"/>
        </h:form>
    </h:column>

    <h:column>
        <h:form>
            <h:commandButton value="Randomize Actor Name"
                             action="#{actorTableBackingBean.editActor(actor)}"/>
        </h:form>
    </h:column>

</h:dataTable>

And this is what ActorTableBackingBean looks like:

@Named
@RequestScoped
public class ActorTableBackingBean implements Serializable {

    @Inject
    ActorDao actorDao;

    private List<Actor> allActors;

    public List<Actor> getAllActors() {
        return allActors;
    }

    @PostConstruct
    public void fillUp(){
        allActors = actorDao.getAllT();
    }

    public String deleteActor(Actor a){
        removeActor(a);
        return "/allActors.xhtml";
    }

    private String removeActor(Actor a){
        try{
            actorDao.deleteActor(a);
            return null;
        }catch (Exception e){
            return null;
        }
    }

    public String editActor(Actor actor){
        actor.setFirstName("SomeRandonName");
        actorDao.editActor(actor);
        return "/allActors.xhtml";
    }

}

And finally ActorDao:

@Stateless
public class ActorDao extends GeneralDao<Actor> implements Serializable {

    @Override
    protected Class<Actor> getClassType() {
        return Actor.class;
    }

    @Override
    public Actor getWithId(int id){
        TypedQuery<Actor> typedQuery =
                em.createQuery("Select a From Actor a WHERE a.actorId =" + id,Actor.class);
        return typedQuery.getSingleResult();
    }

    public void editActor(Actor a){
        em.merge(a);
    }

    public void deleteActor(Actor a){
        em.remove(a);
    }

}

So as you can see edit Actor calls em.merge(a) and this works just fine. However em.remove(a) will return:

Caused by: java.lang.IllegalArgumentException: Entity must be managed to call remove: com.tugay.sakkillaa.model.Actor@6ae667f, try merging the detached and try the remove again.

Even if I try:

 public void deleteActor(Actor a){
    em.merge(a); 
    em.remove(a);
 }

I am still getting the same exception.

So how it works for editing the row, but not for deleting it?

Only way I could make it work was:

public void deleteActor(Actor a){
    Actor actorToBeRemoved = getWithId(a.getActorId());
    em.remove(actorToBeRemoved);
}

What is it that I am doing wrong, or not able to understand?

like image 647
Koray Tugay Avatar asked Jul 06 '13 13:07

Koray Tugay


1 Answers

The merge() method does the following: it takes a detached entity, loads the attached entity with the same ID from the database, copies the state of the detached entity to the attached one, and returns the attached entity. As you note in this description, the detached entity is not modified at all, and doesn't become attached. This is why you get the exception.

You wouldn't get it if you did

public void deleteActor(Actor a){
    a = em.merge(a); // merge and assign a to the attached entity 
    em.remove(a); // remove the attached entity
}

That said, merging is completely unnecessary, since all you want to do is remove the entity. The last solution is fine, except it really loads the entity from the database, which is also unnecessary. You should simply do

public void deleteActor(Actor a){
    Actor actorToBeRemoved = em.getReference(Actor.class, a.getActorId());
    em.remove(actorToBeRemoved);
}

Note that your getWithId() method is not efficient, and needlessly complex. You should replace it with

public Actor getWithId(int id){
    return em.find(Actor.class, id);
}

which will use the first-level cache (and potentially the second-level one as well) to avoid unnecessary queries.

like image 84
JB Nizet Avatar answered Oct 20 '22 11:10

JB Nizet