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
Úloha 1.1
Aktualizujte a skontrolujte si konfiguračný súbor pom.xml.
Na tomto cvičení začneme využívať technológiu Spring. Najjednoduchšou cestou je nastaviť projekt tak, aby sa spúšťal ako Spring Boot projekt. To urobíte v konfiguračnom súbore pom.xml pridaním nového elementu parent (na miesto, kde je identifikátor a verzia projektu) 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>
Do sekcie dependencies pridajte závislosti potrebné pre ďalší postup na cvičení a odstráňte verzie závislostí (do tohto momentu už ich bude spravovať Spring parent). Výsledok by mal vyzerať nasledovne:
<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>
Projekt je teda nakonfigurovaný ako Spring Boot [2] projekt využívajúci knižnicu JPA [3].
Nová š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 hrami
│ │ │ ├── server // balík serverovej časti projektu
│ │ │ ├── service // rozhrania a implementácie servisných komponentov
│ │ │ └── SpringClient.java //nová 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
Úloha 1.2
Skontrolujte si nastavenia databázového pripojenia v súbore application.properties a podľa potrieb ich modifikujte. Odteraz sa budeme na databázu pripájať pomocou JPA, takže je potrebné, aby boli nastavenia správne.
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 #premaže databázu pri každom spustení a vytvorí ju nanovo
spring.jpa.hibernate.ddl-auto=update #aktualizuje databázu bez jej zmazania
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
@Transactionalje 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@NamedQueryv triedeScorena 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 pomocou novej hlavnej triedy 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. Inšpirujte sa príkladom nižšie.
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
@Configurationa@SpringBootApplicationoznačujú hlavnú triedu Spring Boot aplikácie. - Metóda
mainspúšť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
@Beansú 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šeConsoleUIa bude spúšťané metódouplay(). - Ďalšími definovanými komponentmi sú
ConsoleUIaField, 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 (create-drop v application.properties). Tabuľky nevytvárajte, budú vytvorené automaticky na základe anotovaných entít. 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ť.