Archive

Archive for June, 2007

Zend Framework i keszowanie zapytań do bazy danych

June 23rd, 2007 3 comments
Witam, Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład Zend Framework a w szczególności jego kod odpowiedzialny za bazy danych – Zend_Db (działający prawie jak ORM) przypomina w pewien sposób to, za co świat Javy pokochał Hibernate. Konsekwencją użycia ORM w projekcie jest to, że spada nam złożoność zapytań do bazy danych, natomiast zwiększa się ich ilość. Hibernate potrafi keszować obiekty. Zend Framework niestety nie. Jednak wczoraj tego bardzo potrzebowałem, renderowanie prostej strony WWW wymagało 500 zapytań do bazy, grrr, z czego 450 było powtórzeniem poprzednich zapytań. O ile keszowanie obiektów było by bardzo skomplikowane, to w miarę prosto można było keszować wyniki zapytań SQL w obrębie jednego żądania HTTP. Jak się do tego zabrać? Zauważyłem, że wszystkie zapytania do bazy danych produkowane przez Zend Framework w pewnym momencie trafią do metody query obiektu Zend_Db_Adapter_*, fajnie:). Naszą klasę keszującą trzeba odziedziczyć po tej klasie. Oczywiście można to zrobić na wiele innych sposobów, i bardzo ładnie wpasować w to wzorzec projektowy dekorator, ale ja nie miałem czasu. Modyfikujemy metodę query, tak by zapamiętywała swoje wyniki i trzymała w keszu. U mnie działa coś takiego (wstydził bym się tego kodu, gdyby nie to że robi coś na prawdę przydatnego):
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    $sql = $sql . ""; // a sprobuj no bez tego
    $hash = $this->_calculateHash($sql,$bind);
    // czy uda nam sie pobrac z kesza
    if($this->_isCacheEnabled) {
      if($this->_allowCache($sql,$bind)) {
        if(isset($this->_cachedResults[$hash])) {
          return $this->_cachedResults[$hash];  
        } 
      }
    }
    $res = parent::query($sql, $bind);
    // dopisanie do kesza
    if($this->_isCacheEnabled) {
      $this->_cachedResults[$hash] = $res;
    }
    return $res;
  }
  // pozwalam na keszowanie tylko zapytan rozpoczynajacych sie od select
  private function _allowCache($sql,$bind) {
    return preg_match("/^select/i",$sql));
  }
  private function _calculateHash($sql,$bind) {
    return md5($sql.var_export($bind,true));
  }
  [...]
}
Ten kod omal nie zadziała. Jednak trzeba zrobić jeszcze parę myków. Przeciążyć konstruktor naszego adaptera.
  public function __construct($config) {
    parent::__construct($config);
    $this->getConnection()->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('JakubiakDbStatement', array($this)));
  }
I dopisać taką oto koszmarną klasę:
class JakubiakDbStatement extends PDOStatement {
  $_cache = null;
  public function fetchAll($how, $class_name=null, $ctor_args=null) {
    if(!empty($this->_cache)) {
      return $this->_cache;
    }
    $this->_cache = parent::fetchAll($how);
    return $this->_cache;
  }
}
Ten kod, ma prawo nie działać i zawiera błędy. Jednak u mnie działa. I to dobrze działa. Zamiast 500 zapytań do bazy mam ich 50. Serwer jest mi za to wdzięczny, nie trzeba już oliwić wentylatorka od chłodzenia;). PS. do testów użyłem starej wersji Zend_Framework – 0.9.1.
Categories: ORM, PHP, PostgreSQL, Zend Framework Tags:

Prawdy o equals

June 23rd, 2007 10 comments
Witam, W języku Java, jest fajna metoda – equals(). Służy ona do porównywania, czy dwa obiekty są sobie równe. Czyli, na przykład obiekt Osoba implementuje metodę equals, i sprawdza czy imię i nazwisko tej osoby są takie same. Implementując metodę equals, należy pamiętać, że może z niej korzystać nie tylko Twój kod, ale także kod innych bibliotek do których przekazujesz obiekty, np.: Hibernate, JiBX, Axis. Implementując metodę equals w tworzonym właśnie obiekcie, należy pamiętać, że Java spodziewa się określonego działania tej metody. Dla metody equals określono 5 bardzo logicznych warunków. A więc:
  • metoda equals musi być zwrotna, czyli a.equals(a)==true.
  • metoda equals musi być symetryczna, czyli a.equals(b)==b.equals(a).
  • metoda equals musi być przechodnia, czyli jeżeli a.eqauls(b)==true i b.equals(c)==true to a.equals(c)==true. (Jednak gdy: a.equals(b)==false i b.equals(c)==false to a.equals(c) może być true)
  • metoda equals musi być konsekwenta, czyli gdy dwa razy (w różnych chwilach czasu) porównujemy te same obiekty, to wynik tego porównania powinien być taki sam.
  • obiekt jest nie równy null, czyli a.equals(null)==false dla każdego a nie będącego null
Wszystkie te warunki są tak samo ważne, jednak 3 pierwsze dają największe pole do popełnienia błędów. Błędne działanie metody equals, może być trudne do wykrycia i często będzie wyglądać jak błąd w innej bibliotece. Wyobraźmy sobie, że na przykład JiBX (taka biblioteka do serializacji do XMLa) podczas serializacji złożonej struktury danych zapisuje sobie gdzieś, jakie obiekty były już serializowany, tak by nie serializować dwa razy tego samego obiektu. Tymczasem metoda equals oszukuje, i JiBX próbuje 1000 razy marshalować ten sam obiekt. Programista zobaczy komunikat o braku pamięci, pomyśli – j… OpenSource – nic nie działa – tymczasem błąd może tkwić w jego kodzie w tak oczywistej metodzie equals. To oczywiście tylko hipoteza, ale tak może być. Proponuję pisać metodę equals na przykład tak.
public class Osoba {
  private String imie;
  private String nazwisko;

  public boolean equals(Object innyObiekt) {
    if (this == innyObiekt) return true;
    if (!( this.getClass().equals(innyObiekt.getClass()) )) return false;
    Osoba innaOsoba = (Osoba) innyObiekt;
    return (null == getImie() ? null == innaOsoba.getImie() : getImie()
        .equals(innaOsoba.getImie()))
        && (null == getNazwisko() ? null == innaOsoba.getNazwisko()
            : getNazwisko().equals(innaOsoba.getNazwisko()));
  }
Warto zapamiętać bardzo ładną konstrukcję: a==null ? b==null : a.equals(b) . Zaoszczędzi nam ona sporo pisania. Dla skomplikowanej metody equals należy pisać test, np. coś takiego:
public class OsobaTest {
  @Test
  public void testEqualsObject() {
    Osoba antek1 = new Osoba();
    antek1.setImie("Antek");
    assertTrue("Zwrotna",antek1.equals(antek1));
    
    Osoba ola = new Osoba();
    ola.setImie("Ola");
    assertFalse("Antek to nie Ola",antek1.equals(ola));
    
    Osoba antek2 = new Osoba();
    antek2.setImie("Antek");
    
    assertTrue("Antek to Antek2",antek1.equals(antek2));
    assertTrue("Symetryczna",antek1.equals(antek2)&&antek2.equals(antek1));
    
    Osoba antek3 = new Osoba();
    antek3.setImie("Antek");
    
    assertTrue("Antek to Antek",antek2.equals(antek3));
    assertTrue("Przechodnia",
        antek1.equals(antek2)==true&&antek2.equals(antek3)&&antek1.equals(antek3));
    
    assertFalse("Antek to nie null",antek1.equals(null));
    
  }
}
Jak widać, z prostą metodą equals może być dużo zabawy. Jednak to bardzo ważne, żeby jej nie zepsuć. Inaczej nasz obiekt, nie będzie działał prawidłowo w świecie Java. Na zakończenie dodam, że warto też poczytać o metodzie hashCode(). Pozdrawiam
Categories: Java Tags:

Collection vs List

June 15th, 2007 No comments
Postanowiłem, że napiszę kilka słów z podstaw języka Java. Dla początkującego w tym języku programisty (takiego jak ja) łatwo jest pomylić interfejsy: java.util.Collection i java.util.List. Otóż List jest wyspecjalizowaną kolekcją przechowującą obiekty w określonej kolejności. Collection nie gwarantuje kolejności elementów.
Categories: Java Tags:

Zend Framework 1.0.0 RC1 && Zend_Db_Statement

June 8th, 2007 No comments
Zainteresowała mnie dziś nowa wersja Zend Framework 1.0.0RC1 – i to był błąd. Ktoś wpadł na pomysł przepisania klasy Zend_DB_Statement, stworzono nową klasę: Zend_Db_Statement_Pdo i z nią właśnie jest problem. Jednak nie do końca z nią a bardziej z PDO. Wcześniej klasa Statement implementowała metodę bindParam jako wyrażenie regularne podmieniające stringi – o dziwo, to mi działało. Nowa implementacja bindParam deleguje metodę do obiektu PDO. Tymczasem ta metoda klasa PDO (w moim PHP 5.2.1) nie działa: SQLSTATE[HY093]: Invalid parameter number: no parameters were bound. Wniosek, na razie będę dalej korzystam z starej wersji biblioteki.
Categories: PHP, Zend Framework Tags:

Vim – usuwanie pustych linii z pliku

June 8th, 2007 4 comments
Czasami zachodzi potrzeba usunięcia pustych linii z pliku. Oczywiście można to zrobić w VIM:
:%g/^$/d
Categories: Vim Tags:

Kody państw

June 6th, 2007 1 comment
Witam, Prawie zawsze, gdy piszę aplikację zachodzi potrzeba pobrania listy państw. Jest norma ISO, która ma te dane. Polecam Wikipedię. Można tam znaleźć listę państw, a nawet flagi.
Categories: Programowanie Tags:

Hibernate i logowanie zapytań SQL wraz w wartościami parametrów

June 1st, 2007 No comments
Witam. Hibernate potrafi logować zapytania SQL. Można to skonfigurować na kilka sposobów. Ja wybrałem konfiguracje z log4j:
# logowanie zapytań SQL
log4j.logger.org.hibernate.SQL=DEBUG, R
log4j.additivity.org.hibernate.SQL=false
# logowanie wartości wstawianych do SQL
log4j.logger.org.hibernate.type=DEBUG
Pozdrawiam!
Categories: JPA, ORM Tags: