čtvrtek 23. ledna 2014

Pattern: Práce se zdroji, které nakonec musíte uzavřít

Předpoklady

  • createEntityManager vytvoří entity manager nebo vyhodí výjimku, nikdy nevrátí null

Naivní verze

  EntityManager entityManager = null;
  try {
    entityManager = factory.createEntityManager();
    ...
  } finally {
    if (entityManager != null) {
      entityManager.close();
    }
  }
  • dvojí přiřazení 
  • možnost opakovaného přiřazení omylem
  • nepřehledný kód komplikuje čtení i úpravy
  • nejednoznačnost stavu proměnných
  • řádky a podmínky navíc

Dokonalá verze

  final EntityManager entityManager = factory.createEntityManager();
  try {
    ...
  } finally {
    entityManager.close();
  }
  • jediné přiřazení hodnoty
  • ve finally nikdy nebude entityManager null
  • nelze do proměnné přiřadit ani jinou hodnotu (ani omylem!)
  • pokud při create nastane chyba, k přiřazení nikdy nedojde a nedojde ani k uzavírání

Analýza

Není nic jednoduššího, než se poslouchat, klidně přemýšlejte nahlas:
  • "Potřebuji entity manager - je to zdroj, tudíž když ho už nepotřebuji, musím ho nakonec zavřít" (u dependency injection to platí taky, byť to udělá kontejner!)
  • "Pokud nastane chyba, nemůžu dělat nic, takže chybu propaguji ven".
Totéž samozřejmě platí pro soubory, JDBC konexe, JMS, atd ...

Co když ...

Co když potřebujete otevřít zdrojů víc najednou?

Nebuďte líní a udělejte si spec. třídu, wrapper, který buď otevře oba zdroje najednou, nebo při chybě již otevřený zavře. Bude se tudíž chovat zvnějšku atomicky, nikdy nebude ve stavu, kdy by mu jeden zdroj zůstával otevřený.
Přidáte mu také metodu close, která zavře oba zdroje opět atomicky. Nezapomeňte ale také na to, že při zavírání může také nastat výjimka - obvykle bych to řešil dokončením zavírání všech zdrojů a vyhozením runtime výjimky se seznamem chyb, který by měl ale vypovídat o tom, co se stalo. Druhá možnost je vyhodit runtime výjimku při první chybě - to si můžete dovolit, pokud víte, že výjimka při close je fatální a výjimečný problém, kdy má smysl nikoliv po sobě uklízet, ale všeho nechat a "splašit" někoho vně. Asi jako když utrhnete vodovodní baterii, nebudete pokračovat v mytí nádobí ...

Jak je to s kontejnerem a databázovými spojeními?

Jestliže používáte DI (např. anotace @PersistenceUnit nebo @Resource), měli byste dobře vědět, jak přesně se pak aplikace bude chovat.
Pokud používáte JTA, kontejner si pro entity manager vytvoří wrapper. Dokud nejste v transakci (což se řídí mj. anotacemi @TransactionAttribute, tím zda provádíte změny v databázi, konkrétní implementací kontejneru či JPA, a jejich nastavení), kontejner se může snažit zavírat JDBC spojení ihned, jakmile dokončí dotaz, nebo si jí naopak podrží během celého volání metody, a to aniž by se změnil persistence context.
Jakým způsobem přesně tudíž kontejner zdroje využívá, záleží opět na vás a vy si musíte rozmyslet, jaká varianta je pro vás optimální.

Další dotazy k věci? Sem s nimi!