Jpa OneToMany perzisztálási hiba

Fórumok

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.

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 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.

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?

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 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?

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

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.