Sziasztok.
Lenne egy entitás mely a vevő címek gépkocsikhoz rendelését hivatott reprezentálni.
Működik is szépen, ha már van létező (mentett) gépkocsi és ahhoz rendelek címeket.
De ha létrehozok egy új gépkocsi entitást(mely még nem volt perzisztálva), hozzáadok vevő címeket
entity.getVevocimek().add(cim);
(a vevő címek már léteznek az adatbázisban) akkor
Cannot add or update a child row: a foreign key constraint fails
hibaüzenetet kapok.
Azt gyanítom, hogy a JPA nem tudja (vagy én nem tudom hogy kell) megoldani, hogy először elmentse a szülő, majd a gyerek rekordokat.
CREATE TABLE VEVOCIM (
ID INTEGER NOT NULL,
IDMOD INTEGER NOT NULL,
CONSTRAINT PK_VEVOCIM PRIMARY KEY (ID, IDMOD)
)
CREATE TABLE GEPKOCSI (
ID INTEGER NOT NULL AUTO_INCREMENT,
CONSTRAINT PK_GEPKOCSI PRIMARY KEY (ID),
)
CREATE TABLE GEPKOCSI_VEVOCIM_OSSZERENDELO (
ID_GEPKOCSI INTEGER NOT NULL,
ID_VEVOCIM INTEGER NOT NULL,
ID_VEVOCIM_MOD INTEGER NOT NULL,
CONSTRAINT PK_GEPKOCSI_VEVOCIM_OSSZERENDELO PRIMARY KEY (ID_GEPKOCSI, ID_VEVOCIM, ID_VEVOCIM_MOD),
CONSTRAINT FK_GEPKOCSI_VEVOCIM_OSSZERENDELO_01 FOREIGN KEY (ID_GEPKOCSI) REFERENCES GEPKOCSI (ID),
CONSTRAINT FK_GEPKOCSI_VEVOCIM_OSSZERENDELO_02 FOREIGN KEY (ID_VEVOCIM, ID_VEVOCIM_MOD) REFERENCES VEVOCIM (ID,IDMOD)
);
@Entity
@IdClass(VevoPK.class)
public class Vevo {
@Id
@Column(name = "ID")
private int id;
@Id
@Column(name = "ID")
private int idmod;
....
}
@Entity
public class Gepkocsi {
@Id
@Column(name = "ID")
private int id;
@OneToMany
@JoinTable
(
name = "GEPKOCSI_VEVOCIM_OSSZERENDELO",
joinColumns = {@JoinColumn(name = "ID_GEPKOCSI", referencedColumnName = "ID")},
inverseJoinColumns = {@JoinColumn(name = "ID_VEVOCIM", referencedColumnName = "ID", unique = true),
@JoinColumn(name = "ID_VEVOCIM_MOD", referencedColumnName = "IDMOD", unique = true)}
)
private List<Vevocim> vevocimek;
Ha csak simán perzisztálok
public void save {
entityManager.getTransaction().begin();
entityManager.persist(getEntity());
entityManager.getTransaction().commit();
}
akkor ezt a hibaüzenetet kapom.
org.eclipse.persistence.exceptions.DatabaseException Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails
Ha módosítom a Gepkocsi entitást
@OneToMany(cascade = CascadeType.ALL)
akkor az alábbi hibaüzenetet jön:
org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1-1' for key 'PRIMARY'
Error Code: 1062
Call: INSERT INTO VEVOCIM (ID, IDMOD, AJTO, AKTIV, CIMTIPUS, CK, CREATE_TIME, CREATE_USER, EMELET, EPULET, HAZSZAM, HELYRAJZISZAM, IDVEVO, IDVEVOMOD, IRANYITOSZAM, KERULET, KOZTERULET, KOZTERULETJELLEGE, LAT, LEPCSOHAZ, LNG, MOD_TIME, MOD_USER, NEV, ORSZAG, TELEPULESNEVE) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
tehát a már létező vevőcímet akarja visszamenteni az adatbázisba.
Hogy kell ezt rendesen csinálni? A vevőcímek már léteznek, perzisztálva vannak.
Az új gépkocsi rögzítése közben szeretném hozzárendelni a vevő címeket és mentéskor történjen meg gépkocsi adatainak mentése a
GEPKOCSI
táblába és a
GEPKOCSI_VEVOCIM_OSSZERENDELO
táblába is insertálja be a megfelelő sort.
- 1026 megtekintés
Hozzászólások
Ha egy vevőnek több gépkocsija is lehet, akkor inkább ManyToMany kapcsolat kellene. Ez mondjuk nem változtat a kapcsolótábla mappelésén...
A mysql melyik constraint-en száll el? (ha jól sejtem, korrelál a második hibaüzenettel)
A Vevo entitás a VEVOCIM táblára van mappelve, vagy ez két külön dolog?
A Vevo entitás ID mappelése pedig nem tűnik jónak, az idmod
field nem az IDMOD
oszlopra kellene mutasson?
- A hozzászóláshoz be kell jelentkezni
- A vevo entitasnak ket @Id mezoje van. Ez nem biztos, hogy baj, ha megosztott kulcsot akarsz, de itt szerintem nem ez a helyzet.
- Az id generalasa hogy tortenik? Ebbol ugy tunik, sehogy sem autoincrement, sem "kezi" megadas nem latszik. Ez alapjan neked kell feltoltened. Mivel nem mondtal nullable=false-t, az id-hez ezert a jpa nem fogja ellenorizni es megengedi null id-ju rekordok beszurasat. A join-nal unique=true van. Ezert a masodik hivatkozo rekord beszurasanal hibat fog generalni.
- A hozzászóláshoz be kell jelentkezni
Igen, a vevő entitásnak szándékosan van 2 @Id-je tehát a primary key 2 mezőből áll.
Az id-t én generálom mikor a vevő entitás először kerül mentésre. A "masodik hivatkozo rekord" beszúrásának elvileg meg sem kellene történnie mert már létezik.
A folyamat a következő képen kellene hogy kinézzen:
1. Valamikor a múltban keletkeztek vevő entitások, melyek perzisztálva is lettek.
2. Keletkezik egy új
Gepkocsi
entitás, mely
List<Vevocim> vevocimek;
listájához hozzá szeretnék adni
entity.getVevocimek().add(cim)
egy vevőcímet mely ekkor már létezik.
Tehát azt szeretném, hogy perzisztáláskor elmentse mind a
Gepkocsi
entitást a
GEPKOCSI
táblába, mind pedig a megfelelő iserteket beszúrja a
GEPKOCSI_VEVOCIM_OSSZERENDELO
táblába ami összerendeli a gépkocsit a vevő címekkel. Na ez nem történik meg,
Cannot add or update a child row: a foreign key constraint fails
hibával elszáll mert valószínűleg a MySQL még nem látja a
GEPKOCSI
táblában a rekordot és mivel egy foreign key constrait van rajta dob egy hátast.
Viszont ha a folyamat úgy történik, hogy először létrehozok egy a gépkocsi entitást, kitöltöm minden mezőjét, de nem adok a vevőcím listához egyetlen rekordot sem, kiadok egy persist utasítást, akkor elmenti, majd ehhez a mentett entitáshoz adok vevő címeket és újra elmentem, akkor már nem ad ilyen hibát, mert létezik adatbázis szinten is a
GEPKOCSI
táblában a rekord így a
GEPKOCSI_VEVOCIM_OSSZERENDELO
tábla constraintjei nem sérülnek.
A kérdés, hogy fel lehet e úgy annotálni ezt a Gepkocsi osztályt, hogy először mentse el magát a Gépkocsi osztályt és ha ez megtörtént csak utána perzisztálja a
@OneToMany private List<Vevocim> vevocimek;
kapcsolatot.
Vagy a JPA ezt nem tudja lekezleni és keressek más megoldást?
- A hozzászóláshoz be kell jelentkezni
DBn tudsz változtatni? Direkt van a onetomany külön kapcsoló táblába rakva?
--
blogom
- A hozzászóláshoz be kell jelentkezni
Hát ha nagyon muszáj akkor lehet. Mivel OneToMany kapcsolatról van szó, így a VEVOCIM táblába be lehet tenni egy ID_GEPKOCSI mezőt és akkor ki lehet hagyni a kapcsoló táblát.
Viszont ez egy kiegészítő modul, ami csak egy ügyfélnek kell, így lehet szerencsésebb a külön tábla, és az elején még ment a variálás, hogy egy címet csak egy gépkocsihoz lehessen rendelni vagy akár tömbhöz is, és megvan az esélye annak hogy előbb utóbb ManyToMany kapcsolat lesz ebből és lehet így fájdalommentesebb lesz a módosítás ha egyből kapcsoló táblával oldjuk meg.
- A hozzászóláshoz be kell jelentkezni
közben gép elé kerültem, s állítólag tud ilyet a JPA, amilyet te akarsz.
az insertek most milyen sorban mennek el?
a vevő-t hogy keresed elő?
jól sejtem, hogy ez lenne az első insert, ami elmegy, s elhal rajta minden?
--
blogom
- A hozzászóláshoz be kell jelentkezni
A frontend JSF. A vevő egy PrimeFaces autocomplet komponenssel kerül kiválasztásra.
A folyamat úgy néz ki, hogy rábök az új gépkocsi hozzáadása gombra, akkor megjelenik egy JSF page ahol ki tudja tölteni a rendszámot, forgalmi engedély számot, stb.
Ekkor a backend rétegben létrejön egy gépkocsi objektum.
Gepkocsi gepkocsi = new Gepkocsi();
A JSF oldal mezői ennek az objektumnak a megfelelő mezőire vannak állítva. A vevők már léteznek, lehet hónapokkal ezelőtt vitték fel őket és egy autocomplett komponens segítségével kerülnek kiválasztásra és a gepkocsi entitás vevocimek listájához vannak hozzáadva entity.getVevocimek().add(cim).
Az insertek a háttérben nem tudom milyen sorrendben mennek le.
Nekem az ideális az lenne ha először a GEPKOCSI tábla insertje futna le aztán a GEPKOCSI_VEVOCIM_OSSZERENDELO tábláé. A VEVOCIM táblába meg nem kellene insertálnia mert a vevőcím létezik és nem is módosul.
De most vagy az van, hogy előbb a GEPKOCSI_VEVOCIM_OSSZERENDELO táblába akar először insertálni aztán a GEPKOCSIBA, vagy A GEPKOCSIBA insertál utána a GEPKOCSI_VEVOCIM_OSSZERENDELO táblába csak akkor ezt nem értem ha egy tranzakción belül van a két insert akkor a GEPKOCSI_VEVOCIM_OSSZERENDELObe történő insertáláskor miért száll el hogy nem létezik a gépkocsiba az az ID?
- A hozzászóláshoz be kell jelentkezni
személy szerint gyűlölöm a JPA-t, de nem valami detached/attached magic lesz itt a háttérben?
hogy mikor kikerült a felületre a `cím` entitás, a JPA elveszít minden róla szóló infót - s a mentéskor nem tudja, hogy ez egy új entitás (amit tényleg insertelni kell), vagy egy meglévő...
Esetleg ennek a legalja alapján kipróbálnám az `em.merge(entity)` hívást.
De ez már tényleg a JPA olyan mélysége, amit, hacsak lehet, kerülök, sry.
--
blogom
- A hozzászóláshoz be kell jelentkezni
Na sikerült megtalálni mi a hiba.
Bekapcsoltam a logolást, hogy minden SQL utasítás írjon ki.
Abból láttam, hogy mikor a GEPKOCSI_VEVOCIM_OSSZERENDELO táblába insertál akkor a gépkocsi id-je 0, amit ugye az adatbázis kezelő (MySql) oszt, és mivel nem volt beállítva a gépkocsi entitás id mezőjére a @GeneratedValue(strategy=GenerationType.IDENTITY) annotácio így a child rekord inzertálásakor a gépkocsi entitás még nem kapta vissza az adatbázis által neki kiosztott azonosítót. De az annotáció megoldotta a problémát és így már szépen működik.
- A hozzászóláshoz be kell jelentkezni
uh, a primitív típus JPA entitásban dolog fel se tűnt.
nem nagyon error-prone ez?
--
blogom
- A hozzászóláshoz be kell jelentkezni
Annyira nem. 2 óra alatt megtaláltam azt, ami kiverte a szememet. :)
Tipikus fától az erdőt esete. :)
- A hozzászóláshoz be kell jelentkezni