Archiv pro měsíc April, 2007

Jak mě potrápil jeden detached objekt

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 {
        ...
        /** Method forms a copy of an existing Owner for editing */
        protected Object formBackingObject(HttpServletRequest request) throws ServletException {
                // get the Owner referred to by id in the request
                return getClinic().loadOwner(RequestUtils.getRequiredIntParameter(request, "ownerId"));
        }
        /** Method updates an existing Owner. */
        protected ModelAndView onSubmit(Object command) throws ServletException {
                Owner owner = (Owner) command;
                // delegate the update to the Business layer
                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ář:

  1. V session A naloadujete objekt A s ID např 1 (vznikne tak vzpomínaný detached objekt)
  2. 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 NonUniqueObjec­tException. 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 Přidat komentář 30. April 2007

Hibernate in Action

Hibernate in ActionKnihu 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 2 komentářů 24. April 2007

Hibernate Synchronizer - pozor na UserType

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 MonetaryAmountU­serType 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 2 komentářů 23. April 2007

Zajímavé linky 3

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.

Architektura JTrac Schéma architektury JTracu je pořízeno z prezentace Java EE Architecture with the Spring Framework (PDF, 722 KB)

Celý článek Přidat komentář 20. April 2007

Zajímavé linky 2

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 Přidat komentář 13. April 2007

Venturia přijme programátora

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 Přidat komentář 11. April 2007

Spring web flow - framework pro management toku web aplikace

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:

  1. klepnutí na tlačítko objednat
  2. ověření zda je uživatel přihlášený
    1. pokud je uživatel přihlášen jdeme dále
    2. 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)
  3. potvrzení objednávky
  4. 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 3 komentářů 6. April 2007


Kalendář

April 2007
M T W T F S S
« Mar   May »
 1
2345678
9101112131415
16171819202122
23242526272829
30  

Články podle měsíců

Kategorie

Locations of visitors to this page