Ich habe eine Datenbank mit zwei Tabellen User und Country. Ich möchte eine Beziehung herstellen, in der viele Benutzer zu einem Landkreis gehören können. Ich implementiere dies mit Hibernate unter Verwendung der folgenden Modellklassen:
@Entity (name = "user")
public class User {
@Id @GeneratedValue (strategy = GenerationType.IDENTITY)
private int userId;
private String username;
private String password;
@ManyToOne ()
@JoinColumn (name = "countryId")
private Country country;
//Getter & Setter
}
@Entity (name = "country")
public class Country {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int countryId;
private String countryName;
//Getter & Setter
}
Wenn ich versuche, das Benutzerobjekt zu speichern, erhalte ich die folgende Ausnahme:
org.hibernate.HibernateException: Data was not saved: object references an unsaved transient instance - save the transient instance before flushing: com.kyrogaming.models.Country
Wie kann ich das Problem beheben?
Sie können keine Daten für den Ruhezustand speichern, bis Sie auch über alle anderen Objekte, auf die das neu gespeicherte Objekt verweist, Hibernate mitgeteilt haben. In diesem Fall erzählen Sie Hibernate von einer User
, haben es aber noch nicht von Country
erzählt.
Sie können Probleme auf zwei Arten lösen.
Manuell
Rufen Sie session.save(country)
auf, bevor Sie die User
speichern.
CascadeType
Sie können im Ruhezustand angeben, dass diese Beziehung einige Operationen mithilfe von CascadeType ausbreitet. In diesem Fall würde CascadeType.PERSIST ebenso wie CascadeType.ALL die Arbeit erledigen.
Verweis auf bestehende Länder
Basierend auf Ihrer Antwort auf @zerocool haben Sie jedoch ein zweites Problem: Wenn Sie zwei User
-Objekte mit derselben Country
haben, stellen Sie nicht sicher, dass es sich um sameCountry
handelt. Dazu müssen Sie die entsprechende Country
aus der Datenbank abrufen, den neuen Benutzer festlegen und dann die User
speichern. Dann beziehen sich beide User
-Objekte auf die gleiche Country
, nicht nur auf zwei Country
-Instanzen, die zufällig denselben Namen haben. Überprüfen Sie die Criteria
-API als eine Möglichkeit, vorhandene Instanzen abzurufen.
Sieht so aus, als ob Benutzer, die in Ihrem Country-Objekt hinzugefügt wurden, noch nicht in der Datenbank vorhanden sind. Sie müssen kaskadieren, um sicherzustellen, dass bei einem permanenten Land alle Benutzer, die nicht in den Daten vorhanden sind, aber dem Land zugeordnet sind, auch persistent bleiben.
Der folgende Code sollte helfen:
@ManyToOne (cascade = CascadeType.ALL)
@JoinColumn (name = "countryId")
private Country country;
Ich hatte das gleiche Problem. In meinem Fall ist dies der Fall, weil die Suchtabelle "country" einen vorhandenen Datensatz mit countryId == 0 und einen primitiven Primärschlüssel enthält und ich versuche, einen Benutzer mit einer countryID == 0 zu speichern. Ändern Sie den Primärschlüssel des Landes in Ganzzahl. Der Ruhezustand kann jetzt neue Datensätze identifizieren.
Für die Empfehlung, Wrapper-Klassen als Primärschlüssel zu verwenden, siehe diese Stackoverflow-Frage
Um meine 2 Cent hinzuzufügen, habe ich dasselbe Problem, wenn ich versehentlich null
als ID sende. Der folgende Code zeigt mein Szenario (und OP hat kein bestimmtes Szenario erwähnt)}.
Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // -----> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);
Hier setze ich die vorhandene Abteilungs-ID auf eine neue Mitarbeiterinstanz, ohne zuerst die Abteilungsentität zu erhalten, da ich nicht möchte, dass eine andere Auswahlabfrage ausgelöst wird.
In einigen Szenarien kommt deptId
PKID als null
von der aufrufenden Methode und ich bekomme den gleichen Fehler.
Achten Sie also auf null
-Werte für PK ID.
Gleiche Antwort hier
Es ist wegen CASCADE TYPE
wenn Sie setzen
@OneToOne (cascade = CascadeType.ALL)
Sie können Ihr Objekt einfach so speichern
user.setCountry(country);
session.save(user)
aber wenn Sie setzen
@OneToOne(cascade={
CascadeType.PERSIST,
CascadeType.REFRESH,
...
})
Sie müssen Ihr Objekt so speichern
user.setCountry(country);
session.save(country)
session.save(user)
Na wenn du gegeben hast
@ManyToOne ()
@JoinColumn (name = "countryId")
private Country country;
dann Objekt dieser Klasse, ich meine, Land muss zuerst gesichert werden.
der Benutzer kann nur dann in der Datenbank gespeichert werden, wenn für dieses Land ein Schlüssel für diesen Benutzer verfügbar ist. bedeutet, dass Benutzer nur dann gespeichert werden können, wenn dieses Land in der Tabelle Land vorhanden ist.
Dafür müssen Sie dieses Land zuerst in der Tabelle speichern.
Es sollte CascadeType.Merge sein. In diesem Fall wird es aktualisiert, wenn der Datensatz bereits vorhanden ist.