samedi 21 février 2015

@ManyToMany spring-mvc, spring-data-jpa, update error - failed to lazily initialize a collection of role

Very well-known exception "failed to lazily initialize a collection of role". I read many blogs, many suggestions but they did not work for my case. Can anybody help me to understand how to resolve my issue ? All these work in WAR spring-webmvc (JSP view). I have entities Person and Address. 1 Person can have many Addresses, at 1 Address can live many persons (family). Look :



package abc.def.data.model;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

/**
*/
@Entity
@Table( name = "PERSON" )
public class Person implements Serializable {

private static final long serialVersionUID = 1L;

/*
* ID of person.
*/
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
private long id;

/*
* email and used as login of person.
*/
@Column( name = "email", length = 128, nullable = false, unique = true )
private String email;

/*
* Fullname of person.
*/
@Column( name = "fullname", length = 128, nullable = true )
private String fullName;

/*
* Person`s password. It is stored as encrypted value. Nobody know this value except person. There used
* Spring encryption mechanism. Look at the
*/
@Column( name = "password", length = 128, nullable = false )
private String password;

/*
*
*/
@Column( name = "role", length = 16, nullable = false )
private String role;

@Column( name = "timezone", nullable = false )
private int timezone;

@Column( name = "created", nullable = false )
@Type( type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime" )
private DateTime created;

@Column( name = "updated", nullable = false )
@Type( type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime" )
private DateTime updated;

@Column( name = "isenabled", nullable = false )
@Type( type = "true_false" )
private boolean enabled;

/*
* Addresses of person. One person can has many addresses. In that time many persons can live in one
* address (family).
*/
@ManyToMany( fetch = FetchType.LAZY, cascade = {CascadeType.ALL} )
@JoinTable( name = "PERSON_ADDRESS", joinColumns = {@JoinColumn( name = "personid",
referencedColumnName = "id" )}, inverseJoinColumns = {@JoinColumn( name = "addressid",
referencedColumnName = "id" )} )
private Set<Address> addresses = new HashSet<Address>(0);

/**
* closed Contructor. Need for JPA.
*/
protected Person() {

}

/**
* Public Contructor.
*
* @param fullName
* @param email
* @param role
* @param timezone
* @param created
* @param udated
*/
public Person( String fullName, String email, String password, String role, int timezone,
DateTime created, DateTime udated, boolean enabled ) {

this.fullName = fullName;
this.email = email;
this.password = password;
this.role = role;
this.timezone = timezone;
this.created = created;
updated = udated;
this.enabled = enabled;
}

/**
* Public Contructor without enabled status field. By default user is DISABLED here.
*
* @param fullName
* @param email
* @param role
* @param timezone
* @param created
* @param udated
*/
public Person( String fullName, String email, String password, String role, int timezone,
DateTime created, DateTime udated ) {

this.fullName = fullName;
this.email = email;
this.password = password;
this.role = role;
this.timezone = timezone;
this.created = created;
updated = udated;
this.enabled = false;
}

/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {

return String.format(
"Person [id=%s, fullName=%s, email=%s, password=%s, role=%s, timezone=%s, created=%s,"
+ " updated=%s, enabled=%s, {address}]", id, fullName, email, password, role,
timezone, created, updated, enabled );
}

/**
* Getter.
*
* @return the id
*/
public long getId() {

return id;
}

/**
* Getter.
*
* @return the fullName
*/
public String getFullName() {

return fullName;
}

/**
* Getter.
*
* @return the email
*/
public String getEmail() {

return email;
}

/**
* Getter.
*
* @return the password
*/
public String getPassword() {

return password;
}

/**
* Getter.
*
* @return the role
*/
public String getRole() {

return role;
}

/**
* Getter.
*
* @return the timezone
*/
public int getTimezone() {

return timezone;
}

/**
* Getter.
*
* @return the created
*/
public DateTime getCreated() {

return new DateTime( created, DateTimeZone.forOffsetMillis( this.timezone ) );
}

/**
* Getter.
*
* @return the updated
*/
public DateTime getUpdated() {

return new DateTime( updated, DateTimeZone.forOffsetMillis( this.timezone ) );
}

/**
* Getter.
*
* @return the enabled
*/
public boolean isEnabled() {

return enabled;
}

/**
* Getter.
*
* @return the addressCollection
*/
public Set<Address> getAddresses() {

//force clients through our add and remove methods
return Collections.unmodifiableSet( addresses );
}

public void addAddress( Address address ) {

//avoid circular calls : assumes equals and hashcode implemented
if ( !addresses.contains( address ) ) {
addresses.add( address );

//add method to Product : sets 'other side' of association
address.addPerson( this );
}

}

public void removeAddress( Address address ) {

//avoid circular calls : assumes equals and hashcode implemented
if ( !addresses.contains( address ) ) {
addresses.remove( address );

//add method to Product : sets 'other side' of association
address.removePerson( this );
}

}

/**
* Setter.
*
* @param id
* the id to set
*/
public void setId( long id ) {

this.id = id;
}

/**
* Setter.
*
* @param fullName
* the fullName to set
*/
public void setFullName( String fullName ) {

this.fullName = fullName;
}

/**
* Setter.
*
* @param email
* the email to set
*/
public void setEmail( String email ) {

this.email = email;
}

/**
* Setter.
*
* @param password
* the password to set
*/
public void setPassword( String password ) {

this.password = password;
}

/**
* Setter.
*
* @param role
* the role to set
*/
public void setRole( String role ) {

this.role = role;
}

/**
* Setter.
*
* @param timezone
* the timezone to set
*/
public void setTimezone( int timezone ) {

this.timezone = timezone;
}

/**
* Setter.
*
* @param created
* the created to set
*/
public void setCreated( DateTime created ) {

this.created = created;
}

/**
* Setter.
*
* @param updated
* the updated to set
*/
public void setUpdated( DateTime updated ) {

this.updated = updated;
}

/**
* Setter.
*
* @param enabled
* the enabled to set
*/
public void setEnabled( boolean enabled ) {

this.enabled = enabled;
}

/**
* Setter.
*
* @param addressCollection
* the addressCollection to set
*/
public void setAddresses( Set<Address> addressCollection ) {

this.addresses = addressCollection;
}

/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {

final int prime = 31;
int result = 1;
result = prime * result + (int) ( id ^ ( id >>> 32 ) );
return result;
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {

if ( this == obj ) return true;
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
Person other = (Person) obj;
if ( id != other.id ) return false;
return true;
}

}


Address :



package abc.def.data.model;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

/**
* Addresses of person. <br />
* One person can has many addresses. In that time many persons can live in one address (family).
*
*/
@Entity
@Table( name = "ADDRESS", uniqueConstraints = {@UniqueConstraint( columnNames = {"country", "city", "street",
"housenum"} )}, indexes = {} )
public class Address implements Serializable {
private static final long serialVersionUID = 1L;

/*
* Address ID.
*/
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
private long id;

/*
* Name of country.
*/
@Column( length = 128 )
private String country;

/*
* Name of city.
*/
@Column( length = 64 )
private String city;

/*
* Name of street.
*/
@Column( length = 64 )
private String street;

/*
* Number of house.
*/
@Column( name = "housenum" )
private Integer houseNumber;

/*
* Users who live at this Address.
*/
@ManyToMany( fetch = FetchType.LAZY, mappedBy = "addresses" )
public Set<Person> persons = new HashSet<Person>( 0 );

/**
* Getter.
*
* @return the id
*/
public long getId() {

return id;
}

/**
* Getter.
*
* @return the country
*/
public String getCountry() {

return country;
}

/**
* Getter.
*
* @return the city
*/
public String getCity() {

return city;
}

/**
* Getter.
*
* @return the street
*/
public String getStreet() {

return street;
}

/**
* Getter.
*
* @return the houseNumber
*/
public Integer getHouseNumber() {

return houseNumber;
}

/**
* Getter.
*
* @return the personCollection
*/
public Set<Person> getPersons() {

return Collections.unmodifiableSet( persons );
}

/**
* Setter.
*
* @param id
* the id to set
*/
public void setId( long id ) {

this.id = id;
}

/**
* Setter.
*
* @param country
* the country to set
*/
public void setCountry( String country ) {

this.country = country;
}

/**
* Setter.
*
* @param city
* the city to set
*/
public void setCity( String city ) {

this.city = city;
}

/**
* Setter.
*
* @param street
* the street to set
*/
public void setStreet( String street ) {

this.street = street;
}

/**
* Setter.
*
* @param houseNumber
* the houseNumber to set
*/
public void setHouseNumber( Integer houseNumber ) {

this.houseNumber = houseNumber;
}

/**
* Setter.
*
* @param personCollection
* the personCollection to set
*/
public void setPersons( Set<Person> personCollection ) {

this.persons = personCollection;
}

/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {

return String.format( "Address [id=%s, country=%s, city=%s, street=%s, houseNumber=%s, {person}]",
id, country, city, street, houseNumber );
}

/**
*
*/
public void addPerson( Person person ) {

//assumes equals and hashcode implemented: avoid circular calls
if ( !persons.contains( person ) ) {
persons.add( person );

//add method to Product : sets 'other side' of association
person.addAddress( this );
}
}

/**
*
*/
public void removePerson( Person person ) {

//assumes equals and hashcode implemented: avoid circular calls
if ( !persons.contains( person ) ) {
persons.remove( person );
}

//add method to Product : sets 'other side' of association
person.removeAddress( this );
}

/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {

final int prime = 31;
int result = 1;
result = prime * result + (int) ( id ^ ( id >>> 32 ) );
return result;
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {

if ( this == obj ) return true;
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
Address other = (Address) obj;
if ( id != other.id ) return false;
return true;
}


}


In case New registration and new person and new address - no problem. It passed find. In PersonService @Service I have :



// create new Person
Person newPerson = new Person( fullName, email, password, role, timezone, created, udated, true );

// add addresses - it is possible have more 1 address in registration
newPerson.setAddresses( (Set<Address>) addrList );

try {
newPerson = personRepository.save( newPerson );

} catch (Exception e) {
actionResult.setError( true );
actionResult.addErrorItem( "error", e.getMessage() );
}
LOG.debug( "Created new person : {}", newPerson.toString() );
actionResult.setObject( newPerson );

return actionResult;

/*
* Check given addresses for existing.
*/
private Collection<Address> checkAddresses( Set<Address> addrList, Person newPerson ) {

Set<Address> newAddresses = new HashSet<Address>();
Address addr = null;
for (Address address : addrList) {
addr =


addressRepository.findByCountryAndCityAndStreetAndHouseNumber( address.getCountry(), address.getCity(), address.getStreet(), address.getHouseNumber() );
if ( addr != null ) {
addr.getPersons().size();
}

addr = addr == null ? address : addr;
newAddresses.add( addr );

LOG.debug( "checked address {}", addr.toString() );
}
return newAddresses;
}


personRepository :



public interface PersonRepository extends JpaRepository<Person, Long> {
}


In case new user enter existing address - it checks (found) in DB. When it found it have to be added to newPerson() . In this case exception arise. I tried use address.getPerson().size - also exception .


Also I do not want use EAGER. is It possible ?


Aucun commentaire:

Enregistrer un commentaire