Equals ja Hashcode JPA/Hibernate sovelluksissa

Olen tästä kirjoitellut joskus aiemminkin. Mutta kyseessä siis Java-onjektien equals, hashCode, ja toString metodien toteuttaminen oikein. Periaatteessa ihan perusjuttu, jo ensimmäisissä oppitunneissa opittu, mutta muuttuukin jännittävämmäksi kun otetaan persistentit tietovarastot mukaan. Relaatiotietokantojen kanssa voisi sanoa että suurin osa on toteutettu väärin.

Mitä tällä on väliä? Nämä metodit (no ei sentään toString) astuvat peliin kun olioita puljataan kokoelmissa, tai uusissa Java 8 streameissä. Esim. set ei salli duplikaatteja. Se tarkistaa duplikaatit ja joissain toteutuksissa myös asemoi ’ämpärit’ hashCode ja equals metodien avulla. Jos ne on toteutettu väärin, setit eivät toimi oikein. Sama juttu jos yrität hakea listan metodeilla löytyykö sieltä tietty olio.

Miten tämä voidaan toteuttaa? No tapoja on muutama.

– Jätä toteuttamatta, tässä mallissa pelataan Object luokan oletustoteutuksilla, jotka katsovat equals ja hashCode sisäisen pointterin mukaan, eli pelaavat instanssin perusteella. Näin jos on vaikkapa sama tietokannan rivi ladattu kahteen eri instanssiin, ne ovat eri.

– Toteuta primary keyn perusteella, eli jos id on sama, objektit ovat samat, samoin hashCode lasketaan id:stä. Tässä mallissa heikkoutena on jos on kaksi objektia joiden id on null. Mekanismin mielestä ne olisivat samat. Tämä tulee esiin jos yrität esim. tunkea useampaa instanssia settiin.

– Toteuta business keyn perusteella, eli datasta löytyvän luonnollisen avaimen, esim. sosiaaliturvatunnus, etc. Tämän heikkous on että monella datalla voi olla vaikeaa löytää yksilöivää tietoa ilman kannassa generoitua primary key id:tä.

Lopulta ei ole oikein tai väärin, kunhan dokumentoituja pelisääntöjä noudatetaan (Object-luokan API dokumentaatio) – tärkeintä on tiedostaa mitä valinnasta seuraa koodille. Oletus eli ei equals/hashcodea ollenkaan on huonoin vaihtoehto kannan kanssa pelaavalle koodille. Sitä huonompi on ainoastaan rikkinäinen oma toteutus.

toString metodissa on vähemmän sudenkuoppia, mutta yhden kanssa on oltava tarkkana. Kun metodi tulostaa jäseniensä arvoja, ja jäsenet ovat custom olioita – syntyy helposti nullpointer exceptioneitä tai syklisiä referenssejä jotka johtavat stack overflow exceptioniin (ironista ;). Näissä olisi siis viisainta olla menemättä objektien toString metodeita pitkin navigoimaan, ja tyytyä tulostamaan vain jotain niiden sisältä, esim. id, ja sekin vain kun null arvot on tarkistettu.

Stackoverflow foorumin ihastuttavissa keskusteluissa oli linkki tästä tehtyyn hyvään taulukkoon:

To primary key or not to primary key?

No, tähän on myös toinen hyvä vaihtoehto. Tiedostetaan tämän metodin toiminnan puutteellisuus ja ei nojata siihen liikaa. Onko kaksi oliota sama – sehän riippuu tilanteesta ja tarpeista, eli on enemmänkin dynaaminen algoritmi kuin staattinen totuus. Streamien filter-operaatiot ovat oivallisia korvikkeita tälle. Mutta ellei parempaa pysty keksimään, se perinteinen id kenttä on ihan ok kunhan vain itse pitää rajoitukset mielessään.

Lisäksi pienenä lisä-sudenkuoppana: huomaa että joissain JPA-toteutuksissa liittyy automaagista weavingiä jota suoritetaan ajonaikana. Käytännössä siis kun kutsut get/set metodeita, niihin punotaan automaattisesti tietokantakutsuja mukaan. Näin ollen on eroa viittaatko muuttujaan suoraan, vai get metodi kautta. Lähtökohtana, jos get/set metodit on toteutettu oikein (pelkkiä gettereitä/settereitä, ei sivuvaikutuksia), on parempi mennä metodien läpi kun JPA on pelissä mukana.

Ja täällä erinomaista keskustelua aiheesta. Ei ole helppo kysymys, eikä ole yhtä autoritatiivista vastausta:

http://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma

Advertisements

Vastaa

Täytä tietosi alle tai klikkaa kuvaketta kirjautuaksesi sisään:

WordPress.com-logo

Olet kommentoimassa WordPress.com -tilin nimissä. Log Out / Muuta )

Twitter-kuva

Olet kommentoimassa Twitter -tilin nimissä. Log Out / Muuta )

Facebook-kuva

Olet kommentoimassa Facebook -tilin nimissä. Log Out / Muuta )

Google+ photo

Olet kommentoimassa Google+ -tilin nimissä. Log Out / Muuta )

Muodostetaan yhteyttä palveluun %s