Archiv pro 30. 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 1 komentář 30. 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