Ciele
- Aktualizovať štruktúru projektu
GameStudio
. - Anotovať entitné triedy umožňujúce použitie JPA služby v hre.
- Implementovať servisné komponenty pomocou JPA.
- Využiť vytvorené služby vo vašej hre.
- 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).
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.
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).
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, reprezentujePersistenceContext
; 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 triedeScore
na vytvorenie dopytu. NáslednesetParameter("game", game)
nastaví parameter do tohto dopytu,getMaxResults(10)
nastaví maximálny počet vrátených inštancií agetResultList()
vráti výsledok dopytu vo forme zoznamu inštancií triedyScore
.
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ódySpringApplication.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šeConsoleUI
a bude spúšťané metódouplay()
. - Ďalšími definovanými komponentmi sú
ConsoleUI
aField
, 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ť.