7. week

Task 5 - Implementing service components using JPA

Motivation

In this exercise your task will be to implement service components using JPA (Java Persistence API) technology and Spring, and use the services in your game.

Objectives

  1. Update the project structure.
  2. Annotate entity classes that allow using JPA services in game.
  3. Implement service components with JPA.
  4. Use created services in your game.
  5. Adjust your project to run with Spring Boot .

Instructions

Step 1

In the previous exercise you added Maven to manage dependencies of your project. This time it will allow downloading and managing libraries for technologies JPA and Spring.

Warning

Before you add new dependencies into Maven configuration file pom.xml, make sure that you have functioning internet connection, otherwise Maven will not be able to download the libraries correctly.

Task 1.1

Check and update your configuration file pom.xml.

In the configuration file, inside the project element, add the following parent element and dependency elements inside dependencies, as shown below:

<project ...
    ...
    <groupId>sk.tuke</groupId>
    <artifactId>gamestudio</artifactId>
    <version>0.0.1-SNAPSHOT</version>

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

    <dependencies>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <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>
    </dependencies>

</project>

Comment

After updating the file pom.xml, reload the configuration to let Maven download new libraries - right-click on the file pom.xml and select Maven > Reload Project.

Task 1.2

Check and, if needed, update your project structure.

Basic structure of project's directories should be as follows:

gamestudio // main directory of the project
├── src
│  ├── main // directory with project's source files
│  │  ├── java
│  │  │  └── sk
│  │  │     └── tuke
│  │  │        └── gamestudio  // main package of project GameStudio
│  │  │           ├── entity   // package for entity classes
│  │  │           ├── game     // package that should contain source code of your game
│  │  │           ├── server   // package for server side of the application
│  │  │           ├── service  // interfaces and implementation of service somponents
│  │  │           └── SpringClient.java  // main class for running the client side of the application
│  │  └── resources  // resources of the project (images, configuration files, etc.)
│  │     └── application.properties  // file containing project properties
│  └──  test  // directory with source files of tests
├── .gitignore
├── pom.xml  // Maven configuration file

The project is managed as Maven project [1] using configuration file pom.xml. It is also a Spring Boot project [2] and uses the JPA library [3].

Task 1.3

Add or update the file application.properties in your project.

If the file is missing, create it inside the directory src/main/resources/ as shown in the project structure in the previous task.

The file should contain the following properties:

#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

Update the properties relating to database connection, namely url, username, and password, according to your database settings.

Step 2

In this step you will add annotations into entity classes, which will allow using the technology Java Persistence API - in short JPA.

Task 2.1

Into every entity class (Score, Comment, Rating) add an instance variable ident that will be used as an identifier for entries in the database. Add also get and set methods for proper encapsulation of the variable. Annotate the instance variable with annotations @Id and @GeneratedValue from package javax.persistence.*. Add a parameterless constructor.

Warning

The JPA technology requires a parameterless constructor in entity classes. To preserve the constructor that we created earlier, create a new one that will have no parameter.

An example of entity class Score:

package sk.tuke.gamestudio.entity;

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

public class Score implements Serializable {
    @Id
    @GeneratedValue
    private int ident; // identifier for DB entries
    ...
    
    // parameterless constructor which we added now 
    public Score() {}

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

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

    ...
    
}

Task 2.2

Annotate your entity classes with annotations @Entity and @NamedQuery in which define a database query, labelled with some name, which will be used for querying data from the database in the implementation of JPA service component. An example for entity 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 {
    ...
}

Step 3

Task 3.1

Create new implementation of services using the JPA technology. Name the new classes ScoreServiceJPA, CommentServiceJPA and RatingServiceJPA.

Annotate each class with the annotation @Transactional. Also, do not forget to declare an instance variable for EntityManager, annotated with @PersistenceContext.

An example of implementation of the service Score with 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();
    }
}

Explanation :

  • The annotation @Transactional defines new transactional service.
  • PersistenceContext - represents a context that could be described as cache. It has its own, unshared connection to the database (more about JPA concepts).
  • EntityManager entityManager - an instance of entity manager will be automatically injected into the variable of type EntityManager. It represents PersistenceContext.
  • entityManager.persist(score) - will store an entry of given entity into the database through the entity manager.
  • entityManager.createNamedQuery("Score.getTopScores") - it uses a named query for creating a database query, identified by given string, which was defined in the annotation @NamedQuery inside the Score class. After that, setParameter("game", game) will set the parameter of the query, getMaxResults(10) will set the maximal number of retrieved entries, and getResultList() will return the result of the query as a list of Score instances.

Comment

In case the query retrieves only a single value (e.g. in case of average rating of the game in the service Rating), it is required to use the method getSingleResult() instead.

Step 4

In this step you will integrate the created services into the game.

Task 4.1

Use the implemented JPA services in your game, in the same way as JDBC services from the previous exercise. This time, however, you will not create their instances explicitly, just add an annotation @Autowired on top of the private instance variable of the service. An examaple:

public class ConsoleUI {
    private Field field;

    @Autowired
    private ScoreService scoreService;

    ...
}

The Spring framework will automatically inject an instance of given type (ScoreService) into annotated instance variable (in this case variable scoreService).

Step 5

In this step you will run the game together with new implementation of services as a SpringBoot application.

Task 5.1

Implement the main class SpringClient and define all necessary components in it.

Comment

The class SpringClient needs to be located in the package sk.tuke.gamestudio.

The class should be annotated with @SpringBootApplication and @Configuration. The components will be created in methods annotated with @Bean.

An example of the main class for game Minesweeper is as follows:

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();
    }
}

Explanation:

  • Annotations @Configuration and @SpringBootApplication mark the main class of the Spring Boot application.
  • Method main() runs the project as Spring Boot application and defines its main class as a parameter of method SpringApplication.run(...).
  • All methods annotated with @Bean are components, which are created automatically at project runtime. The way of component initialisation is defined inside the method.
  • The main component of the application is a component of type CommandLineRunner, which represents any component that runs in a console user interface. In our case we define that the component will be our ConsoleUI and it will be started by method play().
  • Other defined components are ConsoleUI, Field and ScoreService. Inside their methods we define their initialisation.

Comment

In principle, it doesn't matter how you name the methods annotated with @Bean. The type of the component is specified by the return type of the method. However, it is a good practice to name the methods according to components they represent.

Task 5.2

Based on the example shown in previous task, create the main class of your project in package sk.tuke.gamestudio so that it will run the console user interface of your game.

Warning

Before running the project with recent changes, we suggest dropping the database tables. Do not create them again manually, because the JPA technology will create them automatically based on the definition of entity classes.

When you finish the required adjustments, run the project through the class SpringClient and verify its functionality, and also functionality of service components. The game should be running through Spring Boot.

Step 6

Task 6.1

Upload (commit and push) your implementation into your repository on GitLab. You can update the project as you gradually work on it. Before the next exercise, make sure you uploaded your latest work into your repository. Also, prepare questions that you would like to discuss on the next exercise.

Resources

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