Jiří Hradil blog

o software


Hibernate Search – fulltext nad Hibernate ORM

Pokud chceme do naší Java aplikace integrovat fulltextové vyhledávání, dříve či později skončíme u Apache Lucene, frameworku, který je pro vyhledávání v Javě v podstatě standardem a synonymem. Pokud se rozhodneme používat čistý Lucene, musíme mj. řešit:

  • mapování doménových objektů do Lucene (v Lucene jsou všechny atributy obyčejné řetězce)
  • mapování výsledků vyhledávání zpátky do doménových objektů
  • transakce
  • otevírání a zavírání indexu
  • zamykání indexu, v 1 okamžiku může do indexu zapisovat pouze 1 proces
  • škálování fulltextu přes více serverů

Jestliže se však držíme zavedených řešení a používáme Hibernate (ať už Hibernate Core nebo Hibernate jako JPA providera), můžeme využít rozšíření Hibernate Search.
Hibernate Search je projekt, který Hibernate Core doplňuje o fulltextové prohledávání persistovaných objektů a funguje jako fasáda nad Lucene.

Hibernate Search nabízí:

  • API nad fulltextem podobné JPA
  • odstínění od low-level objektů Lucene-nestaráme se vůbec o otevření/uzavření indexu, o zamykání indexu 1 procesem, atd.
  • fulltextové vyhledávání nad persistovanými objekty pomocí Lucene tříd a objektů
  • automatický převod objektů do indexu a zpět (pokud nevyhovuje/nepostačuje, lze napsat převodníky)
  • automatickou synchronizaci indexu s doménovými objekty
  • reindexaci již persistovaných objektů
  • výsledky vyhledávání jsou kompletní objekty, Hibernate Search vrátí na dotaz seznam vyhovujících ID a Hibernate pak načte z db objekty s těmito ID
  • transakční chování – zápis do fulltext indexu se provádí až nakonec po commitu změn do db
  • škálování jako master/slave pomocí JMS
  • jednoduchost, pokud známe Lucene a Hibernate, můžeme jej začít používat okamžitě.

Ukázková aplikace

Pro praktickou ukázku integrujeme fulltextové vyhledávání do jednoduché webové aplikace pro správu firem (klasická create-read-update aplikace).

Použitý stack

  • Hibernate Entity Manager 3.4.0.GA - persistence
  • Hibernate Search 3.1.0.GA – fulltext
  • Apache Maven 2.0.9 – project management
  • Apache Wicket 1.3.5 – prezentační vrstva
  • Spring 2.5.5 – lepidlo
  • PostgreSQL 8.2-relační databáze

Poznámka k následujícím zdrojovým kódům: uvádím pouze soubory, které se bezprostředně týkají Hibernate Search, kompletní zdrojáky aplikace jsou na konci příspěvku.

Závislosti

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>
 <groupId>org.hradil</groupId>
 <artifactId>HibernateSearchSimpleApplication</artifactId>
<packaging>war</packaging>
 <version>1.0</version>
 <name>Hibernate Search Simple Application</name>

 <dependencies>

 <!--  wicket -->
 <dependency>
 <groupId>org.apache.wicket</groupId>
 <artifactId>wicket</artifactId>
 <version>1.3.5</version>
 </dependency>

 <!-- wicket+spring -->
 <dependency>
 <groupId>org.apache.wicket</groupId>
 <artifactId>wicket-spring-annot</artifactId>
 <version>1.3.5</version>
 </dependency>

 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-entitymanager</artifactId>
 <version>3.4.0.GA</version>
 </dependency>

 <dependency>
 <artifactId>hibernate-annotations</artifactId>
 <groupId>org.hibernate</groupId>
 <version>3.4.0.GA</version>
 </dependency>

 <!-- Hibernate Search -->
 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-search</artifactId>
 <version>3.1.0.GA</version>
 </dependency>

 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring</artifactId>
 <version>2.5.5</version>
 <exclusions>
 <exclusion>
 <groupId>commons-logging</groupId>
 <artifactId>commons-logging</artifactId>
 </exclusion>
 </exclusions>
 </dependency>

 <!-- LOGGING DEPENDENCIES - LOG4J -->
 <dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-api</artifactId>
 <version>1.5.2</version>
 </dependency>

 <dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-log4j12</artifactId>
 <version>1.5.2</version>
 </dependency>

 <!-- work around for jetty commons logging issue -->
 <dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>jcl-over-slf4j</artifactId>
 <version>1.5.2</version>
 </dependency>

 <dependency>
 <groupId>c3p0</groupId>
 <artifactId>c3p0</artifactId>
 <version>0.9.1.2</version>
 </dependency>

 <dependency>
 <groupId>postgresql</groupId>
 <artifactId>postgresql</artifactId>
 <version>8.2-507.jdbc3</version>
 <scope>compile</scope>
 </dependency>

 <!--  JUNIT DEPENDENCY FOR TESTING -->
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>3.8.2</version>
 <scope>test</scope>
 </dependency>

 <!--  JETTY DEPENDENCIES FOR TESTING  -->

 <dependency>
 <groupId>org.mortbay.jetty</groupId>
 <artifactId>jetty</artifactId>
 <version>6.1.4</version>
 <scope>provided</scope>
 </dependency>

 </dependencies>

 <build>
 <resources>
 <resource>
 <directory>src/main/resources</directory>
 </resource>
 <resource>
 <directory>src/main/java</directory>
 <includes>
 <include>**</include>
 </includes>
 <excludes>
 <exclude>**/*.java</exclude>
 </excludes>
 </resource>
 </resources>
 <testResources>
 <testResource>
 <directory>src/test/java</directory>
 <includes>
 <include>**</include>
 </includes>
 <excludes>
 <exclude>**/*.java</exclude>
 </excludes>
 </testResource>
 </testResources>
<plugins>
<plugin>
 <groupId>org.mortbay.jetty</groupId>
 <artifactId>maven-jetty-plugin</artifactId>

 <configuration>
 <scanIntervalSeconds>5</scanIntervalSeconds>
 <contextPath>/</contextPath>
 </configuration>

 </plugin>
<plugin>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>2.0.2</version>
 <configuration>
 <source>1.6</source>
 <target>1.6</target>
 </configuration>
 </plugin>

 </plugins>
 </build>

</project>

Konfigurace

applicationContext-jpa.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
 >

 <description>
 Konfigurace kontextu pro JPA, vcetne datovych zdroju a transakcnich manazeru.
 </description>

 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
 <description>
 Datovy zdroj pro persistentni vrstvu. Obsahuje udaje o pripojeni k databazi.
 </description>
<property name="driverClass" value="org.postgresql.Driver"/>
<property name="jdbcUrl" value="jdbc:postgresql://localhost/HibernateSearchSimple"/>
<property name="user" value="postgres"/>
<property name="password" value=""/>
 </bean>

 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 <description>
 Tovarna pro manazer entit. Je pouzita trida LocalContainerEntityManagerFactoryBean,
 ktera je doporucena pro produkcni nasazeni JPA.
 Viz. http://static.springframework.org/spring/docs/2.5.x/reference/orm.html
 </description>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
 <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="generateDdl" value="false"/>
 </bean>
 </property>

 <!-- nastaveni JPA a Hibernate Search -->
<property name="jpaProperties">

 <value>

 # konfigurace JPA
 hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 hibernate.hbm2ddl.auto=validate

 # debugging / logging
 hibernate.show_sql=true
 hibernate.format_sql=true
 hibernate.use_sql_comments=true

 # konfigurace Hibernate Search
 # kde bude ulozen Lucene index
 hibernate.search.default.indexBase=/tmp/index

 </value>

 </property>
 </bean>

 <tx:annotation-driven transaction-manager="transactionManager"/>

 <!-- transakcni manazer -->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
 </bean>

</beans>

Entita

Company.java

package org.hradil.search.entity;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;

/**
 * Entita reprezentuje firmu.
 * Firmu lze ve fulltextu vyhledat podle id, name a regNo.
 *
 * @author jirka@hradil.org
 */
@Entity
@Table(name = "company")
@Indexed //tridu budeme chtit indexovat ve fulltextu
public class Company implements Serializable {

 private static final long serialVersionUID = 1216348069826762176L;

 @Id
 @Column(name = "id")
 @SequenceGenerator(name = "company_id_seq", sequenceName = "company_id_seq")
 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "company_id_seq")
 @DocumentId //primarni klic objektu ve fulltextu, zaroven podle nej muzeme vyhledavat
 private int id;

 @Column
 @Field //ve fulltextu chceme hledat firmu podle nazvu
 private String name;

 @Column
 @Field //ve fulltextu chceme hledat firmu podle IC
 private String regNo;

 /**
 * Vytvori novou firmu.
 */
 public Company() {
 }

 /**
 * Vytvori novou firmu a predvyplni atributy.
 * @param name nazev firmy
 * @param regNo IC
 */
 public Company(final String name, final String regNo) {
 this.name = name;
 this.regNo = regNo;
 }

 /**
 * Vrati id firmy
 * @return id
 */
 public int getId() {
 return id;
 }

 /**
 * Nastavi id firmy
 * @param id id
 */
 public void setId(int id) {
 this.id = id;
 }

 /**
 * Vrati nazev firmy.
 * @return nazev
 */
 public String getName() {
 return name;
 }

 /**
 * Nastavi nazev firmy.
 * @param name nazev
 */
 public void setName(String name) {
 this.name = name;
 }

 /**
 * Vrati IC firmy.
 * @return IC
 */
 public String getRegNo() {
 return regNo;
 }

 /**
 * Nastavi IC firmy.
 * @param regNo IC
 */
 public void setRegNo(String regNo) {
 this.regNo = regNo;
 }
}

Servisní vrstva - uložení, úprava, vyhledání

CompanyServiceImpl.java

package org.hradil.search.service;

import org.hradil.search.entity.Company;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Implementace sluzby pro firmu.
 *
 * @author jirka@hradil.org
 */
@Transactional(propagation = Propagation.REQUIRED)
@Service
public class CompanyServiceImpl implements CompanyService {

 @PersistenceContext
 private EntityManager em;

 /**
 * {@inheritDoc}
 */
 @Override
 public void addCompany(Company newCompany) {
 em.persist(newCompany);
 }

 /**
 * {@inheritDoc}
 */
 @Override
 @SuppressWarnings("unchecked")
 @Transactional(readOnly = true)
 public List<Company> findCompanyBy(final String fulltextQuery) {

 Assert.notNull(fulltextQuery, "retezec pro vyhledavani nesmi byt null!");

 //vezmeme instanci manazeru fulltextu
 FullTextEntityManager ftEm = Search.getFullTextEntityManager(em);

 //vytvorime parser pro prohledavane atributy firmy, pouzijeme standardni analyzer
 QueryParser parser = new MultiFieldQueryParser(new String[]{"id", "name", "regNo"}, new StandardAnalyzer());

 //vytvorime dotaz do Lucene
 org.apache.lucene.search.Query luceneQuery;

 //pokud po zruseni vsech bilych znaku a hvezdicek zustane jen prazdny retezec, pak vracime vsechny zaznamy
 //napr. dotaz "**** * **  *" bude vyhodnocen tak, ze chceme prohledavat vsechny firmy
 if (StringUtils.trimAllWhitespace(StringUtils.deleteAny(fulltextQuery, "*")).isEmpty()) {
 luceneQuery = new MatchAllDocsQuery();
 } else { //byl zadan retezec, vyhledavame
 try {
 luceneQuery = parser.parse(fulltextQuery); //zparsujeme predany dotaz pomoci parseru Lucene
 } catch (ParseException e) { //neplatny dotaz, prekonverujeme na runtime vyjimku, nemusime zachytavat
 throw new RuntimeException("Neplatny dotaz do fulltextu: " + fulltextQuery, e);
 }
 }

 //vytvorime normalni JPA dotaz, ale pres rozhrani fulltextu
 FullTextQuery query = ftEm.createFullTextQuery(
 luceneQuery,
 Company.class);

 //tridime dle relevance DESC, pote dle id DESC
 SortField[] sortFields = new SortField[2];
 sortFields[0] = SortField.FIELD_SCORE; //relevance, default DESC
 sortFields[1] = new SortField("id", SortField.INT, true); //id, je to intener, DESC=true
 Sort sort = new Sort(sortFields);

 //pridame trideni do dotazu
 query.setSort(sort);

 //a vratime rovnou vyhovujici seznam firem z ORM
 return query.getResultList();

 }

 /**
 * {@inheritDoc}
 */
 @Override
 @SuppressWarnings("unchecked")
 @Transactional(readOnly = true)
 public Company findCompanyBy(int id) {
 return em.find(Company.class, id);
 }

 @Override
 @Transactional(readOnly = false)
 public void updateCompany(Company company) {
 em.merge(company);
 }
}

Zdrojové kódy aplikace

hibernatesearchsimple

Konfiguraci a rozjetí v clusteru si ukážeme v některém dalším příspěvku.

Publikoval Jiří Hradil • 10.05.2009 v 22:05 • pod kategorií hibernatesearch1 komentář

IBM kupuje Sun? Co bude s Javou?

Že je Sun na prodej, je všeobecně známá informace. Stejně tak i varianta, že Sun bude koupen IBM.

Dopady na některé technologie Sunu v případě, že kupcem bude IBM, mohou být následující:

Servery a zálohovací řešení (HW) - IBM měl poslední rok podíl na trhu serverů 31.4%, největší rival HP 29.5%, zatímco Sun 10.6% (zdroj: The Wall Street Journal: IBM in Talks to Buy Sun in Bid to Add to Web Heft ). IBM by si připsal podíl Sunu na trhu a získal by jednoznačný náskok. Servery by se prodávaly pod značkou IBM, dělit produktovou řadu je zbytečnou investicí do marketingu. Sbohem SunFire a SunStorage, zákazníkům je to jedno, uvnitř je to stejně všechno na jedno brdo.

Aplikační servery - Sun má Glassfish Enterprise Server (dříve Sun Application Server),  postavený na open-source Glassfish Application Server, IBM má WebSphere Application Server (WAS). Tříštit síly jak vývojářů, tak technologie nemá z pohledu IBM smysl. Předpokládám, že IBM se pokusí zákazníky přemigrovat na WAS  a open-source Glassfish ponechá svému osudu. Pokud nebude existovat komerční podpora pro Glassfish, tento server z dlouhodobého pohledu zanikne, protože svěřit jej (pouze) do rukou komunity znamená pomalu jej odsoudit k smrti. Velcí, ani normální zákazníci si Glassfish bez podpory nenaimplementují. Vývojáři, používající Glassfish pro Javu, přemigrují na jiný aplikační server. Pokud se drží standardů, změn by nemělo být moc (až na nějaké ty špeky, viď Dagi :) ).

Vývojové nástroje - Sun má NetBeans, IBM má mj.  IBM Websphere Studio Application Developer, který jede nad Eclipse. Z mého pohledu nemá NetBeans nic revolučního, co by Eclipse chybělo a domnívám se, že vývoj NetBeans bude pozastaven. Pro vývojáře, kteří používají NetBeans to bude znamenat několik dnů zkoušení jiného IDE, ale dopad na jejich vývoj bude minimální.

Databáze - Sun koupil MySQL a vlastní tak nejpopulárnější open-source databázi na světě, IBM má DB2. Jakkoli může být DB2 vnímána jako “enterprise” databáze, dnešním trendem je za základní software (databáze nevyjímaje) neplatit. MySQL nezanikne, není to soupeř DB2, IBM bude MySQL brát spíše jako “vstupní” databázi pro menší projekty, či firmy, které chtějí ušetřit (a kdo nechce).  A samozřejmě nabídne k MySQL, jak jinak, placenou podporu.

Java (platforma) - nejdůležitější nakonec. IBM má svou Javu, IBM Developer Kit, který prochází TCK a je tak ověřeno, že splňuje specifikace JSR, tedy měla by to být plnohodnotná, kompatibilní Java. Javu od IBM jsem nikdy nezkoušel, držel jsem se vždy Javy od Sunu, takže praktické zkušenosti rád přenechám zasvěcenějším. V budoucnu tak bude jen jedna Java a pár další, naprosto nevýrazných a nepoužívaných komunitních implementací. Udržovat a rozšiřovat jazyk a platformu, jako je Java, která je snad jediným konkurentem Microsoftích technologií, si může dovolit jen velká společnost a IBM by se toho mohla ujmout dobře.

O Javu jako platformu strach nemám. Tato technologie je natolik používaná a zaběhnutá, že její pád je v krátkodobém a střednědobém období nepravděpodobný. Alternativ k Javě moc není, napadá mě Microsoftí .NET Framework, případně multiplatformní Mono. Hromadný přepis již existujícího Java software je nemožný, existující projekty musí minimálně dožít a ty nové se stejně staví v aktuálních technologiích. A tam už je jedno, jestli je to Java verze 1.6,  1.7 nebo třeba .NET, či jiný, lesklejší a radostnější jazyk.

Publikoval Jiří Hradil • 31.03.2009 v 23:03 • pod kategorií javaŽádné komentáře

Přednáška Apache Wicket na CZJUG

V rámci březnového setkání české Java User Group (CZJUG) na téma “webové frameworky“  jsem připravil a odprezentoval přednášku o webovém frameworku Apache Wicket.

Abstrakt:

Cílem prezentace je uvedení frameworku Apache Wicket pro tvorbu web aplikací, založených na platformě Java. Apache Wicket nabízí srozumitelný komponentový model, umožňující tvořit aplikaci pomocí běžných, http protokolem nezatížených javovských tříd, podporuje jednoduchou tvorbu prezentační části pomocí standardního HTML a přebírá odpovědnost za řízení konverzace mezi klientem a serverem. Wicket se snaží vycházet ze známých, ověřených principů a nenutí vývojáře učit se novou syntaxi, kterou jinde nelze použít. Prezentace je ve znamení jednoduchosti, která se prolíná celým frameworkem a ukazuje, že pro tvorbu komplexních web aplikací je Apache Wicket správnou volbou.

Průběh přednášky byl z mého pohledu velmi zajímavý a příjemně mě překvapily velmi dobře mířené dotazy od aktivních posluchačů. Doufám, že jsem o Wicket vzbudil zájem, který si tento framework určitě zaslouží.

Prezentace v PDF

Kompletní zdroje k prezentaci včetně zdrojových kódů

Publikoval Jiří Hradil • 27.03.2009 v 23:03 • pod kategorií wicketŽádné komentáře

EJB - potřebujeme střílet vrabce “kanónem”?

Tímto chci všem zůčastněným poděkovat za podnětné připomínky k mým EJB příspěvkům.
Rád bych reagoval především na jAbLoK spot “EJB 3.0 aneb Proč že tu mrtvolu stále resuscitují?“od Pavla Kolešnikova, který jsem pochopil jako vyčítání komplikovanosti specifikace EJB 2.x pro jejich nutná rozhraní, záznam v DD, či vyhazovaní vyjímek. Pavel staví rovněž otázku, zda jsou EJB vhodné pro triviální webové aplikace.
Ale co když nechceme psát “jednoduchou” webovou aplikaci, ale skutečně systém, který musí být škálovatelný a robustní, kde musíme používat clustering a load balancing a kde uvítáme, že se pro vyžadovanou spolehlivost a složitost rádi soustředíme hlavně na business implementaci?

EJB přece ví a počítají s tím, že budou použity pro rozsáhlé projekty a zastřešují hlavní potřeby vývojářů tím, že se na sebe snaží převzít do maximální možné míry tolik potřebný “background”. IDE díky jasným pravidlům umožňují tvořit komponenty velmi rychle a celý návrh tak minimalizuje množství chyb. Že jsou třeba k beanu minimálně 2 další rozhraní (pominu-li message-driven beans)? No a? Pravidla jsou tak dána, umožňují nám pak určitou funkčnost a je na každém, zda mu tato vyhovuje, či ne.

Technologií pro Javu je velmi mnoho a nelze jednoznačně říct, že EJB jsou všelékem na veškeré problémy, které musíme řešit. Místo porovnávání “kdo z koho” je rozumných východiskem zvážit, čím může být konkrétní technologie přínosem pro náš projekt. Pokud se najde vhodnější kandidát pro naše potřeby, bylo by hříchem jej nepoužít. Právě volnost rozhodování nám dává prostor pro další zdokonalování a psaní kvalitnějšího software. A pro mě jsou EJB vhodnou technologií.

Publikoval Jiří Hradil • 08.01.2005 v 02:01 • pod kategorií ejbŽádné komentáře

Studujeme EJB

Pro studium Enterprise Java Beans je nezbytně nutná vhodná literatura, která nás provede všemi zákoutími této technologie. Musí nás přesvědčit o tom, že Sun měl ty nejlepší úmysly a skutečně se nám snažil usnadnit náš nelehký vývojářský život.
Zdrojů ke studiu je mnoho a podle dotazů a zdůvodnění, proč EJB nepoužívat mám spíše dojem, že to spousta zvědavců vzdala už na začátku a vůbec se nesnaží pochopit podstatu této skvělé technologie. Vůbec se jim nedivím, pokud studovali podle J2EE tutorialu. Jsou ale mnohem jednodušší cesty.

Head First EJB (EJB 2.0) je vysněnou knihou, která nám jasně ukazuje, jak má vypadat správný a srozumitelný výklad. Musím rovnou říct, že nenávidím zbytečně složité věci a pokud mám něco dobře pochopit, musím si to nakreslit. Právě tento způsob používají autoři Kathy Sierra a Bert Bates a celá kniha je koncipována jako co nejjednodušší (a zároveň velmi kvalitní) příprava na certifikaci Sun Certified Business Component Developer (SCBCD). Témata jsou do hloubky probírána, perfektně rozkreslena a jsou podána velmi nenásilným způsobem. Čtenář pochopí koncepci EJB rozhraní, typy beanů, životní cyklus beanu a vůbec všechno, co potřebuje, aby mohl začít psát skutečné SW komponenty.
Dostupnost: v ČR jsem ji ještě neviděl, objednával jsem přímo z Amazon.
Hodnocení: jedna z nejlepších investicí, které jsem kdy udělal. Pokud potřebujete dokonale vysvětlit EJB, tak není důvod váhat. Nic lepšího se mi zatím do rukou nedostalo (ne, nemám procenta z prodeje ;).

Mastering EJB II (EJB 2.0) je další hutná kniha, kterou si lze zdarma stáhnout ze serveru TheServerSide. Méně srozumitelná, než HF EJB, ale v některých případech jde více do hloubky (patterns, load balancing, clustering). Má několik chyb a není tak úplná jako HF EJB (a book errata mail není funkční), ale nabízí prostě jiný pohled a rozhodně rozšíří vědomosti.
Dostupnost: volně ke stažení po registraci.
Závěr: Velmi dobrý zdroj, doporučuji číst až po zvládnutí základů.

J2EE Tutorial 1.4 (EJB 2.1) nabízí úvod do technologie EJB (společně se servlety, jsp a jsf), ale nějak mi tento způsob výkladu nepadnul do oka. Prostě vysvětlují jednoduché věci složitě a nemají tolik obrázků :). Chybí motivace, proč je třeba technologii používat určeným způsobem. Pak se nedivím, že vývojáři po shlédnutí EJB zavrhnou pro jejich příllišnou “složitost”.
Dostupnost: volně ke stažení
Závěr: Od Sunu, stačí jen prolétnout, není nutno studovat :)

EJB 2.0 Specification je prostě základ, na kterém staví všichni ostatní. Sem si chodím pro radu, když něco potřebuji vysvětlit dokonale a do hloubky “doladit”.
Dostupnost: volně ke stažení
Závěr: Pokud neznáte EJB, je zbytečné začínat tady, opět směruji dychtivé studenty na HF EJB. Co není v této specifikaci, není ani v certifikačním testu SCBCD.

Na konec se ještě zmíním, proč se nyní učím EJB 2.0 (a ne 2.1, či 3.0):

  • studijní materiály jsou psané většinou pro 2.0
  • Sun certifikuje opět pouze 2.0
  • pokud zvládnu základy 2.0, pak nebude problém přejít na novější technologii
Publikoval Jiří Hradil • 06.01.2005 v 17:01 • pod kategorií ejbŽádné komentáře

Objevujeme EJB

Jsou technologie, které jsou všeobecně známé a používané a technologie všeobecně známé a nepoužívané. Po několika měsících experimentování s Hibernate jsem došel k názoru, že to ještě není to pravé ořechové a začal jsem se učit a zkoušet Enterprise Java Beans (EJB). Po nastudování několika knih mám pocit, že je tato technologie přímo zrozená k tomu, aby byla objevena a masivně používána. Nebudu tady popisovat, o čem jsou EJB (to už udělali lépe jiní), jen vypíchnu, co se mi na nich líbí a co ne:

Líbí:

  • Striktně komponentový přístup
  • Definice home/remote (local/local-home) rozhraní k beanu (fakt, mě se tato logika velmi líbí, když pochopíte, o čem to je, nedáte na to dopustit)
  • Striktní definice v názvech metod (ejbCreate, ejbHome…) a vůbec držení vývojáře maximálně zkrátka (řeči o volnosti neberu, když se na projektu střídá hafo lidí, tak někdy ani jmenné konvence nestačí)
  • Container Managed Persistence (CMP) (při deploy create table, atd.) a relationships
  • DD ejb-jar.xml
  • Miluju Sun a tohle dělá Sun :)

Nelíbí:

  • Specifikace EJB je místy příliš obecná (i když, zase vendoři serverů mají volnější ruce, např. load balancing, clustering, generace PK…)
  • EJB-QL je dost chudý (např. 2.0 nemají ani ORDER BY)
  • Složitá technologie, ale jen na začátku. Když pochopíte, jak vše funguje a hlavně proč je to tak uděláno, nedáte na EJB dopustit. Chce to dobrou literaturu a hned musím říct, že J2EE tutorial od SUNu dobrý zdroj není (v dalším příspěvku uvedu lepší materiály ke studiu).

Jak tak procházím java konferenci, či různé příspěvky, mám z nich pocit, že EJB se nepoužívají jednoduše proto, že spoustě lidí připadají příliš složité (což nejsou). Možná je ale pravda jinde-prostě je všichni používají, ale nepíšou o tom :).

Publikoval Jiří Hradil • 06.01.2005 v 01:01 • pod kategorií ejbŽádné komentáře

Java Server Faces a Custom Converter

Problematika vytváření vlastních konverterů v Java Server Faces je nedostatečně zdokumentovaná a začátečníka pronikajícího do této skvělé technologie může stát zbytečně mnoho času.
V J2EE tutoriálu je ukázka jednoduchého konverteru pro úpravu čísel kreditních karet, avšak jedná se o převod String->String. V některých případech bychom však potřebovali převést String na náš vlastní objekt.
Proto se pojďme podívat, jak takový konverter vytvoříme. Předpokládejme, že chceme vytvořit select menu s výběrem našich objektů a tyto postoupit do backing beanu:

Nejdříve vytvoříme objekt Postava, který chceme nabízet k výběru v selectOneMenu. Důležité je překrytí metody equals() - tak, abychom dokázali porovnat objekt Postava podle parametrů (i když se bude jednat o 2 různé instance). Pokud equals() nepřekryjeme, nebude konverter fungovat, protože seznam objektů nabízených v selectOneMenu nebude stejný jako objekt, který v konverteru předáme podle předaného String argument (viz. konverter):

Postava.java (objekt, který chceme konvertovat):

/*
 * Vytvoreno 23.11.2004
 */
package cz.hradil.blog;

/**
 * Trida pro vytvareni objektu Postava. Kazda postava ma sve ID a jmeno.
 *
 * @author Jirka Hradil
 */
public class Postava {

    private Short idcko;

    private String jmeno;

    /**
     * Vytvori novou postavu.
     *
     * @param noveIdcko - ID postavy
     * @param noveJmeno - jeji jmeno
     */
    public Postava(Short noveIdcko, String noveJmeno) {
        this.idcko = noveIdcko;
        this.jmeno = noveJmeno;
    }

    /**
     * @return Vrati ID postavy.
     */
    public Short getIdcko() {
        return idcko;
    }

    /**
     * @return Vrati jmeno postavy.
     */
    public String getJmeno() {
        return jmeno;
    }

    /**
     * @param idcko
     *            Nastavi ID postavy.
     */
    public void setIdcko(Short idcko) {
        this.idcko = idcko;
    }

    /**
     * @param jmeno
     *            Nastavi jmeno postavy.
     */
    public void setJmeno(String jmeno) {
        this.jmeno = jmeno;
    }

    /*
     * 2 postavy jsou si rovne, pokud maji stejne ID a jmeno.
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof Postava)) {
            return false;
        }
        Postava postava = (Postava) obj;

        if (postava.getIdcko() == null || postava.getJmeno() == null) {
            return false;
        }

        return this.getIdcko().equals(postava.getIdcko()) &amp;&amp;
        this.getJmeno().equals(postava.getJmeno());
    }

    /*
     * Hashovaci kod pocitame jako: int result=17;
     * result=37*result+idcko.hashCode(); result=37*result+jmeno.hashCode();
     * Duvod: rovnomerne rozlozeni objektu v hashovacich sektorech.
     */
    public int hashCode() {
        int result = 17;
        result = 37 * result + idcko.hashCode();
        result = 37 * result + jmeno.hashCode();
        return result;
    }

    /*
     * Vraci retezec ID a jmena osoby.
     *
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return idcko.toString() + &quot;:&quot; + jmeno;

    }
}

Pak vytvoříme backing bean, který obsahuje get/set pro uložení zvoleného objektu Postava a rovněž nabízí List objektů SelectItem, který budeme načítat z JSP stránky (viz. view). Pro zvědavce-ano, míchám tady dohromady controller a model, v praxi bychom zřejmě načítali seznam jinak:

VyberPostavyBean.java (backing bean):

/*
 * Vytvoreno 22.11.2004
 */
package cz.hradil.blog;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.faces.model.SelectItem;

/**
 * Controller pro vyber Postavy.
 *
 * @author Jirka Hradil
 *
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class VyberPostavyBean {

    private Postava zvolenaPostava;

    /**
     * @return Vraci zvolenou postavu.
     */
    public Postava getZvolenaPostava() {
        return zvolenaPostava;
    }

    /**
     * @param zvolenaPostava
     *            Nastavi postavu.
     */
    public void setZvolenaPostava(Postava zvolenaPostava) {
        this.zvolenaPostava = zvolenaPostava;
    }

    /**
     * Metoda se zavola po stisknuti tlacitka pro ulozeni.
     */
    public void ulozit() {
        System.out.println(&quot;Jsem v metode ulozit(). Predany objekt: &quot; + zvolenaPostava);
//tady si uz s objektem udelame co chceme
    }

    /**
     * @return Vraci seznam 3 vzorovych postav.
     */
    public List getSeznamPostav() {
        List seznamPostav = new ArrayList();

        Postava bugs = new Postava(new Short(&quot;1&quot;), &quot;Bugs Bunny&quot;);
        Postava runner = new Postava(new Short(&quot;2&quot;), &quot;Road Runner&quot;);
        Postava coyote = new Postava(new Short(&quot;3&quot;), &quot;Wile E Coyote&quot;);

        seznamPostav.add(bugs);
        seznamPostav.add(runner);
        seznamPostav.add(coyote);

        return seznamPostav;
    }

    /**
     * @return Vraci seznam postav jako List polozek SelectItem.
     */
    public List getSeznamPostavMenu() {
        List seznamPostavMenu = new ArrayList();

        for (Iterator iter = this.getSeznamPostav().iterator(); iter.hasNext();) {
            Postava el = (Postava) iter.next();

//Zde si vsimneme, ze do SelectItem musime ukladat cely objekt Postava, ktery pak chceme ziskat zpatky
            seznamPostavMenu.add(new SelectItem(el, el.getJmeno())); //2. argument je text, ktery chceme zobrazit
        }

        return seznamPostavMenu;
    }
}

Konečně se dostáváme ke konverteru. Tady je důležité, aby objekt, který chceme vrátit (převod String->Object) byl stejný jako objekt, který jsme nabízeli v selectOneMenu. A protože se nedokážeme dostat přímo na zvolený objekt v selectOneMenu, musíme na základě předaného argumentu String prohledat seznam původních objektů, vyhledat shodný objekt a vrátit jej. JSF kontrolují, abychom vrátili objekt, který byl na výběr v selectOneMenu. Proto jsme také překrývali equals-abychom zajistili, že původní objekt v selectOneMenu a námi vrácený objekt jsou stejné. Pokud bychom vrátili jiný objekt Postava, který se v selectOneMenu vůbec nenabízel, pak bychom viděli Exception :).

VyberPostavyConverter.java (konverter):

/*
 * Vytvoreno 22.11.2004
 *
 */
package cz.hradil.blog;

import java.util.Iterator;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

/**
 * Convertor String-&gt;Postava a zpet.
 *
 * @author Jirka Hradil
 */
public class VyberPostavyConverter implements Converter {

    /*
     * Z predaneho String vytvori objekt Postava.
     *
     * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext,
     *      javax.faces.component.UIComponent, java.lang.String)
     */
    public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {

        Postava zvolenaPostava = null;

        for (Iterator iter = new VyberPostavyBean().getSeznamPostav().iterator(); iter.hasNext();) {
            Postava el = (Postava) iter.next();
            if (el.getIdcko().toString().equals(arg2)) {
                zvolenaPostava = el;
                break;
            }
        }
        return zvolenaPostava;
    }

    /*
     * Objekt Postava prevede na String.
     *
     * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext,
     *      javax.faces.component.UIComponent, java.lang.Object)
     */
    public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
        return (((Postava) arg2).getIdcko().toString());
    }
}

Teď zaregistrujeme bean a konverter do faces-config.xml:

faces-config.xml:

&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;!DOCTYPE faces-config PUBLIC
&quot;-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN&quot;
&quot;http://java.sun.com/dtd/web-facesconfig_1_0.dtd&quot;&gt;

&lt;faces-config&gt;

&lt;!--  BEANY --&gt;
    &lt;managed-bean&gt;
        &lt;description&gt;Vyber postavy&lt;/description&gt;
        &lt;managed-bean-name&gt;VyberPostavyBean&lt;/managed-bean-name&gt;
        &lt;managed-bean-class&gt;cz.hradil.blog.VyberPostavyBean&lt;/managed-bean-class&gt;
        &lt;managed-bean-scope&gt;session&lt;/managed-bean-scope&gt;
    &lt;/managed-bean&gt;

&lt;!--  KONVERTERY --&gt;
    &lt;converter&gt;
        &lt;converter-id&gt;VyberPostavyConverter&lt;/converter-id&gt;
        &lt;converter-class&gt;cz.hradil.blog.VyberPostavyConverter&lt;/converter-class&gt;
    &lt;/converter&gt;

&lt;/faces-config&gt;

A můžeme vytvořit view:

formular.jsp (view):

&lt;%@ taglib uri=&quot;http://java.sun.com/jsf/html&quot; prefix=&quot;h&quot; %&gt;
&lt;%@ taglib uri=&quot;http://java.sun.com/jsf/core&quot; prefix=&quot;f&quot; %&gt;

&lt;f:view&gt;

    &lt;h:form&gt;
        &lt;h:outputText value=&quot;Postava&quot; /&gt;
        &lt;h:selectOneMenu value=&quot;#{VyberPostavyBean.zvolenaPostava}&quot; converter=&quot;VyberPostavyConverter&quot;&gt;
            &lt;f:selectItems value=&quot;#{VyberPostavyBean.seznamPostavMenu}&quot; /&gt;
        &lt;/h:selectOneMenu&gt;
        &lt;h:commandButton id=&quot;ulozit&quot; action=&quot;#{VyberPostavyBean.ulozit}&quot; value=&quot;Ulozit&quot; /&gt;
    &lt;/h:form&gt;
    &lt;h:messages /&gt;

&lt;/f:view&gt;

A je to. Po zvolení položky v menu se nám do backing beanu přenese zvolený objekt Postava.

ODKAZY:
J2EE 1.4 Tutorial

Publikoval Jiří Hradil • 25.11.2004 v 02:11 • pod kategorií hibernate, jsfŽádné komentáře