Hibernate Mappingproblem: Wie muss hier die Mapping Konfiguration aussehen?

DarthShader

Erfahrenes Mitglied
Hallo,

ich habe ein Hiberante-Mapping-Problem mit dem folgenden Szenario. Ich bin nicht sicher, wie ich das mapping konfigurieren muss.

Dies ist die Klasse "Customer" als "Haupttabelle", sie hat keinerlei Foreign-Keys:

Java:
@Entity
@Table( name = "customer" )
public class Customer
{
	@Id
	@GeneratedValue( strategy = GenerationType.IDENTITY )
	private Integer 	id;

	@OneToOne( mappedBy = "customer", cascade = CascadeType.ALL )
	private Location	homeLocation;

	@OneToOne( mappedBy = "customer", cascade = CascadeType.ALL )
	private Location	workLocation;

	// Getters and setters
	// ....
}

Die folgende Klasse beschreibt eine zweite Tabelle:

Java:
@Entity
@Table( name = "location" )
public class Location
{
	public enum LocationType {
		HOME, WORK
	}

	@Id
	@GeneratedValue( strategy = GenerationType.IDENTITY )
	private Integer 		id;
	
	@ManyToOne( optional = true )
	@JoinColumn( name = "customer_id" )
	private Customer 		customer;

	private LocationType 	locationType;
	prviate String 			someOtherData;

	// Getters and setters
	// ....
}

(Dies ist eine sehr vereinfachte Version meines DB-Schemas/des Domain Modols, aber es gibt das Problem gut wieder.)

Man kann sehen, dass ein Customer zwei Locations hat (Home und Work). Der folgende Code zeigt, wie man ein Customer Objekt erstellt, welches persistiert werden kann:

Java:
Location homeLocation = new Location();
homeLocation.setLocationType( LocationType.HOME );

Location workLocation = new Location();
workLocation.setLocationType( LocationType.WORK );

Customer customer = new Customer();
customer.setHomeLocation( homeLocation );
customer.setWorkLocation( workLocation );

homeLocation.setCustomer( customer );
workLocation.setCustomer( customer );


Nun habe ich ein Customer Objekt, welches ich problemlos mit Hibernate in die Datenbank schreiben kann. Jedoch bin ich nicht in der Lage, dieses Objekt wieder aus der Datenbank zu laden. Versuche ich das, erscheint folgende Exception:

Java:
org.hibernate.HibernateException: More than one row with the given identifier was found:
9, for class: de.foo.domain.Customer

Die Erklärung ist (so denke ich), dass man nun zwei Objekte (Datensätze) in der Tabelle für die Locations hat, die beide zum selben Customer gehören (über den Foreign Key). Wenn Hibernate nun ein Join macht, so findet es zwei Location Objekte vor - jedoch wurde nur eines erwartet.

Was ich nun möchte ist, dass ich das Property "homeLocation" der Klasse "Customer" auf den entsprechenden Datensatz mappe, der in der Spalte "locationType" den Wert "HOME" hat. Entsprechend soll "workLocation" so aufgelöst werden, dass er den Datensatz nimmt, der in der Spalte "locationType" den Wert "WORK" hat.

Wie muss das (annotation basierte) Mapping nun aussehen? Wie kann ich Hibernate anweisen, die Spalte "locationType" (zusätzlich zur "customer_id" Spalte) für die Identifikation des korrekten Datensatzes zu verwenden?


Über Eure Hilfe würde ich mich sehr freuen


Vielen Dank!
 
Hallo!
Warum speicherst du in der Location den Customer mit ab? Ergibt für mich so keinen Sinn, da du die Zuordnung Customer - Location in der Customer-Klasse schon drin hast. Wenn du das weglässt, solltest du auch keine Probleme mehr mit Hibernate haben.
Außer du brauchst diese Zuordnung unbedingt.

mfg flo
 
Hallo,

so ist das Datenbank Schema aufgebaut - ich habe nur bedingt Einfluss darauf.

Zur Erklärung:

- customer ist die Parent-Tabelle
- location ist die Child-Tabelle
- die Child-Tabelle hat eine Referenz auf die Parent-Tabelle (Foreign Key)
- Die Java-Referenzen in der Customer-Klasse "homeLocation" und "workLocation" machen daraus ein Bidirektionales Mapping, sodass ich in Java von der Parent zur Child Tabelle navigieren kann.

Ich denke, so ungewöhnlich ist es nicht. Aus Datenbankschema Sicht wäre es sogar sehr gewöhnlich, wenn nur die Location Klasse eine Referenz auf die Customer Klasse hat.

Das wird mit Hibernate sicherlich gehen, vielleicht mit einer Art zusammengesetzter Key, ich tippe auf Annotations wie @idClass, @embeddable in Kombination mit den richtigen equals und hashCode Methoden. Leider bleibt es bei mir bei diesen Ansätzen, ich komme einfach nicht weiter.

Ich würde mich sehr freuen, wenn hier noch jemand einen Tipp (oder eine Lösung? :) ) für mich hat.


Danke!
 
Hallo,

... Das wird mit Hibernate sicherlich gehen, vielleicht mit einer Art zusammengesetzter Key,...

Ein zusammengesetzter Schlüssel wäre eine Möglichkeit, aber dann hast du das Problem, dass der nicht mehr automatisch generiert wird. Deshalb mal ein anderer Vorschlag.

Die Location ist ja entweder eine Work- oder eine Homelocation, je nachdem, was für ein LocationType benutzt wird. Ich würde das ganze über Vererbung realisieren. Dann kann man die Spalte, die bisher den Typ gespeichert hat, als DiscriminatorColumn benutzen und sowohl Home- als auch Worklocation in die gleiche Tabelle packen. Dadurch gibts es dann auch keine Probleme mehr mit dem Customer.

Das ganze sieht dann ungefähr so aus:

Location:
Java:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="locationtype",discriminatorType=DiscriminatorType.STRING)
public abstract class Location {
	 
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Integer         id;
   
    @OneToOne( optional = true )
    @JoinColumn( name = "customer_id" )
    private Customer customer;
 
    private String  someOtherData;
   
    //.....
}

WorkLocation
Java:
@Entity
@DiscriminatorValue(value="WORK")
public class WorkLocation extends Location {

}
HomeLocation
Java:
@Entity
@DiscriminatorValue(value="HOME")
public class HomeLocation extends Location {
	
}

Customer
Java:
@Entity
public class Customer {
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Integer     id;
 
    @OneToOne( mappedBy = "customer", cascade = CascadeType.ALL )
    private HomeLocation    homeLocation;
 
    @OneToOne( mappedBy = "customer", cascade = CascadeType.ALL )
    private WorkLocation    workLocation;
}

Grüße
THMD
 
Hallo THMD,

vielen Dank für Deine ausführliche Antwort. Diese Lösung ist mir noch nicht eingefallen, das klingt gut und plausibel.

Ein etwas anderer Workaround von mir, der nun funktioniert, ist, dass ich die locations einfach als eine 1:N Beziehung mache, sodass ich in der Customer Kklasse eine Liste von Locations habe. Die Getter Methoden liefern mir dann aus der Liste jenes Location Objekt, was entweder WORK oder HOME ist. So habe ich im Prinzip die Mapping-Logik in die Programmlogik verlegt, vielleicht nicht die beste Variante, aber ich finde es irgendwo doch elegant, weil es das Mapping einfach hält.
 
Hi,

eine ManyToOne-Beziehung und zwei OneToOne-Bezeichnungen zu beantworten ist ja wohl nicht so ganz richtig...

ManyToOne <-> OneToMany
OneToOne <-> OneToOne

Wäre auch sinnvoller, denn wenn dann einer noch seine Ferienwohnung,... angeben will geht das problemlos. In deinem Beispiel aber nicht.

Grüße,

Dirk
 

Neue Beiträge

Zurück