7. týždeň

Úloha 5 - Implementujeme servisný komponent cez JPA

Ciele

  1. Aktualizovať štruktúru projektu GameStudio .
  2. Anotovať entitné triedy umožňujúce použitie JPA služby v hre.
  3. Implementovať servisné komponenty pomocou JPA.
  4. Využiť vytvorené služby vo vašej hre.
  5. Spustiť projekt prostredníctvom Spring Boot .

Úvod

Na dnešnom cvičení je vašou úlohou implementovať servisné komponenty prostredníctvom technológií JPA (Java Persistence API) a Spring a využiť ich vo vašej hre.

Postup

Krok 1

Ak ste sa doteraz inšpirovali projektom z prednášky gamestudio2021, tak už máte vytvorený projekt typu Maven. Ak nie, vytvoríte ho teraz. Na správu závislostí projektu využijeme nástroj Maven, ktorý nám umožní jednoducho stiahnuť a spravovať knižnice, rovnako ako definovať spôsob zostavenia projektu.

Upozornenie

Pred vytváraním Maven projektu sa uistite, že máte priame pripojenie na internet, inak Maven nebude môcť stiahnuť knižnice a v projekte sa zobrazia chyby.

Úloha 1.1

Ak v projekte ešte nevyužívate Maven, vytvorte si novú kostru projektu v IntelliJ IDEA.

Pri vytváraní projektu vyberte z ponuky vľavo Spring Initializr, keďže budeme v projekte využívať technológiu Spring Boot (Obr. 1).

Vytvorenie novej kostry projektu so Spring Initializr
Obr. 1: Vytvorenie novej kostry projektu so Spring Initializr

Vyplňte nastavenia projektu (Obr. 2). Nastavenie Java version vyberte podľa toho, akú verziu inštalácie využívate vo vašom systéme.

Nastavenie vytváraného projektu
Obr. 2: Nastavenie vytváraného projektu

Zvoľte závislosti projektu (tzv. Dependencies). Záznamy pre ne sa automaticky pridajú do konfiguračného súboru pom.xml. Využijeme určite Spring Boot DevTools, Spring Data JPA a PostgreSQL Driver (Obr. 3).

Výber závislostí projektu
Obr. 3: Výber závislostí projektu

Nakoniec nastavte umiestnenie projektu vo vašom počítači a do projektu nakopírujte vašu implementáciu z predošlých týždňov.

Úloha 1.2

Ak ste váš projekt už mali nakonfigurovaný ako Maven projekt a nevytvárali ste nový podľa predošlej úlohy, aktualizujte a skontrolujte si konfiguračný súbor pom.xml.

V konfiguračnom súbore by mal byť element parent so záznamom pre spring-boot-starter-parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.4</version>
    <relativePath/>
</parent>

Medzi závislosťami dependencies by ste určite mali mať:

  • spring-boot-starter-data-jpa
  • spring-boot-devtools
  • postgresql
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>
</dependencies>

Poznámka

Základom Maven projektu je konfiguračný súbor pom.xml, ktorý je umiestnený v hlavnom adresári projektu. IntelliJ IDEA priebežne po každej zmene načíta konfiguračný súbor pom.xml. V prípade problémov ale môžete vynútiť jeho načítanie pomocou kontextového menu priamo nad súborom pom.xml a zvolením položky Maven > Reload Project.

Úloha 1.3

Oboznámte sa so štruktúrou projektu a podľa potreby ju aktualizujte.

Základná štruktúra adresárov projektu by mala byť nasledovná:

gamestudio  //hlavný adresár projektu gamestudio
├── src
│  ├── main  //zdrojové súbory kódu projektu
│  │  ├── java
│  │  │  └── sk
│  │  │     └── tuke
│  │  │        └── gamestudio  // hlavný balík projektu GameStudio
│  │  │           ├── entity   // balík pre entity servisných komponentov
│  │  │           ├── game     // balík s konkrétnymi hrami v projekte GameStudio, v ňom bude balík vašej hry
│  │  │           ├── server   // balík serverovej časti projektu
│  │  │           ├── service  // rozhrania a implementácie servisných komponentov
│  │  │           └── SpringClient.java  //hlavná trieda pre spustenie klientskej časti riešenia
│  │  └── resources  //zdroje projektu (obrázky, konfiguračné súbory atď.)
│  │     └── application.properties  //nastavenia projektu
│  └──  test  // adresár pre zdrojové súbory testov
├── .gitignore
├── pom.xml  //konfiguračný súbor Maven projektu

Celý projektový adresár je zároveň Maven projektom [1], ktorý sa konfiguruje prostredníctvom súboru pom.xml. Projekt je nakonfigurovaný ako Spring Boot [2] projekt využívajúci knižnicu JPA [3].

Úloha 1.4

Skontrolujte si nastavenia databázového pripojenia v súbore application.properties a podľa potrieb ich modifikujte.

Môžete použiť nastavenia konfiguračného súboru podľa prednášky zo 6. týždňa. Skontrolujte a aktualizujte si najmä nastavenie URL, prihlasovacie meno a heslo pre vašu databázu:

#https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
spring.datasource.url=jdbc:postgresql://localhost/gamestudio
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.maximumPoolSize=2
#spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

Krok 2

V tomto kroku pridáte do entitných tried anotácie, ktoré umožnia použitie technológie Java Persistence API - skrátene JPA.

Úloha 2.1

Pridajte do každej entitnej triedy servisných komponentov identifikátor ident slúžiaci pre jednoznačnú identifikáciu entity v databáze a k nemu patričné get a set metódy. Členskú premennú identifikátora označte anotáciami @Id a @GeneratedValue z balíka javax.persistence.*.

Upozornenie

V rámci zachovania pôvodnej funkcionality nebudeme modifikovať existujúci konštruktor, ale doplníme nový bezparametrický verejný konštruktor, ktorý je potrebný pre technológiu JPA.

Príklad na entite Score:

package sk.tuke.gamestudio.entity;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
...

public class Score implements Serializable {
    @Id
    @GeneratedValue
    private int ident; //identifikator
    ...
    
    //doplneny bezparametricky verejny konstruktor
    public Score() {}

    //povodny konstruktor
    public Score(String player, String game, int points, Date playedOn) {...}

    public int getIdent() { return ident; }
    public void setIdent(int ident) { this.ident = ident; }

    ...
    
}

Vytvoreným entitným triedam chýba už len anotácia označujúca entitnú triedu a pomenovaný dopyt, ktorý sa využije v servisnom komponente JPA.

Úloha 2.2

Anotujte entitné triedy anotáciou @Entity a anotáciou @NamedQuery, do ktorej vložte pomenovaný dopyt na databázu, slúžiaci pre využitie v servisnom komponente JPA. Príklad pre entitu Score:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
...

@Entity
@NamedQuery( name = "Score.getTopScores",
             query = "SELECT s FROM Score s WHERE s.game=:game ORDER BY s.points DESC")
@NamedQuery( name = "Score.resetScores",
             query = "DELETE FROM Score")
public class Score implements Serializable {
    ...
}

Krok 3

Úloha 3.1

Vytvorte nové implemetácie služieb pomocou JPA s názvami ScoreServiceJPA, CommentServiceJPA a RatingServiceJPA.

Príklad implementácie služby Score v JPA :

package sk.tuke.gamestudio.service;

import sk.tuke.gamestudio.entity.Score;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
. . .

@Transactional
public class ScoreServiceJPA implements ScoreService {
    
   @PersistenceContext
   private EntityManager entityManager;

   @Override
   public void addScore(Score score) throws ScoreException {
      entityManager.persist(score);
   }

   @Override
   public List<Score> getTopScores(String game) throws ScoreException {
       return entityManager.createNamedQuery("Score.getTopScores")
           .setParameter("game", game).setMaxResults(10).getResultList();
   }

    @Override
    public void reset() {
        entityManager.createNamedQuery("Score.resetScores").executeUpdate();
        // alebo:
        // entityManager.createNativeQuery("delete from score").executeUpdate();
    }
}

Vysvetlenie :

  • Anotácia @Transactional je anotácia definujúca novú transakčnú službu.
  • PersistenceContext - predstavuje niečo ako vyrovnávaciu pamäť. Má svoje vlastné, nezdieľané pripojenie na databázu (viac o JPA konceptoch).
  • EntityManager entityManager - je injektovaná inštancia správcu entít, reprezentuje PersistenceContext; do tejto premennej bude automaticky vložená jeho inštancia.
  • entityManager.persist(score) - uloží danú entitu do databázy pomocou správcu entít.
  • entityManager.createNamedQuery("Score.getTopScores") - využíva pomenovaný dopyt definovaný v anotácii @NamedQuery v triede Score na vytvorenie dopytu. Následne setParameter("game", game) nastaví parameter do tohto dopytu, getMaxResults(10) nastaví maximálny počet vrátených inštancií a getResultList() vráti výsledok dopytu vo forme zoznamu inštancií triedy Score.

Poznámka

V prípade, že dopyt vracia len jeden výsledok (napríklad ako v prípade priemerného hodnotenia v službe Rating), je potrebné namiesto metódy getResltList() použiť metódu getSingleResult().

Krok 4

V tomto kroku využijeme v našej hre vytvorené služby.

Úloha 4.1

Vytvorené JPA služby využite v hre rovnakým spôsobom ako služby JDBC vytvorené v predchádzajúcich cvičeniach. Ich inštancie však nebudete vytvárať ručne. Stačí, ak nad privátnu premennú služby pridáte anotáciu @Autowired. Príklad:

public class ConsoleUI {
    private Field field;

    @Autowired
    private ScoreService scoreService;

    ...
}

Spring automaticky injektuje do anotovanej členskej premennej (v tomto prípade scoreService) inštanciu služby daného typu (ScoreService).

Krok 5

V tomto kroku hru spustíme spolu s vytvorenými službami ako SpringBoot aplikáciu.

Úloha 5.1

Implementujte hlavnú triedu SpringClient a definujte v nej hlavné komponenty.

Poznámka

Trieda SpringClient by mala byť umiestnená v balíku sk.tuke.gamestudio. Ak ste si v prvom kroku cvičenia generovali novú kostru projektu, je možné, že vám prostredie vygenerovalo triedu GamestudioApplication. Tú môžete premenovať na SpringClient.

Príklad definovania hlavnej triedy pre hru Minesweeper je nasledovný:

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sk.tuke.gamestudio.game.mines.consoleui.ConsoleUI;
import sk.tuke.gamestudio.game.mines.core.Field;
import sk.tuke.gamestudio.service.ScoreService;
import sk.tuke.gamestudio.service.ScoreServiceJPA;

@SpringBootApplication
@Configuration
public class SpringClient {

    public static void main(String[] args) {
        SpringApplication.run(SpringClient.class, args);
    }

    @Bean
    public CommandLineRunner runner(ConsoleUI ui) {
        return args -> ui.play();
    }

    @Bean
    public ConsoleUI consoleUI(Field field) {
        return new ConsoleUI(field);
    }

    @Bean
    public Field field() {
        return new Field(9, 9, 1);
    }

    @Bean
    public ScoreService scoreService() {
        return new ScoreServiceJPA();
    }
}

Vysvetlenie:

  • Anotácie @Configuration a @SpringBootApplication označujú hlavnú triedu Spring Boot aplikácie.
  • Metóda main spúšťa projekt ako Spring Boot aplikáciu a definuje jej hlavnú triedu ako parameter metódy SpringApplication.run(...).
  • Všetky metódy označené anotáciou @Bean sú komponenty, ktorých inštancie sú automaticky vytvárané počas behu projektu. Spôsob ich inicializácie sa definuje vo vnútri týchto metód.
  • Hlavným komponentom aplikácie je komponent typu CommandLineRunner, ktorý predstavuje akýkoľvek komponent bežiaci v konzolovom rozhraní. V našom prípade definujeme, že týmto komponentom bude naše ConsoleUI a bude spúšťané metódou play().
  • Ďalšími definovanými komponentmi sú ConsoleUI a Field, kde v oboch metódach špecifikujeme ich inicializáciu.

Poznámka

Na názvoch metód označených anotáciou @Bean v princípe nezáleží, typy komponentov určujú návratové typy týchto metód. Je však dobrým zvykom pomenovať metódy podľa toho, aký komponent predstavujú.

Úloha 5.2

Podľa hore uvedeného vzoru vytvorte hlavnú triedu projektu v balíku sk.tuke.gamestudio tak, aby spúšťala konzolové rozhranie vašej hry. Spustite túto hlavnú triedu.

Pred prvým spustením odporúčame zmazať a nanovo vytvoriť databázu gamestudio. Tabuľky nevytvárajte, budú vytvorené automaticky. Aj to je jedna z užitočných vlastností JPA.

Po dokončení potrebných úprav spustite vašu hru a overte jej funkčnosť aj funkčnosť komponentov služieb. Hra by sa už mala spúšťať prostredníctvom Spring Boot.

Krok 6

Úloha 6.1

Nahrajte vašu implementáciu do vášho repozitára na GitLab-e. Projekt môžete ďalej priebežne aktualizovať. Pred ďalším cvičením sa uistite, že máte v repozitári vaše aktuálne súbory. Zároveň si pripravte otázky, ktoré by ste na cvičení chceli vyriešiť.

Zdroje

  1. Čo je Maven? https://maven.apache.org/what-is-maven.html
  2. Čo je Spring Boot? https://www.quora.com/What-is-the-difference-between-Spring-Boot-and-the-Spring-framework
  3. Čo je JPA? https://en.wikibooks.org/wiki/Java_Persistence/What_is_JPA%3F