You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@openjpa.apache.org by joe <fi...@yahoo.de> on 2010/10/04 16:31:16 UTC

updating a many to many relationship

Hi,

I try to update a many to many relationship between a User and Group 
entity. Lets say the user is member of an additional group.
A simple entityManager.merge(user)
does not work cause in my case the User entity is not the owner-side.
Ok, so i coded this:

EJB method:
// the user entity comes from the GUI so the user and the related
// groups are unmanaged
   public void updateUser(User editedUser)
   {
     User existingUser = entityManager.find(User.class, editedUser.getId());

     List<Group> groupsOfExistingUser = existingUser.getGroups();
     List<Group> groupsOfEditedUser = editedUser.getGroups();

     List<Group> addedGroups = determineAddedGroups(groupsOfEditedUser, 
groupsOfExistingUser);
     List<Group> removedGroups = 
determineRemovedGroups(groupsOfEditedUser, groupsOfExistingUser);
     List<Group> intersectionGroups = 
determineIntersectionGroups(groupsOfEditedUser, groupsOfExistingUser);

     for (Group group : addedGroups)
     {
       group.getUsers().add(editedUser);
       entityManager.merge(group);
     }

     for (Group group : removedGroups)
     {
       List<User> users = group.getUsers();
       removeUser(users, editedUser.getId());
       entityManager.merge(group);
     }

     for (Group group : intersectionGroups)
     {
       List<User> users = group.getUsers();
       replaceUser(users, editedUser);
       entityManager.merge(group);
     }
     try
     {
       entityManager.flush();
     }
     catch (Exception e)
     {
       e.printStackTrace();
     }
     System.out.println("done");
   }


This looks very complicated to me. Is there an easier way?
Abstract away from the complication this code works only in a junit test
using OpenEJB in local server mode, but in production it does not work, 
i got the following exception:

<openjpa-1.2.1-r752877:753278 fatal user error> 
org.apache.openjpa.persistence.InvalidStateException: The generated 
value processing detected an existing value assigned to this field: 
de.test.ejb.common.entity.Group.id.
This existing value was either provided via an initializer or by calling 
the setter method.
You either need to remove the @GeneratedValue annotation or modify the 
code to remove the initializer processing.
         at 
org.apache.openjpa.util.ApplicationIds.assign(ApplicationIds.java:483
)
         at 
org.apache.openjpa.util.ApplicationIds.assign(ApplicationIds.java:463
)
         at 
org.apache.openjpa.jdbc.kernel.JDBCStoreManager.assignObjectId(JDBCSt
oreManager.java:744)
...



@Entity
@Table(name = "USERS")
public class User implements Serializable
{
   private Long id;
   private Long version;
   private String username;
   private List<Group> groups;

   @Id
   @Column(name = "ID")
   @GeneratedValue(strategy = GenerationType.SEQUENCE)
   public Long getId()
   {
     return id;
   }

   public void setId(Long id)
   {
     this.id = id;
   }

   @Version
   @Column(name = "OPT_LOCK")
   public Long getVersion()
   {
     return version;
   }

   public void setVersion(Long version)
   {
     this.version = version;
   }

   @Column
   public String getUsername()
   {
     return username;
   }

   public void setUsername(String name)
   {
     this.username = name;
   }

   @ManyToMany(mappedBy = "users", fetch = FetchType.EAGER, cascade = { 
CascadeType.PERSIST, CascadeType.MERGE })
   public List<Group> getGroups()
   {
     return groups;
   }

   public void setGroups(List<Group> groups)
   {
     this.groups = groups;
   }
}


@Entity
@Table(name = "GROUPS")
public class Group implements Serializable
{
   private Long id;
   private Long version;
   private String name;
   private List<User> users;

   @Id
   @Column(name = "ID")
   @GeneratedValue(strategy = GenerationType.SEQUENCE)
   public Long getId()
   {
     return id;
   }

   public void setId(Long id)
   {
     this.id = id;
   }

   @Version
   @Column(name = "OPT_LOCK")
   public Long getVersion()
   {
     return version;
   }

   public void setVersion(Long version)
   {
     this.version = version;
   }

   public String getName()
   {
     return name;
   }

   public void setName(String name)
   {
     this.name = name;
   }

   @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, 
CascadeType.MERGE })
   @JoinTable(name = "GROUPS_USERS", joinColumns = @JoinColumn(name = 
"GROUP_ID", referencedColumnName = "ID"), inverseJoinColumns = 
@JoinColumn(name = "USER_ID", referencedColumnName = "ID"))
   public List<User> getUsers()
   {
     return users;
   }

   public void setUsers(List<User> users)
   {
     this.users = users;
   }
}





Re: updating a many to many relationship

Posted by joe <fi...@yahoo.de>.
Hi,

> This might be a bit late, but did you try the openjpa.InverseManagerproperty?

thanks for the hint, but it doesn't work, totally.
Assigning some groups to the user the first time works,
but then after removing some groups, all groups are removed
from the user during the update.

public void updateUser(User editedUser)
{
     em.merge(editedUser);
}


Re: updating a many to many relationship

Posted by Michael Dick <mi...@gmail.com>.
Hi Joe,

This might be a bit late, but did you try the openjpa.InverseManagerproperty?

-mike

On Sun, Oct 10, 2010 at 6:11 AM, joe <fi...@yahoo.de> wrote:

> Hi,
>
> The good news is i solved my problem, the bad one is its still much code
> needed to update a bi-directional ManyToMany relationship correctly. But
> it's in the nature of things.
>
>
> http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
>
> I misunderstand JPA in some ways, what i have learned is that i have to
> update both sides of the relationship, manually.
>
> Here the working code:
>
>
> public void updateUser(User editedUser)
> {
>  User managedUser = em.find(User.class, editedUser.getId());
>
>  List<Group> groupsOfManagedUser = managedUser.getGroups();
>
>  List<Group> groupsOfEditedUser = editedUser.getGroups();
>
>  List<Group> addedGroups = determineAddedGroups(groupsOfEditedUser,
> groupsOfManagedUser);
>  List<Group> removedGroups = determineRemovedGroups(groupsOfEditedUser,
> groupsOfManagedUser);
>
>  for (Group addedGroup : addedGroups)
>  {
>    Group managedGroup = em.find(Group.class, addedGroup.getId());
>    managedGroup.getUsers().add(managedUser);
>    managedUser.getGroups().add(managedGroup);
>  }
>
>  for (Group managedGroup : removedGroups)
>  {
>    int userIndex = findUser(managedGroup.getUsers(), editedUser.getId());
>    managedGroup.getUsers().remove(userIndex);
>
>    int groupIndex = findGroup(groupsOfManagedUser, managedGroup.getId());
>    groupsOfManagedUser.remove(groupIndex);
>  }
>
>  updateOtherUserProperties(managedUser, editedUser);
> }
>

Re: updating a many to many relationship

Posted by joe <fi...@yahoo.de>.
Hi,

The good news is i solved my problem, the bad one is its still much code 
needed to update a bi-directional ManyToMany relationship correctly. But 
it's in the nature of things.

http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side

I misunderstand JPA in some ways, what i have learned is that i have to 
update both sides of the relationship, manually.

Here the working code:

public void updateUser(User editedUser)
{
   User managedUser = em.find(User.class, editedUser.getId());

   List<Group> groupsOfManagedUser = managedUser.getGroups();
   List<Group> groupsOfEditedUser = editedUser.getGroups();

   List<Group> addedGroups = determineAddedGroups(groupsOfEditedUser, 
groupsOfManagedUser);
   List<Group> removedGroups = 
determineRemovedGroups(groupsOfEditedUser, groupsOfManagedUser);

   for (Group addedGroup : addedGroups)
   {
     Group managedGroup = em.find(Group.class, addedGroup.getId());
     managedGroup.getUsers().add(managedUser);
     managedUser.getGroups().add(managedGroup);
   }

   for (Group managedGroup : removedGroups)
   {
     int userIndex = findUser(managedGroup.getUsers(), editedUser.getId());
     managedGroup.getUsers().remove(userIndex);

     int groupIndex = findGroup(groupsOfManagedUser, managedGroup.getId());
     groupsOfManagedUser.remove(groupIndex);
   }

   updateOtherUserProperties(managedUser, editedUser);
}

Re: updating a many to many relationship

Posted by joe <fi...@yahoo.de>.
Hi,

Thanks for your reply, i am sorry about my failed discription of the 
problem.

 > Can you help clarify this usage and the expected behavior?

I do my best.

Yes its an EJB-method, but its called from a Swing GUI client 
application. In the GUI there is a dialog where an operator can edit a 
user profile. One part of that action is to assign/remove groups the 
user should/shouldn'T belong to.

So the first thing what happens in that dialog is to retrieve the user 
entity (including the groups) from an EJB. This works totally fine here.
After editing the user's profile in the gui, the changed user entity 
should be send back to the EJB to update the database.

So the EJB receives the unmanaged user entity (including the unmanaged 
group entities). The purpose of the posted EJB method is to bring the 
entities back into managed state to update the database.

This code is quite complex (and it doesn't work in production), and i 
hope there is a simpler way to implement my use case. Any idea?



Re: updating a many to many relationship

Posted by Kevin Sutter <kw...@gmail.com>.
Hi Joe,
I'm a little confused by your example.  Maybe this isn't directly related to
the problem, but why are you using the merge() method?  If you are working
with Entities are already part of the persistence context (I'm assuming all
of your find processing is happening withing a CMT), then all of the Users
and Groups are already managed.  There is no need to call merge() on these
Entities.  Any changes to these Entities should be detected by the OpenJPA
runtime and processed accordingly.

(I know that the comment below indicates that the User and Groups are
unmanaged, but based on the statements that this is an EJB method and your
EM must have been injected into this EJB, the find operation as currently
coded would make the User and Groups managed.  That's the way I am reading
the code.)

The merge() method is for merging detached Entities into the current
persistence context.  Granted, you should still be able to call merge() on a
managed entity, but it's not the proper programming model usage.

Can you help clarify this usage and the expected behavior?  Thanks.

Kevin

On Mon, Oct 4, 2010 at 9:31 AM, joe <fi...@yahoo.de> wrote:

> Hi,
>
> I try to update a many to many relationship between a User and Group
> entity. Lets say the user is member of an additional group.
> A simple entityManager.merge(user)
> does not work cause in my case the User entity is not the owner-side.
> Ok, so i coded this:
>
> EJB method:
> // the user entity comes from the GUI so the user and the related
> // groups are unmanaged
>  public void updateUser(User editedUser)
>  {
>    User existingUser = entityManager.find(User.class, editedUser.getId());
>
>    List<Group> groupsOfExistingUser = existingUser.getGroups();
>    List<Group> groupsOfEditedUser = editedUser.getGroups();
>
>    List<Group> addedGroups = determineAddedGroups(groupsOfEditedUser,
> groupsOfExistingUser);
>    List<Group> removedGroups = determineRemovedGroups(groupsOfEditedUser,
> groupsOfExistingUser);
>    List<Group> intersectionGroups =
> determineIntersectionGroups(groupsOfEditedUser, groupsOfExistingUser);
>
>    for (Group group : addedGroups)
>    {
>      group.getUsers().add(editedUser);
>      entityManager.merge(group);
>    }
>
>    for (Group group : removedGroups)
>    {
>      List<User> users = group.getUsers();
>      removeUser(users, editedUser.getId());
>      entityManager.merge(group);
>    }
>
>    for (Group group : intersectionGroups)
>    {
>      List<User> users = group.getUsers();
>      replaceUser(users, editedUser);
>      entityManager.merge(group);
>    }
>    try
>    {
>      entityManager.flush();
>    }
>    catch (Exception e)
>    {
>      e.printStackTrace();
>    }
>    System.out.println("done");
>  }
>
>
> This looks very complicated to me. Is there an easier way?
> Abstract away from the complication this code works only in a junit test
> using OpenEJB in local server mode, but in production it does not work, i
> got the following exception:
>
> <openjpa-1.2.1-r752877:753278 fatal user error>
> org.apache.openjpa.persistence.InvalidStateException: The generated value
> processing detected an existing value assigned to this field:
> de.test.ejb.common.entity.Group.id.
> This existing value was either provided via an initializer or by calling
> the setter method.
> You either need to remove the @GeneratedValue annotation or modify the code
> to remove the initializer processing.
>        at
> org.apache.openjpa.util.ApplicationIds.assign(ApplicationIds.java:483
> )
>        at
> org.apache.openjpa.util.ApplicationIds.assign(ApplicationIds.java:463
> )
>        at
> org.apache.openjpa.jdbc.kernel.JDBCStoreManager.assignObjectId(JDBCSt
> oreManager.java:744)
> ...
>
>
>
> @Entity
> @Table(name = "USERS")
> public class User implements Serializable
> {
>  private Long id;
>  private Long version;
>  private String username;
>  private List<Group> groups;
>
>  @Id
>  @Column(name = "ID")
>  @GeneratedValue(strategy = GenerationType.SEQUENCE)
>  public Long getId()
>  {
>    return id;
>  }
>
>  public void setId(Long id)
>  {
>    this.id = id;
>  }
>
>  @Version
>  @Column(name = "OPT_LOCK")
>  public Long getVersion()
>  {
>    return version;
>  }
>
>  public void setVersion(Long version)
>  {
>    this.version = version;
>  }
>
>  @Column
>  public String getUsername()
>  {
>    return username;
>  }
>
>  public void setUsername(String name)
>  {
>    this.username = name;
>  }
>
>  @ManyToMany(mappedBy = "users", fetch = FetchType.EAGER, cascade = {
> CascadeType.PERSIST, CascadeType.MERGE })
>  public List<Group> getGroups()
>  {
>    return groups;
>  }
>
>  public void setGroups(List<Group> groups)
>  {
>    this.groups = groups;
>  }
> }
>
>
> @Entity
> @Table(name = "GROUPS")
> public class Group implements Serializable
> {
>  private Long id;
>  private Long version;
>  private String name;
>  private List<User> users;
>
>  @Id
>  @Column(name = "ID")
>  @GeneratedValue(strategy = GenerationType.SEQUENCE)
>  public Long getId()
>  {
>    return id;
>  }
>
>  public void setId(Long id)
>  {
>    this.id = id;
>  }
>
>  @Version
>  @Column(name = "OPT_LOCK")
>  public Long getVersion()
>  {
>    return version;
>  }
>
>  public void setVersion(Long version)
>  {
>    this.version = version;
>  }
>
>  public String getName()
>  {
>    return name;
>  }
>
>  public void setName(String name)
>  {
>    this.name = name;
>  }
>
>  @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST,
> CascadeType.MERGE })
>  @JoinTable(name = "GROUPS_USERS", joinColumns = @JoinColumn(name =
> "GROUP_ID", referencedColumnName = "ID"), inverseJoinColumns =
> @JoinColumn(name = "USER_ID", referencedColumnName = "ID"))
>  public List<User> getUsers()
>  {
>    return users;
>  }
>
>  public void setUsers(List<User> users)
>  {
>    this.users = users;
>  }
> }
>
>
>
>
>