EJB 2.x – Entity Beans (BMP – vytvorenie entitnej triedy)

2. listopadu 2005

Už viete, že ak sa rozhodnete vo vašich entitných triedach využívať princíp BMP, budete si musieť zabezpečiť obsah volania jednotlivých callback metód vo vlastnej réžii. Vieme tiež, že ak klient zavolá metódu create nachádzajúcu sa v home rozhraní entitnej triedy, kontajner zavolá korešpondujúcu metódu ejbCreate, volanú nad inštanciou požadovanej entitnej triedy.

Inštancia nad ktorou je volaná spomínaná metóda ejbCreate je inštancia, ktorú kontajner vybral z poolu a asocioval ju s novo vytvoreným entitným objektom. Táto vaša metóda je zodpovedná za inicializáciu atribútov entitnej triedy, za ich vloženie do databázy a za vrátenie hodnoty primárneho kľúča. V článku EJB 2.x – Entity Beans (callback metódy a životný cyklus) som uviedol, že signatúra metódy ejbCreate musí mať presne ten istý zoznam parametrov, ako odpovedajúca metóda create home rozhrania entitnej triedy a jej návratový typ musí byť trieda primárneho kľúča.

Vo svete objektového návrhu aplikácií existuje jeden návrhový vzor, ktorý sa nazýva Adapter Pattern. Tento vzor je v našom prípade vhodné využiť na vytvorenie implementácie rozhrania EntityBean, ktoré bude zabezpečovať iba priradenie objektu EntityContext a zvyšok bude prázdna implementácia callback metód. Nazvime si túto implementáciu AbstractEntity. Tu je jej zdrojový kód:

AbstractEntity.java:

/**
* Title: AbstractEntity
* Description: Abstraktná entitná trieda implementujúca
* metódy setEntityContext a unsetEntityContext a
* poskytujúca prázdnu implementáciu ostatných metód
* deklarovaných v EntityBean.
*/

import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.RemoveException;
public abstract class AbstractEntity implements EntityBean {
 protected EntityContext ctx;
 public void ejbActivate() {}
 public void ejbLoad() {}
 public void ejbPassivate() {}
 public void ejbRemove() throws RemoveException {}
 public void ejbStore() {}
 public void setEntityContext(EntityContext newCtx) {
  ctx = newCtx;
 }
 public void unsetEntityContext() {
  ctx = null;
 }
}

V nasledujúcom výpise si ukážeme príklad metódy ejbCreateWithData(), ktorá je náprotivkom metódy createWithData() definovanej v home rozhraní EnglishAuctionHome. Ako si môžete všimnúť nižšie, naša entitná trieda EnglishAuctionBean rozširuje vyššie uvedenú abstraktnú triedu AbstractEntity. Keďže táto trieda predstavuje adaptér pre rozhranie EntityBean, už by nemalo byť nutné deklarovať, že entitná trieda implementuje rozhranie EntityBean. Napriek tomu niektoré vývojové nástroje nie sú schopné túto skutočnosť rozpoznať, preto je nutné explicitne deklarovať implementáciu spomínaného rozhrania.

Podotýkam, že nasledujúca ukážka je iba fragment entitnej triedy EnglishAuctionBean, preto sa vám ju nepodarí skompilovať.


public class EnglishAuctionBean extends AbstractEntity
                                      implements EntityBean {
 …
 public Integer ejbCreateWithData(String name,
                 String description) throws CreateException {
  // ak nebude dodané meno, vyhodíme výnimku
  if ((name == null) || (name.trim().length() == 0)) {
   throw new CreateException(„Aukcia musí mať názov!“);
  }
  Connection con = null;
  PreparedStatement stmt = null;
  try {
   // priradíme primárny kľúč a inicializačné parametre
   setId(computeNextPK(„auctionseq“));
   setName(name);
   setDescription(description);
   // stav aukcie nastavíme na nevybavený
   status = IAuctionStatus.AUCTION_PENDING;
   con = DSConnectionHelper.getConnection(„auctionSource“);
   /**
   * Vytvoríme prepared statement a vložíme novú entitu
   * do databázy (ostatné atribúty zatiaľ ponecháme na null)
   **/

   stmt = con.prepareStatement(
    „INSERT INTO auction (id, Name, Description, Status)
    VALUES (?,?,?,?)“);
   stmt.setInt(1, id.intValue());
   stmt.setString(2, name);
   stmt.setString(3, description);
   stmt.setString(4, status);
   int rowsInserted = stmt.executeUpdate();
   if (rowsInserted != 1) {
    throw new EJBException(
     „Nemôžem vložiť aukciu do databázy!“);
   }
   return getId();
  }
  catch (SQLException e) {
   throw new EJBException;
  }
  finally {
   DSConnectionHelper.cleanup(stmt, con);
  }
 }
}

Metóda ejbCreateWithData začína vykonaním nutnej kontroly a následným priradením inicializačných parametrov k inštancii beanu. Metóda getConnection triedy DSConnectionHelper, ktorú sme si vytvorili v minulej časti, vráti objekt pripojenia, ktorý využijeme na vytvorenie príkazu INSERT. Potom, čo je tento príkaz vykonaný, metóda vráti primárny kľúč priradený k entitnému objektu. Metóda cleanup pomocnej triedy DSConnectionHelper volaná vo finally bloku, zapuzdruje obsluhu výnimiek používanú všetkými callback metódami pri uzatváraní objektu Statement a databázového pripojenia.

Obsah tejto metódy si ukážeme nižšie. Samozrejme keďže aplikačný server manažuje tvorbu a uzatváranie databázových pripojení, v skutočnosti metóda cleanup() spojenie neukončí. Zavolanie tejto metódy iba spôsobí, že kontajner bude vedieť, že dané spojenie bolo uvoľnené a môže byť vrátené do connection poolu.

public class DSConnectionHelper {
 …
 public static void cleanup(Statement stmt, Connection con) {
  try {
   if (stmt != null) {
    stmt.close();
   }
   if (con != null) {
    con.close();
   }
  }
  catch (SQLException e) {
   throw new EJBException;
  }
 }
 …
}

Všimnite si, že metóda ejbCreateWithData volá aj metódu computeNextPK, s cieľom priradenia hodnoty primárneho kľúča. Každá tabuľka použitá v našom aukčnom systéme využíva primárny kľúč typu integer. Väčšina databázových systémov poskytuje možnosť automatického generovania sekvenčných čísiel, použitých ako unikátne primárne kľúče pri vkladaní nového riadku. Toto je celkom užitočná vec, pretože vás odbremeňuje od potreby vlastného generovania primárneho kľúča. Na druhej strane však môžu vzniknúť problémy pri prenose aplikácie na iný DB systém. Nie všetky totiž majú spomínanú funkcionalitu a tie, ktoré ju majú, môžu mať odlišný systém predávania takto generovaného čísla aplikácii.

Veľmi jednoduchým alternatívnym riešením je implementácia vašej vlastnej sekvenčnej tabuľky asociovanej s konkrétnou DB tabuľkou, ktorá vyžaduje použitie primárneho kľúča. Takáto tabuľka udržuje iba jednu samostatnú hodnotu, reprezentujúcu posledný primárny kľúč priradený tabuľke, s ktorou je asociovaná. Toto riešenie sme zvolili aj v našom prípade. Nasledujúci výpis ukazuje, ako môžete jednoducho udržiavať sekvenčnú tabuľku.

protected Integer computeNextPK(String tableName)
                                     throws SQLException {
 con = null;
 PreparedStatement stmt = null;
 ResultSet rs = null;
 try {
  con = DSConnectionHelper.getConnection(„auctionSource“);
  // update sekvenčnej hodnoty v tabuľke
  stmt = con.prepareStatement(“
       UPDATE “ + tableName + “ set next_id = next_id + 1″);
  if (stmt.executeUpdate() != 1) {
   throw new SQLException(„Chyba generovania PK!“);
  }
  stmt.close();
  // získame sekvenčnú hodnotu a použijeme ju ako primárny kľúč
  stmt = con.prepareStatement(“
                         SELECT next_id from “ + tableName);
  rs = stmt.executeQuery();
  boolean found = rs.next();
  if (found) {
   return new Integer(rs.getInt(„next_id“));
  }
  else {
   throw new SQLException(„Chyba generovania PK!“);
  }
 }
 finally {
  // zatvoríme spojenie
  DSConnectionHelper.cleanup(stmt, con);
 }
}

Okrem doteraz použitých zdrojových kódov, referencuje metóda ejbCreateWithData aj rozhranie IAuctionStatus. Toto rozhranie jednoducho definuje reťazce, použité v databáze na odlíšenie stavu, v ktorom sa aukcia práve nachádza.

/**
* Title: IAuctionStatus
* Description: Konštanty definujúce možné stavy aukcií
*/

public interface IAuctionStatus {
 public static final String AUCTION_PENDING = „Pending“;
 public static final String AUCTION_OPEN = „Open“;
 public static final String AUCTION_CANCELLED = „Cancelled“;
 public static final String AUCTION_CLOSED = „Closed“;
}

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *