Motivation
In this exercise, your task will be to implement service components using JPA (Java Persistence API) technology and Spring, and use these services in your game.
Objectives
- Update the project structure.
- Annotate entity classes so that they can be used with JPA services in the game.
- Implement service components using JPA.
- Use the created services in your game.
- Adapt your project to run with Spring Boot .
Instructions
Step 1
In the previous exercise, you added Maven to manage your project's dependencies. This time, it will allow downloading and managing libraries for JPA and Spring technologies.
Warning
Before adding new dependencies to the Maven configuration file pom.xml, make sure you have a working internet connection; otherwise, Maven will not be able to download the libraries correctly.
Task 1.1
Check and update your pom.xml configuration file.
In the configuration file, inside the project element, add the following parent element and the dependency elements inside dependencies, as shown below:
<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>
Comment
After updating pom.xml, reload the configuration so that Maven downloads the new libraries - right-click pom.xml and choose Maven > Reload Project.
Task 1.2
Check and, if needed, update your project structure.
The basic structure of the project 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 for games
│ │ │ ├── server // application server side package
│ │ │ ├── service // interfaces and implementation of service components
│ │ │ └── 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 a Maven project [1] using the pom.xml configuration file. It is also a Spring Boot project [2] and uses the JPA library [3].
Task 1.3
Add or update the application.properties file in your project.
If the file is missing, create it in src/main/resources/ as shown in the project structure above.
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 database connection-related properties, namely url, username, and password, according to your database settings.
Step 2
In this step you will add annotations to entity classes that allow using the Java Persistence API - in short JPA.
Task 2.1
Add an ident instance variable to every entity class of the service components, which will serve as an identifier for entries in the database. Also add the appropriate get and set methods for proper encapsulation. Annotate the identifier field with @Id and @GeneratedValue from javax.persistence.*.
Warning
To preserve the original functionality, do not modify the existing constructor; instead, add a new public parameterless constructor, which is required by JPA.
An example for the Score entity:
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; }
...
}
The entity classes now only need the annotation marking them as entity classes and the named query that will be used in the JPA service component.
Task 2.2
Annotate the entity classes with @Entity and @NamedQuery, and place into it a named database query intended for use in the JPA service component. Example for the Score entity:
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 {
...
}
comment: <> (> For the Rating class, you will need two named queries (for the getRating() method and the getAverageRating() method). In this case, you must use the container annotation @NamedQueries from the same package.)
Step 3
Task 3.1
Create new service implementations using JPA with the names ScoreServiceJPA, CommentServiceJPA, and RatingServiceJPA.
Example implementation of the Score service in 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
@Transactionaldefines a new transactional service. PersistenceContext- represents something like a cache. It has its own, unshared database connection (more about JPA concepts).EntityManager entityManager- an injected entity manager instance representingPersistenceContext; its instance will be automatically injected into this variable.entityManager.persist(score)- stores the given entity in the database through the entity manager.entityManager.createNamedQuery("Score.getTopScores")- uses the named query defined in the@NamedQueryannotation in classScoreto create a database query. ThensetParameter("game", game)sets the query parameter,getMaxResults(10)limits the number of returned entries, andgetResultList()returns the query result as a list ofScoreinstances.
Comment
If a query returns only a single result (for example, average rating in the Rating service), it is necessary to use the getSingleResult() method instead.
Step 4
In this step you will integrate the created services into your game.
Task 4.1
Use the implemented JPA services in your game in the same way as the JDBC services from the previous exercise. However, you will not create their instances manually. Instead, add the @Autowired annotation above the service's private field. Example:
public class ConsoleUI {
private Field field;
@Autowired
private ScoreService scoreService;
...
}
Spring will automatically inject an instance of the given type (ScoreService) into the annotated field (in this case scoreService).
Step 5
In this step, the game will be run together with the new service implementation as a Spring Boot application.
Task 5.1
Implement the SpringClient main class and define the main components in it. Use the example below as inspiration.
Comment
The SpringClient class should be located in the sk.tuke.gamestudio package. If you generated a new project skeleton in the first step of the exercise, the IDE may have generated a GamestudioApplication class for you. You may rename it to SpringClient.
An example of a main class for the Minesweeper game is the following:
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:
- The annotations
@Configurationand@SpringBootApplicationmark the main class of the Spring Boot application. - The
mainmethod runs the project as a Spring Boot application and defines its main class as the argument ofSpringApplication.run(...). - All methods annotated with
@Beanare components whose instances are automatically created during runtime. Their initialization is defined inside the methods. - The main component of the application is a
CommandLineRunner, which represents any component that runs in a console interface. In our case, it is theConsoleUI, which will be started by callingplay(). - Other defined components are
ConsoleUIandField, where both methods define their initialization.
Comment
In principle, the names of methods annotated with @Bean do not matter; the return types of these methods define the component types. However, it is good practice to name the methods according to the component they represent.
Task 5.2
Based on the example above, create the main class of your project in the sk.tuke.gamestudio package so that it runs the console interface of your game. Run this main class.
Warning
Before the first run, we recommend deleting and recreating the gamestudio database (create-drop in application.properties). Do not create tables manually; they will be created automatically based on the annotated entities. This is one of the useful features of JPA.
After completing the required adjustments, run your game and verify its functionality as well as the functionality of the service components. The game should now run through Spring Boot.
Step 6
Task 6.1
Upload (commit and push) your implementation to your GitLab repository. You can update the project as you continue working on it. Before the next exercise, make sure your latest files are in the repository. Also prepare questions you would like to discuss in 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