Archiv pro měsíc April, 2007
Známe to všichni – přijdete ráno do práce a máte za úkol
naimplementovat nějaký webový formulář v aplikaci, která používá
Spring Framework a Hibernate.
Už v sample aplikaci Petclinic, která
je součástí distribuce Spring Frameworku je vidět jak na to:
public class EditOwnerForm extends AbstractClinicForm {
...
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
return getClinic().loadOwner(RequestUtils.getRequiredIntParameter(request, "ownerId"));
}
protected ModelAndView onSubmit(Object command) throws ServletException {
Owner owner = (Owner) command;
getClinic().storeOwner(owner);
return new ModelAndView(getSuccessView(), "ownerId", owner.getId());
}
}
Zjednodušeně slovy:
Nechť je formBacking
objekt naloadován z fasády pomocí
metody loadOwner
(a tady vzniká detached objekt). Po odeslání
(a případně validaci) nechť je editovaný (stále mluvíme o našem
detached objektu) uložen do databáze pomocí metody
storeOwner
.
Vše funguje skvěle až do chvíle, kdy nastane tento scénář:
- V session A naloadujete objekt A s ID např 1 (vznikne tak
vzpomínaný detached objekt)
- V session B (vzniká při odeslání formuláře) dojde
k inicializaci objektu A s ID 1 (tedy toho samého objektu jež
právě editujete).
Hibernate vyhodí nepěknou NonUniqueObjectException
.
A dobře dělá – který z dvou objektů typu A, oba s ID
1 je ten pravý.
K tomuto scénáři může dojít zcela jednoduše a hlavně na první
pohled z nevysvětlitelných důvodů. Například díky řetězci
několika many-to-one asociací. V mém případě k tomu došlo při
zavolání metody setAsText(String text)
v jednom z property
editorů. A k tomu dochází ještě před validací a uložením
editovaném objektu.
Nejdřívě mě napadlo vyřešit to lazy loadingem many-to-one asociací.
Při bližším prostudování dokumentace Hibernatu zjistíme, že
asociace many-to-one nejsou defaultně lazy. Ani pomocí
atributu outer-join to nezměníte. Pouze řeknete, zda se má
použít join (outer-join=„true“), samostatný sql dotaz
(outer-join=„false“) a nebo zda se objekt fetchne v rámci
joinu nebo lazy (outer-join=„auto“ – defaultní hodnota).
K lazy loadu může dojít pouze když má objekt definovanou proxy.
Ovšem tohle řešení je hodně ugly…krom toho to řeše problém pouze
u vazech many-to-one.
Použité řešení bylo nakonec jiné – při ukládání objektu do
databáze udělat něco takového:
protected ModelAndView onSubmit(Object command) throws ServletException {
Owner owner = (Owner) command;
Owner detachedOwner = getClinic().loadOwner(owner.getId().intValue());
BeanUtils.copyProperties(detachedOwner, owner);
getClinic().storeOwner(owner);
return new ModelAndView(getSuccessView(), "ownerId", owner.getId());
}
Provedli jsme nové naloadování editovaného objektu. Pokud již Hibernate
v této session má tento objekt fetchnutý zafunguje first level cache a
dojde pouze k vrácení odkazu na daný objekt bez jeho druhého
loadování z db. Poté zkopírujeme vlastnosti ze změněného objektu do
čertvě fetchnutého. Nakonec jej uložíme.
Závěr
Článkem jsem chtěl poukázat na to, že i když nám Hibernate život
hodně zjednodušuje, je pořád dost věcí, na které je potřeba dávat
pozor, a které dokážou pěkně potrápit (v tomto případě
2 hodiny).
Celý článek 30. April 2007
Knihu Hibernate in Action jsem
přelouskával (s určitými přestávkami) po večerech pár měsíců.
Zpočátku jsem ji používat pouze jako referenční příručku –
nahlížel jsem do ní pouze když jsem potřebaval zjistit řešení
nějakého problému. Po přečtení několika fragmentů kódu jsem zjistil,
že bude dobré přečíst si knihu celou.
S odstupem času musím dát za pravdu podtitulu knihy –
A guide to the concepts and practise of object/relational
mapping. Hned v první kapitole Understanding object/relational
persistence kniha vysvětluje základní přístupy a problémy při
mapování objektů do relační databáze.
Nechci zde uvádět popis každé kapitoly, pouze v několika bodech
zmíním co všechno se lze v knize dozvědět:
- typy vazeb mezi objekty a velmi detailní popis jejich mapování
v Hibernatu včetně příkladů
- různé způsoby performance tunningu (batch fetchning, outer join, query
cache)
- popis Hibernate Session a SessionFactory
- popis lifecyclu persistentních objektů
- popis first a second level cache Hibernatu
- konkurenční přístup k datům a strategie řešení tohoto
problémů
- transakce
- podrobný popis nástrojů, které pomáhají urychlit vývoj softwaru
pomocí Hibernatu a také pomáhají udržet konzistentní mode napříč
všemi úrovněmi datového modelu. Mám na mysly DDL, hibernatí HBM souboru a
POJOs. Ukázáno je i použítí XDocletu.
Zárukou kvality knihou jsou i její autoři. Christian Bauer
napsal i další publikace o Javě – například Java
Persistence with Hibernate. Gavin King je samotným autorem
Hibernatu.
Závěr
Jak je z předchozích řádků vidět – kniha se mi libila
s všem začínajícím programátorům, koho výraz ORM se spojení
s jakou zajímá, ji doporučuji k přečtení.
Během čtení knihy jsem naprogramoval PHP based ORM framework
eVent, který jsme ve Venturii s úspěchem použili na více
než desítce projektů. Umí toho sice zlomek toho co Hibernate, ale
z velké části pokrývá naše potřeby. Mezi zajímavé vlastnosti
patří automatické generování UI a validačního kódu pro doménové
objekty. Jen pro zajímavost – metainformace má eVent uložen přímo ve
zdrojovém kódu doménových objektů – v komentářích. PHP od
verze 5.1 umí pomocí reflexe přistupovat ke komentářům. Funguje to
tedy podobně jako vzpomínaný XDoclet s tím rozdílem, že XDoclet má
své místo při compile-timu a eVent ke komentářům přistupuje při
runtimu.
Informace o knize
- Název
- Hibernate in Action
- Autoři
- Christian Bauer, Gavin King
- Vydal
- Manning Publications Co.
- ISBN
- 1932394–15-X
- Datum vydání
- 2005
- Počet stran
- 400
Celý článek 24. April 2007
Při čtení článku Schéma
databáze – používáme hibernate mě napadlo, abych napsal
o jednom problému, který souvisí s generováním POJOs
z mapovacích souborů pomocí Hibernate
Synchronizeru v případě používání vlastních typů
(UserTypes).
Hibernate umožňuje psaní vlastních typů – takzvaných
UserTypes.Mohou být buď jednoduché (skalární –
s jednou vlastností) – typ UserType nebo kompozitní
– CompositeUserType (více vlastností).
Ukažme si na příkladu použití kompozitního UserTypu:
package cz.vavru.sample.hibernate;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Currency;
import net.sf.hibernate.CompositeUserType;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.engine.SessionImplementor;
import net.sf.hibernate.type.Type;
import cz.vavru.sample.hibernate.MonetaryAmount;
public class MonetaryAmountType implements CompositeUserType {
public Class returnedClass() {
return MonetaryAmount.class;
}
...
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session,
Object owner) throws SQLException {
double value = resultSet.getDouble(names[0]);
if (resultSet.wasNull())
return null;
Currency currency = Currency.getInstance(resultSet.getString(names[1]));
return new MonetaryAmount(new Double(value), currency);
}
public void nullSafeSet(PreparedStatement statement, Object value, int index,
SessionImplementor session) throws SQLException {
if (value == null) {
statement.setNull(index, Hibernate.BIG_DECIMAL.sqlType());
statement.setNull(index + 1, Hibernate.CURRENCY.sqlType());
} else {
MonetaryAmount amount = (MonetaryAmount) value;
String currencyCode = amount.getCurrency().getCurrencyCode();
statement.setDouble(index, amount.getValue().doubleValue());
statement.setString(index + 1, currencyCode);
}
}
...
}
Příslušný doménový objekt MonetaryAmount
vypadá
následovně:
package cz.vavru.sample.hibernate;
import java.io.Serializable;
import java.util.Currency;
import java.util.Iterator;
public class MonetaryAmount implements Serializable {
private static final long serialVersionUID = Long.MIN_VALUE * 34;
private Double value;
private final Currency currency;
public MonetaryAmount(Currency currency) {
value = new Double(0d);
this.currency = currency;
}
public MonetaryAmount(BigDecimal bigDecimal, Currency currency) {
this.value = new Double(bigDecimal.doubleValue());
this.currency = currency;
}
public MonetaryAmount(Double value, Currency currency) {
this.value = value;
this.currency = currency;
}
public Currency getCurrency() {
return currency;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof MonetaryAmount))
return false;
final MonetaryAmount monetaryAmount = (MonetaryAmount) o;
if (!currency.equals(monetaryAmount.currency))
return false;
if (value != monetaryAmount.value)
return false;
return true;
}
public int hashCode() {
int result;
result = 29 * value.intValue() + currency.hashCode();
return result;
}
public String toString() {
return "Value: '" + getValue() + "', " + "Currency: '" + getCurrency() + "'";
}
}
A ukázka využití v mapovacím souboru jiného doménového
objektu, který MonetaryAmount
používá:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
<hibernate-mapping>
<class name="cz.vavru.sample.hibernate.MyEntity"
table="ccg_pg_yearly_settings" >
<id name="id" type="java.lang.Integer" column="id">
<generator class="increment" />
</id>
...
<property name="budget" not-null="false"
type="cz.vavru.sample.hibernate.MonetaryAmountType">
<column name="budget_amount" />
<column name="budget_currency" />
</property>
</class>
</hibernate-mapping>
Kód, který Hibernate Synchronizer vygeneruje nesprávně používá pro
vlastnost budget
typ MonetaryAmountUserType
místo správného MonetaryAmount
.
package cz.vavru.sample.hibernate;
public class MyEntity implements Serializable {
...
private cz.vavru.sample.hibernate.MonetaryAmountType budget;
public cz.vavru.sample.hibernate.MonetaryAmountType getBudget () {
return budget;
}
public void setBudget (cz.vavru.sample.hibernate.MonetaryAmountType budget) {
this.budget = budget;
}
...
}
Správný kód je tedy:
package cz.vavru.sample.hibernate;
public class MyEntity implements Serializable {
...
private cz.vavru.sample.hibernate.MonetaryAmount budget;
public cz.vavru.sample.hibernate.MonetaryAmount getBudget () {
return budget;
}
public void setBudget (cz.vavru.sample.hibernate.MonetaryAmount budget) {
this.budget = budget;
}
...
}
Přiznám se, že mi odhlaní tohoto problému zabralo asi hodinu.
Odkazy
Celý článek 23. April 2007
Dnešek jsem strávil v posteli. I když jsem zdravý jako řípa
jednou za rok mě na den dva skolí chřipka – asi aby si tělo na
vyrábělo protilátky. Část dne jsem věnoval svému RSS klientovi.
A tady je malý digest toho co jsem pročetl:
- na článek How to Report Bugs
Effectively jsem narazil kdesi na netu a s chutí jsem si jej
přečetl. A určitě ho musím dát přečíst kolegům od nás
z Gopasu, kteří píšou zápisy do buglistu. Často se stává, že
samotná reprodukce bugu mi zabere víc času než jeho odstranění.
- článek Model
View Presenter mutated: Observable View ukazuje jeden ze způsobů jak
odstranit business logiku z code-behind souborů ASP.NET stránek.
- možná jsem poslední člověk z java komunity, který nezná
issue-tracker tool JTrac. Narazil jsem na něj
v článku JavaEE
Architecture Using The Spring Framework. Už samotný výčet frameworků,
které JTrac používá je hodně zajímavý:
JTrac uses the Acegi Security Framework, Spring Webflow, Spring MVC, Spring
AOP, Spring DAO, Hibernate 3 and Spring Modules.
Schéma architektury JTracu je pořízeno z prezentace Java
EE Architecture with the Spring Framework (PDF, 722 KB)
Celý článek 20. April 2007
Druhá várka zajímavých linků je tady.
- Je tu Spring Framework
2.0.4 – nová verze oblíbeného frameworku, která kromě řady
bugfixů obsahuje i několik významných zlepšení výkonnosti.
Například opakované vytváření bean je údajně 12
rychlejší.
- Byl releasnut Spring Web
Flow 1.0.2. Za zmínku stojí zlepšení podpory pro JSF.
- dalším zajímavým článkem je Hibernate
can meet your validation needs. Ukazuje použítí Hibernate annotations, a
vlastních tag libraries k validaci doménových objektů. Součástí
článků je i příklad ke stažení.
- článek Ant
1.7: Using Antlibs se zabývá používáním Antlibs – novinkou verze
1.7. Více o Antlibs najdete v manuálu v části
Concepts and Types.
- a na závěr jeden starší článek pro začínající developery
o integraci podpory SVN do Eclipse IDE – How
to use Subversion with Eclipse.
Celý článek 13. April 2007
Ve firmě Venturia, pro kterou dlouhá
léta dělám, se uvolnilo místo
programátora.
Obvyklou omáčku se dozvíte na výše uvedeném linku, zde je pár slov ode
mě:
- Nehledáme hotového člověka, ale ani naprostého juniora. Náš člověk
by měl mít dobré znalosti PHP a OOP.
- Hlavní náplní by bylo vytváření (a údržba) webových stránek. PHP
weby děláme pomocí Zend Frameworku, Javovské weby zase Spring Frameworku,
Hibernate, SWF atd… Každopádně znalost javy není podmínkou.
- Pracovní doba ve Venturii je zhruba 8.30 až 17.00 hod. Nový
člověk s námi bude sedět v kanceláři (celkem tři lidé). Oběd
je 2 min. cesty.
- Součástí pohovoru jsou testy z PHP 5, HTML a CSS. Každý ze tří
testů má asi 12 otázek.
- Co se vývoje týče je ve Venturii obrovská volnost. Když někdo přijde
s novou věcí a dokáže její použítí vysvětlit a obhájit může se
s ní ihned začít pracovat. Takto jsme v poslední době zavedli
naříklad: Zend Framework, Creole, Smarty, HttpQuickForms, Spring Framework,
Spring Web Flow, Hibernate atd…
Pokud máte zájem můžete použít přímo tento
formulář.
Celý článek 11. April 2007
Dnes existuje spousta MVC frameworků, které vám dovedou zařídit skvělé
(rozuměj flexibilní) routování. Světem webu dnes vládnou nice URL`s,
přístupnost atd…V některých případech však požadujete něco
zcela jiného. Například taková registrace uživatelů nebo odesílání
objednávky. Takovéto procesy bývají rozděleny do několika kroků a bývá
zcela klíčové zajistit, aby se uživatel do každého kroku procesu mohl
dostat pouze námi definovanými cestami. Při odesílání objednáky
z e-shopu je jeden z možných scénářů takovýto:
- klepnutí na tlačítko objednat
- ověření zda je uživatel přihlášený
- pokud je uživatel přihlášen jdeme dále
- pokud není přihlášen nabídneme přihlašovací fomulář (a zde opět
mohou nastat dva stavy – uživatel již je nebo není
zaregistrován)
- potvrzení objednávky
- objednávka je odeslána
Během tohoto procesu je nutné nějak ošetřit, že jsou nastaveny všechny
potřebné údaje a jednoduše řešeno – systém je v takovém stavu
v jakém ho potřebujeme mít. Můžeme zvolit svoje proprietární
řešení a vše si ošetřovat například pomocí session nebo použít
SWF. Pomocí SWF můžeme
nadefinovat business procesy pomocí toků (flows), ve kterých můžeme
nadefinovat jednotlivé stavy procesů, možné přechody z jednoho stavu
do druhého a akce a podmínky, které tento přechod provází. Pomocí SWF
vlastně provádíme abstrakci našich business procesů a zvyšujeme tím
jejich přenositelnost. Veškerá logika pro řízení toku je soustředěna na
jednom místě a ne rozeseta na x místech kódu.
Celý článek 6. April 2007