Hibernate M:N Beziehung


#1

Ich steh gerade vor dem kleinen Problem bei einer M:N Beziehung, alles funktioniert nur sobald ich etwas aus der Liste von der Beziehung lösch, löscht Hibernate alle Elemente der Beziehung aus der Verbindungstabelle und fügt die verbliebenen neu ein.

Kennt jemand ne Möglichkeit nur die gelöschten zu entfernen, so das er nicht immer alles löscht und einfügt?


#2

Ich steh gerade vor dem kleinen Problem bei einer M:N Beziehung, alles funktioniert nur sobald ich etwas aus der Liste von der Beziehung lösch, löscht Hibernate alle Elemente der Beziehung aus der Verbindungstabelle und fügt die verbliebenen neu ein.

Sry aber ich kapier nich was du meinst ^^ Nochmal anschaulich bitte :slight_smile:
Hört sich für mich aber so an, als ob irgendwas mit nem Cascade nicht stimmt oder diverse Objekte vllt detached sind oder sowas.


#3

Ich hab eine M:N Beziehung, daher hab ich ja 3 Tabellen
Tracks, Tracks_Positions, Positions wenn ich jetzt eine Position lösch, löscht Hibernate alle Positionen zum Track aus der Tracks_Positions um die verbleibenden dann wieder einzufügen.


#4

Sehr merkwürdig. Zeig doch mal, wie du die Entitäten gemapped hast.


#5

Hier ist der Track

@Entity
// @org.hibernate.annotations.Entity(dynamicUpdate=true)
@Table(name = "tracks")
public class Track
{
  @Id
  @GeneratedValue(generator = "tracks_id_seq", strategy = GenerationType.SEQUENCE)
  @SequenceGenerator(name = "tracks_id_seq", sequenceName = "tracks_track_id_seq", allocationSize = 1)
  @Column(name = "track_id")
  private int id;

  @OneToMany(cascade = CascadeType.ALL, targetEntity = GPSPosition.class, fetch = FetchType.EAGER)
  @JoinTable(name = "track_positions", joinColumns = {@JoinColumn(name = "track_id")}, inverseJoinColumns = {@JoinColumn(name = "gpsposition_id")})
  private List<GPSPosition> positions = new Vector<GPSPosition>();

  private String name;

  @Column(name = "small_description")
  private String smalldescription;

  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "user_id")
  private User user;

  /**
   * 
   */
  public Track()
  {
    positions = new Vector<GPSPosition>();
  }

  public int getID()
  {
    return id;
  }

  public void setID(int id)
  {
    this.id = id;
  }

  public List<GPSPosition> getPositions()
  {
    return positions;
  }

  public void setPositions(List<GPSPosition> positions)
  {
    this.positions = positions;
  }

  public String getName()
  {
    return name;
  }

  public void setName(String name)
  {
    this.name = name;
  }

  public String getSmallDescription()
  {
    return smalldescription;
  }

  public void setSmallDescription(String smalldescription)
  {
    this.smalldescription = smalldescription;
  }

  public User getUser()
  {
    return user;
  }

  public void setUser(User user)
  {
    this.user = user;
  }

  /**
   * @param el
   */
  public void addPosition(GPSPosition el)
  {
    positions.add(el);
  }
}

und hier ist der Track

// @Embeddable
@Entity
// @org.hibernate.annotations.Entity(dynamicUpdate=true)
@Table(name = "gpspositions")
public class GPSPosition
{
  @Id
  @GeneratedValue(generator = "gpsposition_id_seq", strategy = GenerationType.AUTO)
  @SequenceGenerator(name = "gpsposition_id_seq", sequenceName = "gpspositions_gpsposition_id_seq", allocationSize = 10)
  @Column(name = "gpsposition_id")
  private long id;
  private double longitude, latitude, elevation;
  private Timestamp timestamp;

  public long getID()
  {
    return id;
  }

  public void setID(long id)
  {
    this.id = id;
  }

  public double getLongitude()
  {
    return longitude;
  }

  public void setLongitude(double longitude)
  {
    this.longitude = longitude;
  }

  public double getLatitude()
  {
    return latitude;
  }

  public void setLatitude(double latitude)
  {
    this.latitude = latitude;
  }

  public double getElevation()
  {
    return elevation;
  }

  public void setElevation(double elevation)
  {
    this.elevation = elevation;
  }

  public Timestamp getTimestamp()
  {
    return timestamp;
  }

  public void setTimestamp(Timestamp timestamp)
  {
    this.timestamp = timestamp;
  }

  public int hashCode()
  {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(elevation);
    result = prime * result + (int)(temp ^ (temp >>> 32));
    result = prime * result + (int)(id ^ (id >>> 32));
    temp = Double.doubleToLongBits(latitude);
    result = prime * result + (int)(temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(longitude);
    result = prime * result + (int)(temp ^ (temp >>> 32));
    result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
    return result;
  }

  public boolean equals(Object obj)
  {
    if(this == obj)
      return true;
    if(obj == null)
      return false;
    if(getClass() != obj.getClass())
      return false;
    GPSPosition other = (GPSPosition)obj;
    if(Double.doubleToLongBits(elevation) != Double.doubleToLongBits(other.elevation))
      return false;
    if(id != other.id)
      return false;
    if(Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude))
      return false;
    if(Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude))
      return false;
    if(timestamp == null)
    {
      if(other.timestamp != null)
        return false;
    }
    else
      if(!timestamp.equals(other.timestamp))
        return false;
    return true;
  }

}

Vielleicht erkennst du ja woran es liegt, sehen tut man es nur wenn man die SQL Statements mitloggen lässt.


#6

Bin ich blind, oder seh ich nur die M:N Beziehung nicht?

Aber was mir auffällt:

private List<GPSPosition> positions = new Vector<GPSPosition>();

Igitt, igitt. Warum kein Set (ist ja eh nur alles einmal vorhanden)? Und wenn schon kein Set, warum dann ein langsamer, synchronnisierter Vector?


#7

zwischen den beiden Elementen ist keine M:N Beziehung aber zwischen den Tabellen.

  private List<GPSPosition> positions = new Vector<GPSPosition>();```

Der Grund ist der, ich verwende die Position noch in anderen Objekten, daher konnte ich keine 1:M Beziehung machen.

Ich mag nen Set nicht so richtig, weil da kommt man irgendwie immer nur über Iteratoren an die Elemente und soo langsam ist nen Vector auch nicht ;)

#8

Und warum verwendest du dann eine @OneToMany Annotation und keine @ManyToMany?


#9

Weils ja eigentlich ne 1:M ist, ich habs gerade auf M:N umgebaut was auch läuft aber keinen Unterschied in dem Problem aus macht.


#10

Den Grund, warum du keine 1:M Beziehung verwenden kannst, hab ich nicht so ganz verstanden.

Verwende nur testhalber mal anstelle einer List ein Set. Weil so, wie du es jetzt implementiert hast, ist es theoretisch möglich, dass n gleiche Datensätze in die DB geschrieben werden (was auch nicht Sinn und Zweck der Sache ist). Evtl. hat Hibernate damit ja Probleme.


#11

Mitm Set hats geklappt danke


#12

Das von Dir beobachtete Verhalten nennt sich im Hibernate Universum Bag Semantic. In der Doku wirst Du einige Informationen dazu finden.
Bag Semantic würde ich immer vermeiden. Wenn Du eine List brauchst, kannst Du mittels @IndexColumn die Spalte angeben, wo die Position des Elements in der Liste persistent gespeichert wird. Dadurch machst Du aus der Bag Semantic List Semantic und das von Dir beschriebene Problem ist beseitigt.

Die andere Möglichkeit hast Du ja nun schon selbst probiert, Set Semantic. Meiner Erfahrung nach ist das allerdings mit Vorsichtig zu genießen. Es kann nämlich zu häßlichen Exceptions kommen, wenn man equals / hashcode selbst implementiert. Hibernate benutzt diese Methoden natürlich während dem Erzeugen der Entities, wenn sie dem Set hinzugefügt werden. Greift man nun aber in einer der Methoden auf Member zu, die zu diesem Zeitpunkt noch nicht geladen sind, dann wirft Hibernate wenig aussagekräftige Exceptions zur Laufzeit.
Außerdem kann man bei Set Semantic die Reihenfolge der Elemente nicht persistent speichern.