Problem With Association With Attributes
I have the following schema that I am trying to setup using hibernate's jpa implementation with annotations.
Code:
CREATE TABLE Event( guid char(32) NOT NULL, title varchar(75), PRIMARY KEY (guid));
CREATE TABLE Component(guid char(32) NOT NULL, title varchar(75), PRIMARY KEY (guid));
CREATE TABLE EventComponentTie(event_guid char(32) NOT NULL, purpose varchar(20) NOT NULL, seq_num int NOT NULL, component_guid char(32) NOT NULL, PRIMARY KEY (event_guid, purpose, seq_num, component_guid));
The idea here is that an Event can have a number of Components associated with it. These associated are grouped by their purpose, and within each group they are ordered by the seq_num.
Ideally I would like to have a OneToMany mapping on the Event that would return a Map with the key being the purpose column in EventComponentTie and the values be a list of Components ordered by the seq_num field in EventComponentTie. After some research, it appears that this isn't currently possible. So I decided to create an entity to represent the EventComponentTie association. Here is what I have implemented:
Code:
@Entity
@Table(name = "Event")
public class PEvent implements Serializable
{
private String guid;
private String title;
private Set<PEventComponentTie> eventComponentTies;
@Id
public String getGuid() { return guid; }
public void setGuid( String guid ) { this.guid = guid; }
public String getTitle() { return title; }
public void setTitle( String title ) { this.title = title; }
@OneToMany(fetch=FetchType.LAZY)
@Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
@JoinColumn(name="event_guid")
public Set<PEventComponentTie> getEventComponentTies()
{
return eventComponentTies;
}
public void setEventComponentTies(Set<PEventComponentTie> eventComponentTies)
{
this.eventComponentTies = eventComponentTies;
}
}
Code:
@Entity
@Table(name = "Component")
public class PComponent implements Serializable {
private String guid;
private String title;
@Id
public String getGuid() { return guid; }
public void setGuid( String guid ) { this.guid = guid; }
public String getTitle() { return title; }
public void setTitle( String title ) { this.title = title; }
}
The association table EventComponentTie has a composite key made up of all the columns of the table, so I use an EmbeddedId
Code:
@Entity
@Table(name="eventcomponenttie")
public class PEventComponentTie implements Serializable
{
private PEventComponentTiePrimaryKey id;
private PEvent event;
private PComponent component;
public PEventComponentTie() { super(); }
public PEventComponentTie( PEvent event, PComponent component, String purpose, Integer index )
{
this();
this.id = new PEventComponentTiePrimaryKey();
this.id.setEvent_guid(event.getGuid());
this.id.setPurpose(purpose);
this.id.setIndex(index);
this.setPComponent(component);
}
@EmbeddedId
public PEventComponentTiePrimaryKey getId() { return id; }
public void setId( PEventComponentTiePrimaryKey id ) { this.id = id; }
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="event_guid",nullable=false,insertable=false,updatable=false)
public PEvent getEvent() { return event; }
public void setEvent( PEvent event ) { this.event = event; }
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="component_guid",nullable=false,insertable=false,updatable=false)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public PComponent getPComponent() { return component; }
public void setPComponent( PComponent component ) { this.component = component; }
}
And here is the composite key class:
Code:
@Embeddable
public class PEventComponentTiePrimaryKey implements Serializable
{
private String component_guid;
private String event_guid;
private String purpose;
private Integer index;
@Column(nullable = false)
public String getComponent_guid()
{
return component_guid;
}
public void setComponent_guid( String component_guid )
{
this.component_guid = component_guid;
}
@Column(nullable = false)
public String getPurpose()
{
return purpose;
}
public void setPurpose( String purpose )
{
this.purpose = purpose;
}
@Column(name = "seq_num", nullable = false)
public Integer getIndex()
{
return index;
}
public void setIndex( Integer index )
{
this.index = index;
}
@Column(nullable = false)
public String getEvent_guid()
{
return event_guid;
}
public void setEvent_guid( String event_guid )
{
this.event_guid = event_guid;
}
@Override
public int hashCode()
{
int componentGuidHash = this.component_guid == null ? 0 : this.component_guid.hashCode();
int eventGuidHash = this.event_guid == null ? 0 : this.event_guid.hashCode();
int purposeHash = this.getPurpose() == null ? 0 : this.purpose.hashCode();
int seq_numHash = this.getIndex() == null ? 0 : this.index.hashCode();
return componentGuidHash ^ eventGuidHash ^ purposeHash ^ seq_numHash;
}
@Override
public boolean equals( Object o )
{
if (this == o) {
return true;
}
if (!(o instanceof PEventComponentTiePrimaryKey)) {
return false;
}
PEventComponentTiePrimaryKey other = (PEventComponentTiePrimaryKey) o;
boolean componentGuidCheck = true;
boolean eventGuidCheck = true;
boolean purposeCheck = true;
boolean indexCheck = true;
if ((this.getComponent_guid() == null && other.getComponent_guid() != null) ||
(this.getComponent_guid() != null && other.getComponent_guid() == null) ||
(!this.getComponent_guid().equals(other.getComponent_guid()))) {
componentGuidCheck = false;
}
if ((this.getEvent_guid() == null && other.getEvent_guid() != null) ||
(this.getEvent_guid() != null && other.getEvent_guid() == null) ||
(!this.getEvent_guid().equals(other.getEvent_guid()))) {
eventGuidCheck = false;
}
if ((this.getPurpose() == null && other.getPurpose() != null) ||
(this.getPurpose() != null && other.getPurpose() == null) ||
(!this.getPurpose().equals(other.getPurpose()))) {
purposeCheck = false;
}
if ((this.getIndex() == null && other.getIndex() != null) ||
(this.getIndex() != null && other.getIndex() == null) ||
(!this.getIndex().equals(other.getIndex()))) {
indexCheck = false;
}
return componentGuidCheck && eventGuidCheck && purposeCheck && indexCheck;
}
}
The mapping seems to be working correctly. I can call getEventComponentTies() on Event and I get back a Set of PEventComponentTie objects. The problem I encounter is when I try to remove an EventComponentTie from the Set. I get the following exception:
Quote:
15:43:26,827 ERROR JDBCExceptionReporter:78 - Batch entry 0 update eventcomponenttie set event_guid=null where event_guid=98033C7700C3C31500FC327E705B8006 and component_guid=98033C770140984B00FC2BF1EA4880BA and event_guid=98033C7700C3C31500FC327E705B8006 and seq_num=2 and purpose=Reading Materials was aborted. Call getNextException to see the cause.
15:43:26,828 WARN JDBCExceptionReporter:77 - SQL Error: 0, SQLState: 23502
15:43:26,833 ERROR JDBCExceptionReporter:78 - ERROR: null value in column "event_guid" violates not-null constraint
The generated SQL that causes this is:
Code:
update
eventcomponenttie
set
event_guid=null
where
event_guid=?
and component_guid=?
and event_guid=?
and seq_num=?
and purpose=?
I did a lot of reading in the forums and found numerous individuals mentioning this issue of a remove generating an update which sets the row to null. The solution given seems to be to set inverse=true for the relationship. I believe the way to do this is to add the mappedBy property to the OneToMany relationship as follows for the PEvent class:
Code:
@OneToMany(fetch=FetchType.LAZY,mappedBy="Event")
@Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
@JoinColumn(name="event_guid")
public Set<PEventComponentTie> getEventComponentTies()
{
return eventComponentTies;
}
When I do this, I don't get the error, but it doesn't actually perform the remove the way that I would like it to, which makes sense since I believe adding the mappedBy is basically telling it not to manage persistence for the collection.
I am using Postgresql 8.2 and Hibernate 3.2.5, and Hibernate Annotations 3.3.0.
Here is the code I am using to test with:
Code:
PEvent event = em.get().find(PEvent.class,"98033C7700C3C31500FC327E705B8006" );
Set<PEventComponentTie> ties = event.getEventComponentTies();
PComponent component = em.get().find(PComponent.class,"98033C770140984B00FC2BF1EA4880BA");
String purpose = "Reading Materials";
int idx = 2;
PEventComponentTiePrimaryKey pk = new PEventComponentTiePrimaryKey();
pk.setComponent_guid(component.getGuid());
pk.setEvent_guid(event.getGuid());
pk.setPurpose(purpose);
pk.setIndex(idx);
PEventComponentTie target = null;
for (PEventComponentTie tie : ties) {
if (tie.getId().equals(pk)) {
target = tie;
break;
}
}
if ( target != null )
{
ties.remove(target);
}