Metamodel i typowane zapytania criteria API

W poprzednim wpisie długo rozpisywałem się na temat Criteria API. Dość ciekawą konstrukcją w API było określanie typu pobieranych danych w przypadku wyciągania pojedynczych kolumn. Wyglądało to tak:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Hero> hh = query.from(Hero.class);
query.select(hh.<String>get("name"))
     .where(cb.equal(hh.get("id"), 1L));

Jest to jednak podejście potencjalnie błędogenne. Możemy zawsze pomylić się w podawaniu typu kolumny, a dwa – przy zmianie encji i typu danych trzeba uaktualniać wszystkie zapytania oparte o kryteria. Nie do końca to fajne. Dlatego też w JPA istnieje coś fajnego – metamodel.

API Metamodelu

Zacznijmy od odpowiedzi na pytanie – co to jest ów metamodel? W skrócie są to informacje o wszystkich encjach w danym persistence-unit – o ich własnościach, typach czy relacjach. Zapisane w formacie obiektowym, dzięki czemu można na owym metamodelu działać cuda w trakcie działania aplikacji. Punktem wejścia do poznawania metamodelu jest nasza instancja EntityManagera, która posiada szereg metod, które zwracają odpowiednie informacje o encji, klasie wbudowanej czy typach (po kolei metody entity(), embeddable() i managedType().

Metamodel mm = entityManager.getMetamodel();
EntityType<Hero> hero_ = mm.entity(Hero.class);

Jak zawsze zachęcam do zapoznania się z dokumentacją dotyczącą Metamodel,EntityType, ManagedType oraz EmbeddableType. Cała hierarchia metamodelu jest dość rozbudowana i mocno ‘zgeneryzowana’. Dzięki temu jednakże możemy w trakcie działania aplikacji przyglądać się naszym encjom czy typom operując na mocnym typowaniu. By zaś konkretnie pokazać do czego przydaje się metamodel pierwszy kawałek kodu:

Metamodel mm = entityManager.getMetamodel();
EntityType<Hero> hero_ = mm.entity(Hero.class);
// Suffix na koncu sluzy informacji, ze nie jest to instancja encji per se - jest to stala konwencja w JPA
                
for( Attribute<? super Hero, ?> attr : hero_.getAttributes() ) {
      System.out.println( attr.getName() + " " + 
                          attr.getJavaType().getName() + " " + 
                          attr.getPersistentAttributeType() );
}

Rezultat w konsoli wygląda tak:

weapons java.util.List ONE_TO_MANY
id java.lang.Long BASIC
dragon com.wordpress.chlebik.jpa.domain.Dragon ONE_TO_ONE
deities java.util.List MANY_TO_MANY
finance com.wordpress.chlebik.jpa.domain.FinanceState EMBEDDED
level java.lang.Integer BASIC
name java.lang.String BASIC
creationDate java.util.Date BASIC
nicknames java.util.List ELEMENT_COLLECTION

Na tym przykładzie widać już do czego może się przydać metamodel w przypadku kryteriów – bez najmniejszych problemów możemy sprawdzić typ konkretnej własności. Nie obchodzi nas jaki on naprawdę jest – wystarczy, że mamy możliwość dostarczenia tej informacji przy konstruowaniu zapytania. Jednakże zanim uda się nam skompilować kod zawierający zapytanie z użyciem kryteriów (i silnego typowania) musimy zrobić jeszcze jedną rzecz – wygenerować ów metamodel.

Generowanie metamodelu

Domyślnie (przynajmniej w moim projekcie) metamodel nie jest generowany automatycznie. Istnieje możliwość wygenerowania go ręcznie – po prostu tworząc odpowiednią klasę z tym samym pakiecie co encja/typ/klasa wbudowana, której metamodel jest nam potrzebny. Czyli nasza klasa Hero miałaby metamodel, który wyglądałby tak:

package com.wordpress.chlebik.jpa.domain;

import java.util.Date;
import javax.annotation.Generated;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@StaticMetamodel(Hero.class)
public abstract class Hero_ {

	public static volatile SingularAttribute<Hero, Long> id;
	public static volatile SingularAttribute<Hero, Date> creationDate;
	public static volatile ListAttribute<Hero, Deity> deities;
	public static volatile ListAttribute<Hero, Nickname> nicknames;
	public static volatile SingularAttribute<Hero, Integer> level;
	public static volatile SingularAttribute<Hero, String> name;
	public static volatile SingularAttribute<Hero, Dragon> dragon;
	public static volatile ListAttribute<Hero, Weapon> weapons;
	public static volatile SingularAttribute<Hero, FinanceState> finance;

}

Tak jak pisałem – klasa powinna znajdować się w tym samym pakiecie co klasa docelowa, a także być oznaczona adnotacją @StaticMetamodel. Oczywiście mądrzy (i leniwi, zwłaszcza leniwi) programiści wymyślili automaty do generowania tego typu rzeczy. Najsensowniej rozpocząć od tego wpisu na Stackoverflow, a następnie zapoznać się z linkiem tam zamieszczonym, który jasno pokaże jak się uporać z tym problemem w zależności od używanego dostawcy JPA lub mechanizmu budowania projektu. W moim projekcie (generowany z użyciem Netbeans) wystarczyło dodać plik JAR z generatorem metamodelu Hibernate do classpath projektu i magicznie zaczęło działać. Podczas budowania projektu klasy metamodelu generowane są dynamicznie. Oczywiście należy wygenerowane klasy przerzucić do docelowego pakietu, aby IDE nie krzyczało i by kompilacja w ogóle się powiodła. Można robić to ręcznie, albo dopisać w narzędziu budowania projektu odpowiednią regułkę (wtedy jakiekolwiek zmiany w encjach czy typach są na 100% od razu odwzorowywane w metamodelu).

Dzięki powyższemu możemy ostatecznie przepisać nasze zapytanie do postaci unikającej konieczności umieszczania informacji o pobieranym typie. Taka forma użycia metamodelu nazywana jest ‘kanoniczną’.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Hero> hh = query.from(Hero.class);                
query.select( hh.get( Hero_.name ) ) 
     .where(cb.equal(hh.get("id"), 1L));
TypedQuery<String> tq = entityManager.createQuery(query);

System.out.println("Imie bohatera: " + tq.getSingleResult()  );  

W sumie to tyle. Temat króciutki, ale dość ułatwiający życie programistom używającym kryteriów. Następnym razem zajmiemy się czymś jeszcze fajniejszym – cachowaniem i blokadami.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s