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
- Update the project structure.
- Annotate entity classes that allow using JPA services in game.
- Implement service components with JPA.
- Use created services in your game.
- 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 typeEntityManager
. It representsPersistenceContext
.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 theScore
class. After that,setParameter("game", game)
will set the parameter of the query,getMaxResults(10)
will set the maximal number of retrieved entries, andgetResultList()
will return the result of the query as a list ofScore
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 methodSpringApplication.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 ourConsoleUI
and it will be started by methodplay()
. - Other defined components are
ConsoleUI
,Field
andScoreService
. 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
- What is Maven? https://maven.apache.org/what-is-maven.html
- What is Spring Boot? https://www.quora.com/What-is-the-difference-between-Spring-Boot-and-the-Spring-framework
- What is JPA? https://en.wikibooks.org/wiki/Java_Persistence/What_is_JPA%3F