Hi there, great product.
This is probably really simple but, believe me, I've searched everywhere for an answer.
I have 2 Business Objects - User and Group - each having a (Set) collection of the other (many-to-many).
Everything seemed fine until it came to edit a persisted User object. I receive a nonUniqueObjectException upon update()ing a load()ed instance, only when the User holds a Group object it contained when loaded.
In case that was a bit vague - User object to be edited is load()ed. From a form submission, User attributes are amended. The User Set 'groups' is replaced with a new Set made up of load()ed Groups as specified in the form (drop down of group ID's). The User is then passed to my service to be update()ed (I know i should be able to use saveOrUpdate() as well).
I realise the problem is with the User's Groups being loaded into the session and then trying to persist them again. Doesn't hibernate realise this relation already exists and skip it (or replace all relations completely) for the User instance?
I've attempted to evict() the Groups of that User before updating, but shouldn't I be able to just call save at the service level and let hibernate use the mappings for collections. Sure I'm wrong here though. Also, marking it as lazy is not an option.
Anywho. The User mapping:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping
>
<class
name="com.affiliate.bo.User"
table="users"
>
<id
name="id"
column="user_id"
type="java.lang.Long"
>
<generator class="increment">
<!--
To add non XDoclet generator parameters, create a file named
hibernate-generator-params-User.xml
containing the additional parameters and place it in your merge dir.
-->
</generator>
</id>
<property
name="dateOfCreation"
type="calendar"
column="dateOfCreation"
/>
<property
name="email"
type="java.lang.String"
column="email"
not-null="true"
unique="true"
/>
<property
name="firstName"
type="java.lang.String"
column="firstName"
/>
<property
name="lastLogin"
type="calendar"
column="lastLogin"
/>
<property
name="lastName"
type="java.lang.String"
column="lastName"
/>
<property
name="telephone"
type="java.lang.String"
column="telephone"
/>
<property
name="title"
type="java.lang.String"
column="title"
/>
<property
name="accountNonLocked"
type="boolean"
column="accountNonLocked"
not-null="true"
/>
<set
name="groups"
table="userGroups"
lazy="false"
cascade="save-update"
sort="unsorted"
order-by="group_id asc"
>
<key
column="user_id"
>
</key>
<many-to-many
class="com.affiliate.bo.Group"
column="group_id"
outer-join="auto"
/>
</set>
<property
name="enabled"
type="boolean"
column="enabled"
not-null="true"
/>
<property
name="password"
type="java.lang.String"
column="password"
not-null="true"
/>
<property
name="expirationDate"
type="calendar"
column="expirationDate"
/>
<property
name="credentialsExpirationDate"
type="calendar"
column="credentialsExpirationDate"
/>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-User.xml
containing the additional properties and place it in your merge dir.
-->
</class>
<query name="findUserByEmail"><![CDATA[
from User user where user.email = :email
]]></query>
</hibernate-mapping>
The Group mapping:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping
>
<class
name="com.affiliate.bo.Group"
table="groups"
>
<id
name="id"
column="group_id"
type="java.lang.Long"
>
<generator class="increment">
<!--
To add non XDoclet generator parameters, create a file named
hibernate-generator-params-Group.xml
containing the additional parameters and place it in your merge dir.
-->
</generator>
</id>
<property
name="name"
type="java.lang.String"
column="name"
unique="true"
/>
<set
name="members"
table="userGroups"
lazy="false"
cascade="save-update"
sort="unsorted"
order-by="user_id"
>
<key
column="group_id"
>
</key>
<many-to-many
class="com.affiliate.bo.User"
column="user_id"
outer-join="auto"
/>
</set>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Group.xml
containing the additional properties and place it in your merge dir.
-->
</class>
</hibernate-mapping>
The editUser logic (Webwork):
Code:
package com.affiliate.controller.actions;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import com.affiliate.bo.User;
import com.affiliate.exceptions.*;
public class EditUser extends BaseAction{
private static final Log logger = LogFactory.getLog(EditUser.class);
private String userId;
private String firstName;
private String lastName;
private String password;
private String password2;
private String telephone;
private String title;
private String[] group;
private boolean isSubmitted = false;
private User user; //the persisted user instance
public String execute(){
if(userId == null){
if(logger.isWarnEnabled()){
logger.warn("No user id specified");
}
return INPUT;
}
try{
user = getAffService().getUser(Long.valueOf(userId));
}
catch(ResourceNotFoundException e){
if(logger.isWarnEnabled()){
logger.warn("Attempt to load user with id " + userId + " failed");
addActionError("User by id; " + userId + " not found");
}
return INPUT;
}
//collate the group selections
Set groupSet = new HashSet();
if(group != null){
for(int i = 0;i < group.length;i++){
try{
groupSet.add(getAffService().getGroup(Long.valueOf(group[i])));
}
catch(ResourceNotFoundException e){
if(logger.isWarnEnabled()){
logger.warn("Attempt to load group with id " + group[i] + " failed");
}
return INPUT;
}
}
}
//set the fields
user.setFirstName(firstName);
user.setLastName(lastName);
user.setTitle(title);
user.setTelephone(telephone);
if(password != null){ user.setPassword(password);}
user.setGroups(groupSet);
try{
getAffService().updateUser(user);
}
catch(PersistenceException e){
if(logger.isErrorEnabled()){
logger.error("Couldn't update user record: " + userId + " " + e.getMessage());
}
return INPUT;
}
return SUCCESS;
}
public String getUserId(){
return userId;
}
public void setUserId(String userId){
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String[] getGroup() {
return group;
}
public void setGroup(String[] group) {
this.group = group;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword2() {
return password2;
}
public void setPassword2(String password2) {
this.password2 = password2;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean getIsSubmitted(){
return isSubmitted;
}
public void setIsSubmitted(boolean submitted){
this.isSubmitted = submitted;
}
}
The logs showing the exception:
Code:
9:46:08,678 ERROR EditUser,TP-Processor5:75 - Couldn't update user record: 1 a different object with the same identifier value was already associated with the session: 1, of class: com.affiliate.bo.User; nested exception is net.sf.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: 1, of class: com.affiliate.bo.User
Any help would be marvellous.