AlexSC
AlexSC

Reputation: 1933

How to move items between collections in Hibernate

I have two entities mapped in my application in order to model a Team and its Members. A Team can have many Members and a Member can belong to no more than one Team. Everything is fine about handling this concepts. The problem comes when I try to move a Member from one existing Team to another.

The entities are presented below, in simplified form. The very last method, transfer(), is the one that should perform the removal of a certain Member from its Team and send it to another one.

@Entity
public class Member extends Person {
    @ManyToOne
    private Team team;

    protected Member() {
        super();
    }

    public Member(Team team, String name) {
        super(name);
        this.team = team;
    }

    // Trivial getters and setters...

    public Team getTeam() {
        return team;
    }

    protected void setTeam(Team team) {
        this.team = team;
    }
}

@Entity
public class Team {
    @Id
    private long id;
    private String name;
    @OneToMany(mappedBy="team", cascade=CascadeType.ALL)
    private List<Member> members = new ArrayList<Member>();

    protected Team() {
    }

    public Team(String name) {
        this.name = name;
    }

    // trivial getters and setters...

    public Member addMember(String name) {
        Member member = new Member(this, name); 
        members.add(member);
        return member;
    }

    protected void addMember(Member member) {
        members.add(member);
        member.setTeam(this);
    }

    public void removeMember(Member member) {
        members.remove(member);
    }

    public Member memberByName(String memberName) {
        for(Member member : members)
          if(member.getName().equals(memberName))
              return member;
        return null;
    }

    public Collection<Members> getMembers() {
        return Collections.unmodifiableCollection(members);
    }

    public void transfer(Member member, Team destination) {
        members.remove(member);
        destination.addMember(member);
    }
}

I have this unit test code that is intended to validate the transfer service

Team teamA = teamRepository.teamById(idTeamA);
Team teamB = teamRepository.teamById(idTeamB);
Team teamC = teamRepository.teamById(idTeamC);

Member zaki = teamA.memberByName("Zaki");
Member denise = teamA.memberByName("Denise");

EntityTransaction t = teamRepository.transactionBegin();
teamA.transfer(zaki, teamB);
teamA.transferir(denise, teamC);
t.commit();

I have the following exception in the commit() line

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: application.domain.Member

Any ideas?

UPDATE 1:

I decided to perform a little test and changed the code of the transfer() method as follows

public void transfer(Member member, Team destination) {
    member.setTeam(this);
}

The result was curious: no error, but also no update on the tables. Hibernate couldn't track the update and the transfer simply didn't happen.

UPDATE 2:

I decided to give it a try on the suggestion from Steve K and changed the transfer() method to the following:

    public void transfer(Member member, Team destination) {
        destination.addMember(member);
        members.remove(member);
    }

Looking the addMember() and removeMember() methods (below) we see that the Team is begin updated too.

    protected void addMember(Member member) {
        members.add(member);
        member.setTeam(this);
    }

    public void removeMember(Member member) {
        members.remove(member);
    }

So, the member is being added to the destination collection, its Team is being set to the destination Team and then the member is being removed from the current collection (current Team).

The test case was changed to

EntityTransaction t = teamRepository.transactionBegin();
teamA.transfer(zaki, teamB);
teamA.getEntityManager().refresh(teamA); // I get an exception here!!!
t.commit();

In the refresh() line I have the following exception:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: domain.Member
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
        // ... many calls
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: domain.Member
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:139)
        // ... many calls
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1335)
    ... 28 more

It seems, after all, that transfering instances from one collection to another (that are implementing a simple aggregation) is not supported in Hibernate!

Upvotes: 4

Views: 1803

Answers (4)

Vlad Mihalcea
Vlad Mihalcea

Reputation: 154080

Your mappings and data access logic are just fine.

You need to move the transaction boundary at the beginning of the code:

EntityTransaction t = teamRepository.transactionBegin();

Team teamA = teamRepository.teamById(idTeamA);
...
teamA.transferir(denise, teamC);
t.commit();

But the exception you got it's not from the code you've shown us:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: application.domain.Member

There might be a pending persist action being queued up and only translated during flush time (commit time in your case). So you need to activate the SQL logs being executed and try finding out the detached entity.

Upvotes: 3

Steve K
Steve K

Reputation: 4921

The transfer() method is completely redundant. All you should need to do is call member.setTeam(destination) and Hibernate will update the collections accordingly.

Your Update's code:

member.setTeam(this);

does nothing. It should be

member.setTeam(destination);

You're seeing no updates because you're not changing the value. The member's already a member of this Team.

UPDATE:

Try just changing your transfer() method to this:

public void transfer(Member member, Team destination){
    member.setTeam(destination);
}

After you've moved around all the team members you want to move, you can commit or flush the transaction before moving on to the next unit of work. Moving the items around in the local lists is not necessary unless your units of work are badly defined. Transfer your people, then commit. Then you're ready for the next bit of processing. If you need to do so in the middle of a transaction, then call entityManager.flush() which will execute the currently queued updates but still allow you to later commit or rollback. Flushing in this manner is an anti-pattern, though. If you need to know which team a member is a part of during your processing, ask the member, not the team (which is much more efficient anyway).

Upvotes: 0

Predrag Maric
Predrag Maric

Reputation: 24433

There are a couple of things that are not clear. First one is how do you handle hibernate session in this test? Is it active throughout the whole test? Second, does teamRepository.teamById(idTeamA) create also members? Because Member zaki = teamA.memberByName("Zaki") will return null if Zaki isn't created.

My setup would be something like this:

  • In @Before method, initial data is created (teamA (with Zaki and Denise), teamB and teamC)
  • In test method, begin transaction and get those entities by id

    teamRepository.transactionBegin();
    Team teamA = teamRepository.teamById(idTeamA); // does this create a team, or returns an existing one? here, it should only return existing team
    ... // get all teams
    Member zaki = teamA.memberByName("Zaki");
    ... // get all members

  • Transfer members

    teamA.transfer(zaki, teamB);
    teamA.transfer(denise, teamC);

  • Commit transaction

    t.commit();

Upvotes: 0

dpawel09
dpawel09

Reputation: 116

Moving Member from one list to other is not going to change the team value. Better idea would be to directly change team from A to B in a Member entity.

Upvotes: 1

Related Questions