Van egy OneToMany reláció, pl. egy Post-Comments tankönyvi lépda:
public class Post implements Serializable {
private int id;
private List<Comment> comments;
@Id
@Column(name = "ID")
@TableGenerator(...)
@GeneratedValue(...)
public int getId() {
return id;
}
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Comment> getComments() {
return comments;
}
}
public class Comment implements Serializable {
private int id;
private int idPost;
private Post post;
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "ID_POST", insertable = false, updatable = false)
public int getIdPost() {
return idPost;
}
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "ID_POST", referencedColumnName = "ID")
public Post getPost() {
return post;
}
}
Az adatbázis kezelő alatta MySql és a Comment tábla ID mezője AUTOINCREMET.
Abban az esetben ha entityManager.merge(post) utasítással mentek egy Post entitást melynek a comments listájához adok egy Comment entitást, akkor szépen elment, viszont a Comment entitás ID mezője, melynek a MySql ad értéket az autoinc miatt nem frissül. Csak ha egy entityManager.refres() vagy bármilyen más módon újra lekérem. Mit kell még tenni hogy a merge hatására frissítse az entitást úgy hogy az id értékét visszakapja anélkül, hogy újra lekérdezném?
A JPA provider EclipseLink.
- 405 megtekintés
Hozzászólások
Ez most egy programozoi forum? Ebben mi az unix?
Egyebkent lehet nem kezeli ez az ORM a db altal adott dolgokat: uj ID, trigger altali modositasok, alapertekek amikrol az ORM nem tud. Szerintem kulon kell bekapcsolni valahol hogy mely entitasok sorainal legyen ReRead az DML-ek utan, alapbol nem olvasna vissza ha en irtam volna.
- A hozzászóláshoz be kell jelentkezni
Ez most egy programozoi forum? Ebben mi az unix?
Címlap > Fórumok > Programozás > Java
:)
- A hozzászóláshoz be kell jelentkezni
A #merge()
visszaadja a PersistenceContext
-ben lévő entitást, így nem kapod meg az id tartalmát?
entity = entityManager.merge(entity);
System.out.println(entity.getId());
- A hozzászóláshoz be kell jelentkezni
Sajnos ez a problémám, hogy nem. Nem voltam egyértelmű a probléma leírásakor.
- A hozzászóláshoz be kell jelentkezni
Ahogy az előttem szóló is írta, a merge visszatérési értéke fogja tartalmazni a perzisztens entitást, azaz annak lesz id-je.
Nem tudom, ki hogy van vele, de valahogy mindig is utáltam a JPA-t. A Hibernate API-ja sokkal érthetőbb és nekem jobban kézre áll.
Más: a cascade-ra figyelj, nem biztos, hogy jó ötlet mindkét irányból Cascade.ALL-t használni. Tipikusan a child oldalról nem szokás, mivel a child törlése nem szabadna, hogy a parent-et is törölje. Én általában csak legfeljebb az egyik irányból szoktam cascade-ot engedélyezni, még @OneToOne vagy @ManyToMany esetben is.
Más2: teljesen felesleges a Comment-be a getIdPost property. A getPost().getId() is jó (ugyanez JPQL-ben is)! Ha lazy fetch van, akkor sem fogja a teljes Posta objektumot felolvasni, mert itt amúgy is csak egy lazy proxy van, ami már eleve rendelkezik az ID-vel. Ha meg nincs lazy fetch, akkor pláne felesleges.
- A hozzászóláshoz be kell jelentkezni
A merge visszatérési értékének nincs id-je. Az adatbázisba szépen bekerül a szülő és a gyerek rekord is, de a merge visszatérési értékében nincs benne az id (értéke 0). De ha utána refresh()-el vagy ha lekérdezem bármilyen módon find(), vagy query akkor már megkapom az id értékét amit a MySql kiosztott neki.
- A hozzászóláshoz be kell jelentkezni
Én amúgy megnézném a naplófájlokban hogy egyáltalán milyen SQL-eket ad ki az EclipseLink. Ha a merge-kor tényleg nem történik select, akkor bizony szükséged lesz arra refreshre, mert lekérdezés nélkül nem fogja tudni az ORM, hogy mi lett az azonosító. A persistence.xml-ben ezt kell beállítani, hogy az EclipseLink kinaplózza az SQL utasításokat:
<property name="eclipselink.logging.level.sql" value="FINE"/>
- A hozzászóláshoz be kell jelentkezni
Írsz komplett példát arra, ahogy nálad nem működik? Ezek az entitások, de a lényeg nem az entitásokban van, hanem különösen a tranzakciós határoknál.
- A hozzászóláshoz be kell jelentkezni
CREATE TABLE POST
(
ID INTEGER AUTO_INCREMENT,
POST VARCHAR(100),
CONSTRAINT PK_POST PRIMARY KEY (ID)
);
CREATE TABLE COMMENT
(
ID INTEGER AUTO_INCREMENT,
ID_POST INTEGER,
COMMENT VARCHAR(100),
CONSTRAINT PK_COMMENT PRIMARY KEY (ID),
CONSTRAINT FK_COMMENT_01 FOREIGN KEY (ID_POST) REFERENCES POST (ID)
);
@Entity
public class Post {
private int id;
public String post;
public List<Comment> comments;
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Basic
@Column(name = "POST")
public String getPost() {
return post;
}
public void setPost(String post) {
this.post = post;
}
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
}
@Entity
public class Comment {
private int id;
public String comment;
public Post post;
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Basic
@Column(name = "COMMENT")
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
@ManyToOne
@JoinColumn(name = "ID_POST", referencedColumnName = "ID")
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
public Comment() {
}
public Comment(String comment, Post post) {
this.comment = comment;
this.post = post;
}
}
A kódrészlet amit az entitásokat létrehozza és perzisztálja:
EntityManager em = Persistence.createEntityManagerFactory(Constants.PERSISTENCE_UNIT_NAME).createEntityManager();
Post post = new Post();
post.setPost("blablabla");
post.setComments(new ArrayList<>());
post.getComments().add(new Comment("comment 1", post));
em.getTransaction().begin();
em.persist(post);
em.getTransaction().commit();
System.out.println(post.getComments().get(0).getId());
em.getTransaction().begin();
post.getComments().add(new Comment("comment 2", post));
post.getComments().add(new Comment("comment 3", post));
post = em.merge(post);
System.out.println(post.getComments().get(1).getId());
System.out.println(post.getComments().get(2).getId());
em.getTransaction().commit();
em.refresh(post);
System.out.println(post.getComments().get(1).getId());
System.out.println(post.getComments().get(2).getId());
A kimenet:
1
0
0
2
3
- A hozzászóláshoz be kell jelentkezni
Aham:
em.getTransaction().begin(); post.getComments().add(new Comment("comment 2", post)); post.getComments().add(new Comment("comment 3", post)); post = em.merge(post); System.out.println(post.getComments().get(1).getId()); System.out.println(post.getComments().get(2).getId()); em.getTransaction().commit(); em.refresh(post); System.out.println(post.getComments().get(1).getId()); System.out.println(post.getComments().get(2).getId());
Naszóval, röviden és elvileg: commit() vagy flush() vagy refresh() esetén lesznek id-k így ebben a formában. Amíg tranzakcióban vagy és az nem zárul le, addig nem lesznek id-k, ha azok adatbázis által autogenerated típusúak.
Ha így írod, akkor jobb lesz?
// ... post = em.merge(post); em.getTransaction().commit(); System.out.println(post.getComments().get(1).getId()); System.out.println(post.getComments().get(2).getId()); // ...
- A hozzászóláshoz be kell jelentkezni
WTF? Már miért ne lennének id-k?
Legfeljebb annyi lehet, hogy a programozó által létrehozott ojjektumokat nem cseréli le menedzselt entitásokra.
- A hozzászóláshoz be kell jelentkezni
Amennyire rémlik (kb. 3-4 éve nem használtam JPA-t), az EntityManager még nem kezeli az ID-t, mert még nem zárult le a tranzakció, ahhoz vagy lezárt tranzakció, refresh() vagy flush() kell.
- A hozzászóláshoz be kell jelentkezni
Hm ..., így visszakaptam az id-t ...
- A hozzászóláshoz be kell jelentkezni
Gondolom mert a commit is csinál refersh-t, nem a commit az érdekes.
Én mondjuk eleve fordítva csinálnám: vagyis nem a szülő mezőjét birizgálnám, hanem a gyereket szúrnám be - azon beállítva a szülőt.
:)
- A hozzászóláshoz be kell jelentkezni
Ezt most nem igazán értem. Írnál rá egy példát?
- A hozzászóláshoz be kell jelentkezni
1.csinál postot üres comment mezővel
2. csinál commentet, beállítva post mezőt
- A hozzászóláshoz be kell jelentkezni
Gondolom mert a commit is csinál refersh-t, nem a commit az érdekes.
Ennél azért komplexebb a tranzakció kezelése, arra kell gondolni, hogy egy JavaEE környezetben tipikusan elosztott kétfázisú tranzakciók vannak, amíg nem zárul le a tranzakció, addig nem kapsz vissza olyan azonosítókat, amelyek később érvénytelenek lennének, ha beüt valami baj. Például ebben az esetben létrehozod a struktúrát az EntityManager oldalán, lekérdezed az azonosítót, megy a commit(), elhasal és nálad meg lennének olyan azonosítók, amelyek amúgy nem léteznek az adatbázisban. Szóval emlékeim szerint ez by design ilyen, de tényleg régen volt dolgom JPA projekttel.
Én mondjuk eleve fordítva csinálnám: vagyis nem a szülő mezőjét birizgálnám, hanem a gyereket szúrnám be - azon beállítva a szülőt.
Ezért van a "cascade", hogy ezt megoldja helyetted. És tranzakcióban hiába is szúrod be, elvileg akkor se kapsz vissza id-t, amíg a tranzakció le nem zárul - vagy nem kérsz implicit szinkronizálást flush() vagy refresh() használatával, ami antipattern.
- A hozzászóláshoz be kell jelentkezni
Jájj...Egy darab tranzakción belül "látszanak" az abban korábban elvégzett műveletek eredményei.
- A hozzászóláshoz be kell jelentkezni
lásd még különböző "transaction isolation level" konfigurációs lehetőségek.
De amúgy igen.
zászló, zászló, szív
- A hozzászóláshoz be kell jelentkezni
Hja, elkerülte a figyelmem az AUTOINCREMENT :)
Az ok a következő: Mivel az ID-et az adatbázis motor generálja, ezért a JPA réteg nem kapja meg addig, amíg nem beszélgetett vele. Márpedig a JPA réteg (alapértelmezetten) nem beszélget addig a DB motorral, amíg nem feltétlen szükséges, késlelteti a parancsok kiküldését. Ennek teljesítmény oka van elsősorban, hogy objektum-orientált módon tudd manipulálni az objektumaidat, és utána hatékonyan lehessen ezt SQL-re fordítani (ami teljesen más világ). Ha szükséged van tranzakció közepén is az ID-ekre, akkor explicit flush-t kell hívni.
Olvass utána annak, hogyan működik a flushing és a session cache (EntityManager) működése.
Ha van választásod a JPA implementáció kiválasztásában, akkor inkább a Hibernate-et ajánlanám, nagyon jól összerakott szoftver jó dokumentációval.
- A hozzászóláshoz be kell jelentkezni
Ha van választásod a JPA implementáció kiválasztásában, akkor inkább a Hibernate-et ajánlanám, nagyon jól összerakott szoftver jó dokumentációval.
Ízlések és pofonok, én mindig is utáltam a Hibernate megoldásait, mint zsebben a száraz szart. :D
- A hozzászóláshoz be kell jelentkezni
Nekem pedig az ElcipseLink és a JPA-ról jut eszmebe Zuzu Petals... A Hibernate saját API-ját jobban szeretem. Ráadásul sokkal érettebb is az egész, meglepően kevés bugba futottunk bele az évek során, ellenben az EclipseLink-el, ami azért okozott izgalmas pillanatokat.
- A hozzászóláshoz be kell jelentkezni
Mondom, ízlések és pofonok, én nagyon nem szeretem a Hibernate API-t, ami időnként kilóg a JPA alól, az EclipseLink-et pedig még a TopLink korából ismerem. Szóval ez egyszerűen attól függ, hogy ki milyen környezetben szocializálódott.
- A hozzászóláshoz be kell jelentkezni
Így már teljesen érthető a dolog.
Választási lehetőségem van az implementáció kiválasztásában, csak kérdés mennyire fájdalmas a migrálás EclipseLink-ről Hibernate-re.
Egyszer volt egy halovány próbálkozás, hogy a persitence.xml-ben átírtam a
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
opciót a Hbernate-re
de elkezdett panaszkodni arra, hogy én a gettereknél használtam az annaotációkat, ő meg valamiért azt szerette csak ha a mező volt annotálva. Túl sok osztályt kellet volna átírni emiatt és annak sem volt időm akkor utánaolvasni hogy ez most miért van így, így ott el is halt a dolog. De egyre inkább előtérbe kerül megint hogy át kellene állni.
- A hozzászóláshoz be kell jelentkezni
de elkezdett panaszkodni arra, hogy én a gettereknél használtam az annaotációkat
Ez új, ilyen gondom még sosem volt, mármint hogy panaszkodik...
- A hozzászóláshoz be kell jelentkezni
Szerintem össze van kutyulva a gyerek - szülő hivatkozás. Nézz át még egyszer valami példát, doksit, hogy ne értsd félre a használatát!
pl. a Commentben az idpost mező nem kell, annotációba nem is az a mezőnév kell, hanem a szülő, a post id mező neve (ID).
- A hozzászóláshoz be kell jelentkezni