29. ledna 2009

Porovnání jQuery a Prototype (odkaz)

Právě jsem si přečetl velmi zajímavé porovnání dvou populárních JavaScriptových knihoven Prototype a jQuery od Glenna Vanderburga pod názvem Why I still prefer Prototype to jQuery.

Text je psaný velmi seriózně, žádné pocity, subjektivní dojmy, ale holá fakta, proto je, dle mého soudu, hodně cenný a zajímavý.

Co se mě a JavaScriptu týče používám jQuery, ale je pravda, že převážně na manipulaci s DOMem, což je jeho silná stránka oproti Prototype. A navíc jsem měl kliku, že jsem nepotřeboval zatím nic, co se v jQuery jeví jako problematické. Každopádně se budu muset na Prototype podívat blížeji.

Hezké počteníčko

21. ledna 2009

Moduly pro Tapestry 5

Poslouchat Java Posse se vyplácí. Dozvěděl jsem se o projektu Chenille Kit, který nabízí poměrně velké množství komponent a služeb, které jsou založené na Tapestry IoC, tj. služby jsou použitelné i mimo Tapestry.

K dispozici jsou následující moduly:


  • Access - pro řízení přístupu ke stránkám aplikace

  • Google - přístup ke službácm Google (např. geocoding)

  • Hibernate - zjednodušení práce s Hibernate

  • Hivemind - zpřístupnění komponent z Hivemindu

  • Image - jednoduché změny velikosti obrázku

  • LDAP - služba na provádění dotazů do LDAPu

  • Lucene - služba na indexování a vyhledávání pomocí Lucene

  • Mail - služba na posílání mailů založená na Commons Mail

  • Quartz - služba na plánování založená na Quartzu

  • Reports - služba pro generování reportů pomocí JasperReports

  • Scripting - služba na plánování založená na BSF

  • Tapestry - zajímavé komponenty pro Tapestry

  • Templating - templatovací služba založena na Velocity a FreeMakeru

Hodně zajímavá snůška. Ukazuje se, že Tapestry má velmi dobře udělaný design a zakomponovat do něj další služby je triviální. A množství služeb se bude jenom rozšiřovat, takže se můžeme těšit na zelené zítřky s Tapestry.

Jak selektivně podle třídy mazat objekty z Hibernate session

Moje honba za LazyInitializationException byla úspěšná (musím se přiznat, že velkým pomocníkem mi byl Eclipse Memory Analyzer, který mi pomohl zjistit, kdo se na objekt odkazuje). Problém byl v tom, že jsem chtěl vymazat objekty ze Sessiony, ale protože jsem neznal jednotlivé instance, zavolal jsem metodu clear a znova zaasocioval to co jsem si myslel, že je důležité (a na něco jsem samozřejmě zapomněl).

V první fázi opravy chyby jsem tedy zaasocioval i další objekty a vše začalo fungovat. Pak mě ovšem postihla myšlenka, proč se mám já a můj kód, který by ideálně měl být na použitém ORM nezávislý, přizpůsobovat Hibernate. Tak jsem začal pátrat v javadoc, zda neexistuje nějaká možnost, jak zjistit jaké objekty jsou v Sessioně uložené. Pak by je stačilo selektivně odstranit pomocí metody evict.

Odpověď zní: existuje. Session má metodu getStatistics, která vrací SessionStatistics. Ten nám umožní zjistit jaké kolekce a jaké entity (včetně jejich identifikátoru) máme v sessioně uloženy. Takže stačí projít tyto údaje a v klidu vymazat co potřebujeme. Napsal jsem na to takovou drobnou třídu:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.engine.CollectionKey;
import org.hibernate.engine.EntityKey;
import org.hibernate.stat.SessionStatistics;

import cz.svt.util.Tuple;
import cz.svt.util.Tuple2;

/**
 * This class is capable to print the summary of Hibernate cache content and also is able to evict objects from cache.
 */
public class HibernateCachceEvicter {
  
/** The Hibernate session. */
  
protected Session session;

  
protected static final Log log = LogFactory.getLog(HibernateCachceEvicter.class);

  
/**
    * Creates the empty evicter.
    */
  
public HibernateCachceEvicter() {
     
super();
  
}

  
/**
    * Creates the evicter and sets the transaction.
    *
@param session The session
    */
  
public HibernateCachceEvicter(Session session) {
     
super();
      setSession
(session);
  
}

  
/**
    * Sets the Hibernate session.
    *
@param session The session
    */
  
public HibernateCachceEvicter setSession(Session session) {
     
this.session = session;
      return this;
  
}

  
/**
    * Prints the content of the Hibernate cache grouped by the collections role and entity name.
    *
@return The cache content, first is entities and second collections
    */
  
public Tuple2<TreeMap<String, Counter>, TreeMap<String, Counter>> logCacheContent() {
     
if ((session != null) && (log.isInfoEnabled())) {
        
try {
           
SessionStatistics statistics = session.getStatistics();
            TreeMap<String, Counter> entities =
new TreeMap<String, Counter>();
           
for (Object key : statistics.getEntityKeys())
              
getCounter(entities, ((EntityKey) key).getEntityName()).inc();
            TreeMap<String, Counter> collections =
new TreeMap<String, Counter>();
           
for (Object key : statistics.getCollectionKeys())
              
getCounter(collections, ((CollectionKey) key).getRole()).inc();
            log.info
("The hibernate cache contains " + statistics.getEntityCount() + " entities.");
           
for (String key : entities.keySet())
              
log.info("  " + key + "=" + entities.get(key).getValue());
            log.info
("The hibernate cache contains " + statistics.getCollectionCount() + " collections.");
           
for (String key : collections.keySet())
              
log.info("  " + key + "=" + collections.get(key).getValue());
           
return Tuple.newTuple(entities, collections);
        
} catch (HibernateException e) {
           
log.warn("Exception during walking through Hibernate cache to log its content", e);
        
}
      }
     
return Tuple.newTuple(null, null);
  
}

  
/**
    * Returns the counter from the map. If there is no such counter yet, creates new one and put it into map.
    *
@param map The map
    *
@param key The key
    *
@return The counter
    */
  
protected Counter getCounter(TreeMap<String, Counter> map, String key) {
     
Counter counter = map.get(key);
     
if (counter == null) map.put(key, counter = new Counter());
     
return counter;
  
}

  
/**
    * Evicts the object of the specified classes from Hibernate cache.
    *
@param classNames The array of the classNames to evict from cache
    */
  
public void evictFromCache(String... classNames) {
     
evictFromCache(new TreeSet<String>(Arrays.asList(classNames)));
  
}

  
/**
    * Evicts the object of the specified classes from Hibernate cache.
    *
@param classNames The sorted set of the classNames to evict from cache
    */
  
public void evictFromCache(SortedSet<String> classNames) {
     
if (session == null) return;
      log.info
("Cache before evicting:");
      TreeMap<String, Counter> entities = logCacheContent
().getV1();
     
try {
        
int entitiesToEvictCount = 0;
        
for (String key : entities.keySet())
           
if (classNames.contains(key)) entitiesToEvictCount += entities.get(key).getValue();
         List<Object> entitiesToEvict =
new ArrayList<Object>(entitiesToEvictCount);
        
for (Object key : session.getStatistics().getEntityKeys()) {
           
EntityKey eKey = (EntityKey) key;
           
if (classNames.contains(eKey.getEntityName()))
             
entitiesToEvict.add(session.load(eKey.getEntityName(), eKey.getIdentifier()));
        
}
        
for (Object entityToEvict : entitiesToEvict)
           
session.evict(entityToEvict);
         log.warn
("Evicted " + entitiesToEvictCount + "entities.");
     
} catch (HibernateException e) {
        
log.warn("Exception during evicting objects from cache", e);
     
}
     
log.info("Cache after evicting:");
      logCacheContent
();
  
}

  
/**
    * This class is counter.
    */
  
protected static class Counter {
     
/** The value of the counter. */
     
int value;

     
/**
       * Creates the counter a resets the value.
       */
     
public Counter() {
        
super();
        
value = 0;
     
}

     
/**
       * Increments the value of the counter.
       */
     
public void inc() {
        
value++;
     
}

     
/**
       * Returns the value.
       *
@return The actual counter value
       */
     
public int getValue() {
        
return value;
     
}

   }

}


Zajímavé je metoda evictFromCache, která provádí mazání objektů ze sessiony. Nejprve si pomocí logCacheContent vypíšu obsah cache (seskupený podle tříd) před mazáním. Pak si zjistím kolik instancí budu mazat (podle výstupu z vypsání obsahu sessiony) a vytvořím patřičně veliké pole. Následuje procházení sessiony a pokud narazím na třídu, kterou chci vymazat, podle identifikátoru instance ji natáhnu metodou load a vložím do pole. Teprve poté je mažu (byl jsem línej, zkoušet, zda by se to nějak nepralo, kdybych zároveň procházel obsah sessiony a zároveň z ní mazal). Na závěr si jenom pro porovnání udělám výpis obsahu po mazání.

Protože nechci mít v kódu odkaz na takovou ošklivou Hibernatí záležitost, udělal jsem si jednoduchý aspect, který mi na požadovaná místa dotlačí vymazání instancí patřičných tříd.

Předpokládám, že to již někdo řešil, ale pokud to někomu pomůže, pak jen berte inspiraci.

PS. Třídu Counter jsem v příkladu nechal jenom pro úplnost.

17. ledna 2009

Parametrizované testy v TestNG

Jak už jsem psal dříve, jako testovací framework používáme TestNG. A protože jsem se konečně dostal ke knize Next Generation Java Testing: TestNG and Advanced Concepts od autora TestNG, Cédrica Beusta, mohu se s vámi podělit o novinky, které jsem načerpal (každopádně ta kniha stojí za přečtení, zabývá se totiž nejen unitovým testováním).

Jak jsem již naznačil v minulém textu, hlavní výhodou, kterou vidím, jsou data providery. Proč se tedy tento článek nejmenuje data providery v TestNG? Protože jsem se dozvěděl i o jiné možnosti parametrizace testů v TestNG. Jedná se o factories (český název továrny mi nepřijde tak jasný), které jsou podobné parametrům v jUnitu 4. Takže jak to funguje (příklad je lepší než 1000 slov, opráším příklad z minulého článku):


public class SquareTest {

private Calculator calculator = new Calculator();
private int param;
private int result;

@Factory
public static Object[] create() {
return new Object[] { new SquareTest(0, 0),
new SquareTest(1, 1), new SquareTest(2, 4), new SquareTest(4, 16) };
}

public SquareTest(int param, int result) {
this.param = param;
this.result = result;
}

@Test
public void square() {
calculator.square(param);
assertEquals(result, calculator.getResult());
}

}
Na první pohled je rozdíl oproti jUnit velmi malý (pomineme-li míň anotací a jejich jiná jména). Nevracíme paramtery, ale již vytvořené instance tříd. Jaká je v tom výhoda? Factory metoda může vracet instance jakýchkoliv tříd, nejenom té ve které je definovaná. Tyto třídy mohou obsahovat nejenom testovací metody (označené anotací @Test) a i factory metody. Je to o hodně flexibilnější.

A proč TestNG podporuje jak factory metody tak data providery, když de facto realizují totéž? Protože pokud parametry využijeme u většiny testovacích metod v třídě, je pohodlnější je předat pomocí konstruktoru z factory metody. Data providery se hodí, pokud potřebujeme více skupin dat pro různé testovací metody. A samozřejmě nic nám nebrání obě věci kombinovat.

Na závěr jsem si nechal dotažení konceptu data provideru k dokonalosti. Co když chceme zavolat testovací metodu hodně krát, opravdu hodně hodně moc krát. Pak asi není ideální vytvořit všechny parametry dopředu, vložit je do dvourozměrného pole a vrátit z data provideru, to zblajzne moc paměti. Jak tedy na to? Data provider bude místo Object[][] vracet Iterator, jehož metoda next() vrátí jednorozměrné pole s hodnotami parametrů.

Pokud jsem vás stále nepřesvědčil, že TestNG stojí za zkoušku, pak to jistě zkusím zase příště.

14. ledna 2009

ORM mých snů - iBatis 3

Všechny velké zajímavé projekty aplikací, na kterých jsem se podílel jako ORM používali Hibernate. Přiznám se, že jsem byl tímto frameworkem zpočátku nadšen. Pak moje nadšení trochu ochablo, ale verze 3 zase přinesla vylepšení, která jsem kvitoval (především z hlediska mapování).

Postupem času, ale čím dál tím víc cítím, že Hibernate (a v podstatě jakýkoliv JPA framework) není to pravé ořechové. Proč?

Nelíbí se mi, že je velmi těžké se z DataTypu dostat na connection, což je potřeba např. pro uložení XML do DB přes datový typ SQLXML, který je nutno vytvářet pomocí metody createSQLXML objectu Connection. O tom, že tuto instanci musím po provedení dotazu zase uvolnit pomocí její metody free ani nemluvě.

Dále se mi nelíbí, že nemám plně pod kontrolou SQL dotazy (jsou generovány z HQL), tj. případná optimalizace dotazů není úplně triviální. Samozřejmě je možné si vyžádat od Hibernate spojení a provést dotaz ručně, ale metoda connection objektu Session je deprecated, tak nevím jak to bude v Hibernate 4.

Šíleně mě začíná obtěžovat cache natažených objektů, Session. Pokud provádím nějaké batchové operace, které manipulují s velkým množstvím objektů (desetisíce), pak je problém jak to celé výkonově optimalizovat. Je nutné průběžně dělat commit, session vyclearovat a znova zaasociovat všechny objekty, které budu potřebovat. Nejen je to pracné, ale velmi často dohledávám původce nějaké LazyInitializationException.

Ale co jiného, co lepšího. Vždy jsem pokukoval po iBatisu. Zkoušel jsem jej, ale něco mi vždy řeklo, že to nějak s Hibernate zmáknu (přeci jenom ten framework znám a málo co mě při práci s ním překvapí). Ale návrh na verzi 3, který se mi dostal pod ruku mě nadchl. Proč?

Šíleně se mi zalíbila myšlenka definice rozhraní, které se zove Mapper, ale de facto je to DAO. Implementaci vytvoří iBatis sám. Např:

public interface EmployeeMapper {
Employee getEmployee (int employeeId);
List listAllEmployees();
}
Navíc se případné anotace, jak mapovat budou umísťovat do tohoto rozhraní a ne do modelu (jsem absolutním nepřítelem anotování modelu anotacemi pro DAO vrstvu).

Dále se mi zalíbila představa generování dynamických SQL dotazů pomocí DSL v Javě. Obávám se, že to není správný přístup. SQL by mělo být mimo aplikaci, takže v XML. Ale myšlenka je to jistě zajímavá.

Teď už nezbývá nic jiného než se těšit jak to celé dopadne.

13. ledna 2009

Optimalizace výkonu databáze DB2 - db2top utility

Jak optimalizovat výkon databáze? Těžko. Jak optimalizovat výkon databáze DB2? Také těžko. Nebo ne?

Tak těď už to neplatí, alespoň si to myslím. Ještě jsem nově objevenou utilitku db2top nezkoušel v případě nouze, ale podle popisu a prvního vyzkoušení je hodně mocná.

Dříve jsem k monitoringu databáze využíval pár utilit operačního systému (v Linuxu např. ps, iostat) a pak řadu nástrojů databáze (např. monitory, snapshoty). db2top integruje všechny tyto schopnosti do jednoho programu.

Utilitky umí pracovat nejenom v interaktivním režimu, ale i v režimu záznamu a následného vyhodnocení. Dokonce je možné hodnoty exportovat do csv a následně importovat např. do Excelu (já používám OpenOffice) a vytvářet grafy.

Koho jsem nalákal rozhodně doporučuji přečíst si DB2 problem determination using db2top utility.

12. ledna 2009

Java 7 - máme se na co těšit

Máme zde nový rok 2009 a první téma v novém roce nemůže patřit ničemu jinému než nově přicházející Javě - tj. Javě 7. Dostalo se ke mně pár zajímavých informací, které oblažily mé srdíčko.

Za prvé: Java Modularity mimo jiné nahradí classpath (také proto, že nepodporuje více verzí jedné knihovny). Konečně se zbavíme classpath: skutečnosti, které přinášela jenom starosti a žádné slasti. Každopádně, kdo kdy řešil nějaký problém spojený classpath, především na serveru je to lahůdka, pak jistě zaplesá.

Za druhé: closures se odkládají. Další moudré rozhodnutí (představení closures vyvolalo velké množství debat). Vzniklo více návrhů a není jasné, který je nejlepší. Proto oddálení uvedení této featurky je rozumné, uvidíme zda je budeme v budoucnu potřebovat (v Javě 7 budou změny podporující dynamické jazyky, jejichž existence možná tuto potřebu zašlape do země).

Věřím, že vás tyto informace potěší alespoň tak, jak potěšili mě. A když to tak náhodnou zase nedopadne, pak se nebojme, chleba kvůli tomu dražší nebude. Rozhodně vám přeji vše nej... do nového roku.