-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: Hibernate Mapping using an Association With Attributes
PostPosted: Fri Jan 04, 2008 5:18 pm 
Newbie

Joined: Fri Jan 04, 2008 4:21 pm
Posts: 4
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);
         }


Top
 Profile  
 
 Post subject: I have a solution, sort of
PostPosted: Thu Jan 17, 2008 9:30 am 
Newbie

Joined: Fri Jan 04, 2008 4:21 pm
Posts: 4
I set this problem aside for a while hoping that someone would provide some help, but finally came up with a workable solution yesterday. This is not an ideal solution, but it will get the job done.

After a little debugging, I discovered that if you use @OneToMany, deletes will always be done by setting the foreign key to null via an update. I then found that if I use a CollectionOfElements mapping, it uses a regular delete. So here is the new mapping in PEvent:

Code:
@CollectionOfElements(fetch=FetchType.LAZY,targetElement=PEventComponentTie.class)
@JoinTable(name="EventComponentTie",joinColumns=@JoinColumn(name="event_guid"))
@OrderBy("purpose asc, index asc")
public List<PEventComponentTie> getEventComponentTies()
{
return eventcomponentties;
}

public void setEventComponentTies( List<PEventComponentTie> ties )
{
  this.eventcomponentties = ties;
}


and then I have an embeddable component:

Code:
@Embeddable
public class EventComponentTie implements java.io.Serializable {
  private String purpose;
  private String componentGuid;
  private Integer index;
 
  public EventComponentTie() {}
 
  public EventComponentTie( String componentGuid, String purpose, Integer idx )
  {
    super();
    this.componentGuid = componentGuid;
    this.purpose = purpose;
    this.index = idx;
  }
 
  public String getPurpose()
  {
    return purpose;
  }

  public void setPurpose( String purpose )
  {
    this.purpose = purpose;
  }

  @Column(name="component_guid")
  public String getComponentGuid()
  {
    return componentGuid;
  }

  public void setComponentGuid( String componentGuid )
  {
    this.componentGuid = componentGuid;
  }

  @Column(name="seq_num")
  public Integer getIndex()
  {
    return index;
  }

  public void setIndex( Integer index )
  {
    this.index = index;
  }
}


This does, I believe, mean that whenever I make changes to the collection, all of the elements will be deleted, and inserted, but I will have to live with that drawback. I will also have to write additional code to retrieve the actual PComponent objects using the componentGuid in the EventComponentTie, which makes this fairly inelegant. But this atleast will work. Any helpful thoughts or ideas would still be appreciated.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.