Zadanie 2 — anotácie a proxy

Ciele

  1. Precvičiť získavanie metadát o kóde z anotácií.
  2. Naučiť sa spracovávať anotácie počas prekladu programu.
  3. Naučiť sa dynamicky vytvárať triedy v Jave pomocou dynamického proxy.

Úloha 1: Metadáta

Pomocou anotácií @Id, @Table a @Column bude možné špecifikovať detaily ukladania:

  • stĺpe, ktorý bude primárnym kľúčom
  • názov tabuľky,
  • názov stĺpca,
  • podporu prázdnej hodnoty (NULL a NOT NULL),
  • unikátnosť hodnoty (UNIQUE).

Tieto informácie sa použijú pri generovaní SQL príkazov. Nie je nutné implementovať vlastnú validáciu splnenia podmienok (napr. not null), ale ponechať to na databázu.

Anotácie musia byť umiestnené v novom Gradle podprojekte annotations, v balíku sk.tuke.meta.persistence.annotations. Anotácia @Id nebude mať parametre.

Anotácia @Table má mať jeden parameter (nepovinný):

  • name – názov tabuľky v databáze (ak sa nezadá, použije sa názov triedy)

Anotácia @Column má mať parametre (všetky nepovinné):

  • name – názov stĺpca v databáze (ak sa nezadá, použije sa názov premennej)
  • nullable – môže obsahovať null? (predvolená hodnota true)
  • unique – má byť hodnota unikátna? (predvolená hodnota false)

Triedy definujúce databázovú tabúľku teraz budu musieť mať anotáciu @Table. Členské premenné, ktoré nemajú anotáciu @Column sa nebudú brať do úvahy pri generovan tabuľky.

Nezabudnite aktualizovať implementáciu tak, aby sa pri generovaní SQL používali názvy špecifikované pomocou anotácií.

Organizácia projektu

Podprojekt annotations môže mať takúto konfiguráciu Gradle (build.gradle.kts):

plugins {
    id("java-library")
}

group = "sk.tuke.meta.persistence"
version = "1.0-SNAPSHOT"

Potrebujete ho pridať tiež ako závislosť projektu persistence (persistence/build.gradle.kts):

dependencies {
    api(project(":annotations"))
    ...

A tiež do zoznamu projektov v settings.gradle.kts:

include("annotations")

Úloha 2: Anotačný procesor

SQL príkaz pre vytvorenie tabuliek sa má generovať počas kompilácie. Metóda createTables() bude využívať SQL kód vygenerovaný už počas prekladu a bude fungovať aj bez zadania zoznamu tried.

Vygenerovaný SQL príkaz musí byť umiestnený tak, aby po zabalení výsledného programu do JAR a prenesení do iného prostredia, bolo možné ho správne načítať.

Anotačný procesor implementujte v novom podprojekte processor.

Poznámka

Podstatné je uvedomiť si, že SQL súbor musí byť uložený tak, aby bolo možné ho použiť aj po tom, ako výslednú aplikáciu v podobe JAR súboru prenesiete do iného prostredia a spustíte. Najlepšie je, aby bol priamo súčasťou JAR súboru. Dá sa to dosiahnuť tak, že ho vytvorite pomocou Filer.createResource(StandardLocation.CLASS_OUTPUT, ...).

Na jeho načítanie v bežiacom programe môžete použiť metódu ClassLoader.getResourceAsStream(), ktorá hľadá súbor v CLASSPATH.

Organizácia projektu

Podprojekt processor môže mať takúto konfiguráciu Gradle (build.gradle.kts):

plugins {
    id("java-library")
}

group = "sk.tuke.meta.persistence"
version = "1.0-SNAPSHOT"

dependencies {
    implementation(project(":annotations"))
    implementation(project(":persistence"))
}

Je potrebné ho pridať aj do zoznamu projektov v settings.gradle.kts:

include("processor")

A ako závislosť pre example:

dependencies {
    annotationProcessor(project(":processor"))
    ...

Úloha 3: Oneskorené načítanie

Aby sa predišlo načítavaniu referovaných objektov, ktoré nebudú používané, knižnica zabezpečí oneskorené načítanie (lazy fetching) – referovaný objekt bude načítaný až pri jeho prvom použití.

Pre konfiguráciu oneskoreného načítavania pridajte do anotácie @Column parametre

  • lazyFetch (boolean, predvolene false)
  • targetClass (typ Class, trieda, objekt ktorej sa má načítať)

Oneskorené načítanie stačí podporovať len pre prípady, kedy sa na objekt referuje cez rozhranie. To, ktorá konkrétna trieda má byť vytvorená pri načítaní z databázy, sa uvedie pomocou parametra anotácie, napríklad:

    @Column(lazyFetch = true, targetClass = Department.class)
    private IDepartment department;

Pri načítaní objektu, obsahujúceho takúto referenciu, sa referovaný objekt nebude načítavať z databázy, kým sa nepoužije. Napríklad:

Person p = manager.get(Person.class, 1);  // only Person is loaded
IDepartment d = p.getDepartment();        // d is just a proxy
d.getName();                              // now the real object is loaded

Nezabudnite ošetriť ukladanie objektov obsahujúcich proxy namiesto referencie.

Celková štruktúra projektu

.
├── annotations
│   ├── build.gradle.kts
│   └── src
│       └── main
│           └── java
│               └── sk
│                   └── tuke
│                       └── meta
│                           └── persistence
│                               └── annotations
│                                   ├── Column.java
│                                   ├── Id.java
│                                   └── Table.java
├── example
│   └── ...
├── gradle
│   └── ...
├── gradlew
├── gradlew.bat
├── persistence
│   ├── build.gradle.kts
│   └── src
│       └── main
│           └── java
│               └── sk
│                   └── tuke
│                       └── meta
│                           └── persistence
│                               ├── PersistenceException.java
│                               ├── PersistenceManager.java
│                               ├── ReflectivePersistenceManager.java
│                               └── ...
├── processor
│   ├── build.gradle.kts
│   └── src
│       └── main
│           ├── java
│           │   └── sk
│           │       └── tuke
│           │           └── meta
│           │               └── persistence
│           │                   └── ...
│           └── resources
│               └── META-INF
│                   └── services
│                       └── javax.annotation.processing.Processor
└── settings.gradle.kts

Odovzdanie

Zadanie je potrebné vypracovať v priradenom projekte v Gitlabe do konca 14. apríla.