Skip to main content

Repositories

Learn

What is a Repository?​

A repository is Spring's abstraction of the data access layer. In this bootcamp, we use the JPA (Java Persistence API) to implement the repository.

The core functionality of every API is to serve data stored in a database. The repository interface is agnostic to the back-end, be that an in-memory database, an SQL instance, or some flavor of no-SQL.

What is an entity?​

An entity is a Java class that can be mapped to a JPA repository. To create an entity with Spring, two annotation are required at minimum:

  1. @Entity over the class definition and
  2. @Id over the ID field.

What is a DTO and why do we use them?​

In a Spring application, a data transfer object (or DTO) is a POJO that mirrors and entity. It contains all the same fields as the entity, but none of the functionality (except getter and setters). We use DTOs to encapsulate relevant data for transport. As you'll see in the exercise, our GET endpoint will now accept and return a DTO.

Because DTOs mirror entities, translating a DTO to an entity and vice versa is very simple. We will use a transformer class to do this in the exercise.


Do​

1. Add dependencies​

In order to add a data persistence layer, we need to add some new dependencies to build.gradle:

build.gradle
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
...
}
Important

There are two groups (or closures) named dependencies: one within the buildscript closure at the top of the file and one on its own further down. We want to add there dependencies to the standalone closure.

The first dependency adds JPA to the project, while the second add our database options: H2, in-memory database.

Make sure to refresh your Gradle project after adding these dependencies. IntelliJ may also prompt you to choose whether or not you'd like to auto-import changes. You may do so if you with, but it isn't necessary.

2. Write StudentEntity and StudentRepository​

We will use our existing Student POJO as the DTO of our service rather than creating a new DTO class. We will create a new StudentEntity class to use with the repository.

In the src/main/java/com.geteche.students directory, create a new package classed entity.

In the newly create package, create a new Java class called StudentEntity.java.

This class should have the same two fields as Student (id and name), a default constructor, parameterized constructor, and getters, just like our original Student class.

StudentEntity.java
@Entity 
@Table(name = "student")
public class StudentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
...
}
  • As mentioned above, @Entity designated our class as a Spring entity.

  • @Table tells the JPA which database table this entity is stored in. we don't technically need it for this example, but in a real-world application with a complex, pre-existing database, it is a necessary.

  • @Id denoted which field stores the ID (i.e the primary key) of the entity in the database.

  • @GeneratedValue tells Spring that the following field should be automatically generated. This is commonly used on Id fields (as we are using it) to ensure that every instance of the entity has a unique ID.


The repository should be created as an interface within a new package named repository.

In the src/main/java/com.geteche.students directory, create a new package called repository.

In the newly created package, create a new Java interface called StudentRepository.java.

StudentRepository.java
public interface StudentRepository extends JpaRepository<StudentEntity, Integer> {}

The two values within the angle bracket (<>) are the entity stored in this repository and the type of that entity's ID, respectively. This is why the idfield of our Student class is an Integer instead of an int; and int isn't an object, and thus can't be used as a parameter of a genericized class.

3. Refactor StudentService​

We do not want StudentService to be creating our Student objects directly anymore. We want to use methods of our StudentRepository to retrieve them. We also need to update the service to return StudentEntity objects rather than Student objects.

The JpaRepository interface (which our StudentRepository extends) provides us with several methods for performing basic CRUD (create, read, update, delete) operations. We can also define our own methods in our repositories, but for the bootcamp, we won't need to.

StudentService.java
@Service 
public class StudentService {

@Autowired
StudentRepository studentRepository;

public StudentEntity getStudentById(Integer id) {
Optional<StudentEntity> myStudent = studentRepository.findById(id);
}
...
}

4. Write StudentTransformer​

For the transformer, in the src/main/java/com.geteche.students directory, create a new package called transformer.

In the newly created package, create a new Java class called StudentTransformer.java.

The transformer only needs two static methods: one to convert from a StudentEntity to Student and one to convert from a Student to a StudentEntity.

info

We are writing this transformer to demonstrate the process, but in an actual application, you might want to use an external library such as MapStruct to reduce the amount of code you have to write.

StudentTransformer.java
public class StudentTransformer {

public static Student toStudent(StudentEntity fromStudent) {
...
}

public static StudentEntity toStudentEntity(Student fromStudent) {
...
}
}

In addition, we need to refactor the getStudentById() method in our StudentController to utilize the transformer. More specifically, the method returns a Student, so we will have to convert the StudentEntity we receive from StudentService to a Student before returning it.

StudentController.java
  ...

public Student getStudentById(@PathVariable("StudentId") Integer id) {

return StudentTransformer.toStudent(StudentService.getStudentById(id));
}

5. Write POST endpoint​

Now that we are using our repository, we need to be able to write to it. For this, we will be creating a new endpoint in StudentController.

Just like our GET endpoint, our new POST endpoint requests an annotated method in the controller. The header for the controller method is provided below:

StudentController.java
@PostMapping(
value = "/v1/students",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)

public Student createStudent(@RequestBody Student student) {
...
}

In addition, we need to write a createStudent() method in StudentService to actually perform the logic.

StudentService.java
  public StudentEntity createStudent(StudentEntity studentToCreate) {
...
}

6. Update Configuration files​

Now that we've implemented a Spring repository, we need a database to back it, For now, we will use an H2 in-memory database. To add this, add the following lines to the end of src/main/resources/application.properties:

src/main/resources/application.properties
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

7. Write tests​

In this step, we will:

  • Update existing unit and integration tests.
  • Write and additional test in StudentControllerTest and StudentServiceTest for the createStudent() method.
  • Add a new unit test for StudentTransformer.
  • Update the integration test for first create a Student before retrieving it.
  • Below are just example if you stuck, Please tru on your own first.

note

We do not need to write any unit tests for StudentRepository because we did not write any new methods for it. we assume that the writers of the JPA have thoroughly tested its functionality. However, if we had written our own methods in StudentRepository, we would need to test them.