I need help to understand how ORM works. Here is the scenario that is very common. I have two main tables Organization and RelatedParty which have to be in many to many relation. But there is also relation_type attribute that defines what kind of relation exists between Organization and Relatedparty.
Here are my entity classes: Organization:
@Entity
@Table(name = "organization", catalog = "...", schema = "")
@XmlRootElement
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "organization_id", nullable = false)
private Integer organizationId;
@Column(name = "organization_name", nullable = false)
private String organizationName;
@OneToMany(cascade = {CascadeType.ALL}, mappedBy = "organization")
private List<Organdrelatedparty> organdrelatedpartyList;
...
//getter setter methods
Organdrelatedparty: which uses composite primary key OrgandrelatedpartyPK
@Entity
@Table(name = "organdrelatedparty", catalog = "...", schema = "")
@XmlRootElement
public class Organdrelatedparty implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
protected OrgandrelatedpartyPK organdrelatedpartyPK;
@JoinColumn(name = "relatedParty_id", referencedColumnName = "relatedParty_id", nullable = false, insertable = false, updatable = false)
@ManyToOne(optional = false, cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private Relatedparty relatedparty;
@JoinColumn(name = "orgRelation_id", referencedColumnName = "orgRelation_id", nullable = false)
@ManyToOne(optional = false)
private ParOrgrelationtype orgRelationid;
@JoinColumn(name = "organization_id", referencedColumnName = "organization_id", nullable = false, insertable = false, updatable = false)
@ManyToOne(optional = false)
private Organization organization;
...
//getter setter methods
OrgandrelatedpartyPK
@Embeddable
public class OrgandrelatedpartyPK implements Serializable {
@Basic(optional = false)
@NotNull
@Column(name = "relatedParty_id", nullable = false)
private int relatedPartyid;
@Basic(optional = false)
@NotNull
@Column(name = "organization_id", nullable = false)
private int organizationId;
...
//getter setter methods
RelatedParty: which is in unidirectional oneToMany relationship with organdRelatedParty class. In other word that relatedParty entity has no knowledge about organdRelatedParty entity that is on the other side.
@Entity
@Table(name = "relatedparty", catalog = "...", schema = "")
@XmlRootElement
public class Relatedparty implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "relatedParty_id", nullable = false)
private Integer relatedPartyid;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 100)
@Column(name = "firstName", nullable = false, length = 100)
private String firstName;
@Size(max = 100)
@Column(name = "lastName", length = 100)
private String lastName;
@Basic(optional = false)
@NotNull
@Column(name = "isForeign", nullable = false)
private boolean isForeign;
...
//getter setter methods
For insertion, if I persist new Organization Entity, it cascades persist activity to new OrgandrelatedParty which also cascades persist activity to new RelatedParty. So all the related entities are persisted and it works fine.
For updating, User is expected to change existing organization and relatedParty entities and also add new relatedParty to organization. So we prefer to delete all OrgandrelatedParties first and add new relatedParties and edited relatedParties again after that.
This is our method that handles updating: We pass new organization and also all new and old relatedParties as a list to method: firs we delete all old OrgAndRelatedParties then we create again all relatedParties in list as new OrgandrelatedParties. This is main method to update organization.
public void updateOrganization(Organization newOrganization, List<Relatedparty> newShareList) throws ControlException {
try{
tx.begin();
this.updateOrgAndRelatedShares(newOrganization, newShareList);
customerController.updateOrganization(newOrganization);
tx.commit();
}catch(ControlException ex){
...
customerController's updateOrganization method does first find old Organization by find method of entity manager then copies all attributes of new organization to old then merges old organization and flush:
public void updateOrganization(Organization newOrganization)
{
Organization preOrganization = em.find(Organization.class, newOrganization.getOrganizationId);
preOrganization.setOrganizationId(newOrganization.getOrganizationId);
preOrganization.setOrganizationName(newOrganization.getOrganizationName);
em.merge(preOrganization);
em.flush();
}
here are other methods:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
private void updateOrgAndRelatedShares(Organization org, List<Relatedparty> shareList) throws ControlException
{
for(Iterator<Organdrelatedparty> it = org.getOrgandrelatedpartyList().iterator(); it.hasNext();)
{
Organdrelatedparty op = it.next();
it.remove();
op.setOrganization(null);
op.setRelatedparty(null);
deleteOrgRelated(op);
}
org.getOrgandrelatedpartyList().clear();
for(Relatedparty relatedParty: shareList){
int parOrgRelationTypeId = relatedParty.getIsPerson() ? 1:2;
createOrgAndRelatedParty(org, relatedParty, parOrgRelationTypeId);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteOrgRelated(Organdrelatedparty org) throws ControlException{
try{
org = em.find(Organdrelatedparty.class, org.getOrgandrelatedpartyPK());
em.remove(org);
em.flush();
}
catch(Exception ex){
Logger.getLogger(RelatedpartyController.class.getName()).log(Level.SEVERE, null, ex);
throw new ControlException("Couln't delete org relation", ex);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
private void createOrgAndRelatedParty(Organization org, Relatedparty relatedParty, int parOrgRelationTypeId) throws ControlException{
if(findRelatedPartyByRegNum(relatedParty.getRegisterNumber()) == null || relatedParty.getRelatedPartyid() == null){
createRelated(relatedParty);
}else{
relatedParty = updateRelatedParty(relatedParty);
}
Organdrelatedparty preOrp = new Organdrelatedparty(relatedParty.getRelatedPartyid(),
preOrp.setOrganization(org);
preOrp.setRelatedparty(relatedParty);
preOrp.setOrgRelationid(prepareOrgandRelatedPartyType(parOrgRelationTypeId));
org.getOrgandrelatedpartyList().add(preOrp);
}
And my question is when I merge organization entity with new List organdrelatedpartyList it throws exception like this:
SEVERE: java.lang.IllegalArgumentException: Cannot merge an entity that has been removed: mn.bsoft.crasmonclient.model.Organdrelatedparty[ organdrelatedpartyPK=mn.bsoft.crasmonclient.model.OrgandrelatedpartyPK[ relatedPartyid=71, organizationId=19 ] ]
I found out that eclipseLink does persist operation first then remove operations. So I think that it tries to insert organdrelatedparty entity that has same composite id with entity which was not deleted previously from database. I flushes every time I remove old organdrelatedparties. But it doesn't help. What is the solution? Any idea guys.
I'm using jpa 2.0; eclipseLink as provider and glassfish 3.1.2