Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > Egyperces - Olvasás közvetlenül írás után Spring Data JPA alatt

Szűrő megjelenítése

Egyperces - Olvasás közvetlenül írás után Spring Data JPA alatt

Egy bugot kényszerültem felvenni az időközben 1.0-s release-ét elérő Leaflethez, miszerint NullPointerException váltódik ki egy új blogbejegyzés létrehozásakor. A hiba már csak azért is érdekes volt, mert a bejegyzés maga létrejött az adatbázisban, azonban a bejegyzés tartalmát már nem tudta visszaadni a motor. Hamarosan kiderült, hogy mivel a tárolás és a létrehozott bejegyzés tartalmának visszaadása két explicit hívás a service réteg felé, a tranzakció nem sérült, így volt lehetséges a bejegyzés tárolása. Tehát az új bejegyzés visszakérése volt a hibás, mely a bejegyzés létrehozása során visszatérő azonosító alapján tudja felkérni azt - effektíve egy teljesen új adatbázis-kérés indul. Ez fontos tényező a problémát illetően, mert ez már önmagában megoldhatna bizonyos átmeneti hibákat - ezt az egyet kivéve.

Szóval mint rövidesen kiderült, a bejegyzéssel N:1 kapcsolatban álló felhasználó objektuma - az azonosítóját leszámítva - üres volt. Annak egy kötelező paramétere (a felhasználó role-ja, enum-ként) konvertálásra kerül felolvasáskor, és nyilván ez az üres paraméter okozta az NPE-t. Hangsúlyozom, ez már egy teljesen új adatbázis-kérés, így a teljes objektumnak betöltve kéne lennie, ráadásul mivel a felhasználó felolvasása EAGER módba van állítva, a teljes felhasználó objektumnak ott kellene lennie. De nem volt.

Először caching problémára gondoltam, ami azért lett volna furcsa, mert explicit cache még nincs az alkalmazásban. Gyors utánaolvasással kiderítettem, hogy a Hibernate-nek van egy beépített cache-e, de az ki van kapcsolva. Valahogy mégis úgy tűnt, forró nyomon járok - hiszen a tárolásra odaadott objektumban csak a felhasználó azonosítója volt, amit aztán a felolvasás is pontosan úgy adott vissza. Megjegyzem, rögtön utána egy explicit “get” kéréssel már helyesen és NPE-mentesen kaptam meg a bejegyzés tartalmát.

Aztán az jutott eszembe, hogy valószínűleg a “save” művelet önmagában nem elég, “flush”-ölni is kellene az adatokat - hiszen lehet, hogy még a commit nem történt meg az adatbázisban a rekordon, és így próbálom rögtön utána felolvasni. Sajnos ez a tippem sem jött be, a helyzet változatlan maradt. Jött nagyjából két órányi StackOverFlow bújás, amikor is belefutottam egy bejegyzésbe, ahol a JPA saját belső cachingjéről volt szó.

Gondoltam adok neki egy lehetőséget. Szükség volt az EntityManager instance-ra, és bár ennek használata JpaRepository-k esetén kicsit körülményes művelet, szerencsére a JpaRepository-k felett levő DAO rétegben könnyedén el tudtam érni. Az EntityManager-től el kell kérni az entitáshoz tartozó cache-t és nemes egyszerűséggel kiüríteni azt - mindezt rögtön az írás után. A hiba ezután eltűnt.

public <S extends T> S save(S entity) {

	if (entity instanceof SelfStatusAwareIdentifiableEntity) {
		((SelfStatusAwareIdentifiableEntity) entity).setCreated(new Date());
	}

	S savedEntity = jpaRepository.saveAndFlush(entity);
	jpaContext.getEntityManagerByManagedType(entity.getClass()).clear();

	return savedEntity;
}

A lényeg tehát, hogy a JPA egy némiképp egyszerű, belső cachinget is használ. Ez adott requesten belül hasznos, többször nem kéri el az adatbázistól ugyanazt az entitást. Azonban írási művelet esetén, mivel az entitást “kézzel” rakjuk általában össze, ha abból kimarad valami (pl. a kapcsolatok esetén a kapcsolódó entitás azonosítóján kívül minden), akkor a request idejére ez a hiányos entitás lesz a cached instance. Így ha rögtön az írás után olvasni is kell az újonnan létrejövő rekordot, előtte kézzel üríteni kell a JPA cache-t. Ellenkező esetben bosszúságos, több órányi kutakodás lesz a vége.

Kommentek

Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.