Spring Data JPA Entities & Repositories

We need to write Java code to interact with the tables so that we can start saving the data into the tables. We can read from the tables. We can update and delete the data from the tables for the same.

Add the required dependency

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

The dependency we use for working with Spring Data JPA. This is the library under which we have many interfaces and classes, which will help us to interact with the database tables whenever we want to use this framework.

Create POJO classes for Entity

First, we need to create the POJO classes representing each of the tables that we have inside the database which we intend to use.

For the same, I’m going to create a new package, and the package name is going to be an entity. Inside this entity package, we are going to store all the entity classes of my database.

Creating Base Entity class

Inside both accounts and customer table, we have 4 columns : 

These 4 columns are the metadata columns that we maintain in each of the tables. So it is a good idea to create a separate superclass that will maintain the details of these 4 columns.

Let’s create a BaseEntity class and create 4 different fields, each representing one of the metadata columns.

NOTE — Please make sure this field names are matching with your column names that you have created inside the table.

This way we don’t have to tell the Spring Data JPA framework to which column this field is mapped inside my database.

As a next step, we are supposed to create the getter and setter methods for these fields, and creating all those getters and setters will be a very cumbersome process. And at the same time, the code looks very lengthy, which we don’t want.

That’s why we need to use our Lombok related annotations.

@Getter
@Setter 

– add these two annotations at the top of the class. So when we mention these annotations, behind the scenes, Lombok library is going to generate the getter and setter methods.

@ToString – which will help us to convert these objects to the string format.

package com.eazybytes.accounts.entities;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.time.LocalDateTime;

@Getter
@Setter
@ToString
public class BaseEntity {
private LocalDateTime createdAt;
private String createdBy;
private LocalDateTime updatedAt;
private String updatedBy;
}

After these annotations have been added to the class, now the class will have getter and setter methods for each of the fields and also toString() method.

Behind the scenes, Lombok is going to do the magic of generating them inside the bytecode of your classes.

@MappedSuperclass annotation

Since we are trying to leverage this base entity class, BaseEntity, for all the other entity classes that we are going to create inside our project, we need to mention an annotation which is @MappedSuperclass.

This indicates to the Spring Data JPA framework that this class is going to act as a superclass for all my other entity classes, wherever I’m trying to extend this BaseEntity class.

@Column annotation

We can also mention @Column annotation at the top of each column. 

Using this, @Column annotation, we can mention what is the column name inside the database that we are trying to map to this field.

But since the column name and the field names are matching, we don’t need to mention the column name manually with the help of the @Column annotation.

For the columns, createdAt, createdBy – we don’t want them to be updated whenever data is updated. They need to be added only when the first time data is added.

For this purpose, we are going to add : 

@Column(updatable = false)
private LocalDateTime createdAt;

@Column(updatable = false)
private String createdBy;

The purpose of this updatable = false is whenever a record is being updated inside the database table, I don’t want this column to be considered by the Spring Data JPA to populate the value and to update the value, which means this field will not be updated whenever I’m trying to update my record, because I only want to maintain this createdAt time to represent the what is the original time when my record is inserted.

Same is the case for createdBy also.

@Column(insertable = false)
private LocalDateTime updatedAt;

@Column(insertable = false)
private String updatedBy;

insertable = false – this tells to my Spring Data JPA framework, please do not populate our updatedAt, updatedBy – these two columns whenever it is trying to insert a very new record inside the database.

Of course, this makes sense because while we are trying to insert the record for the very first time, why do we want to update these two columns? We need to maintain them as null values.

Complete BaseEntity class

@MappedSuperclass
@Getter
@Setter
@ToString
public class BaseEntity {
@Column(updatable = false)
private LocalDateTime createdAt; // maps to column : `created_at` date NOT NULL

@Column(updatable = false)
private String createdBy; // maps to column : `created_by` varchar(20) NOT NULL

@Column(insertable = false)
private LocalDateTime updatedAt; // maps to column : `updated_at` date DEFAULT NULL

@Column(insertable = false)
private String updatedBy; // maps to column : `updated_by` varchar(20) DEFAULT NULL
}

This class is going to act as a superclass for all my other entities, wherever I’m trying to extend this BaseEntity class.

Create POJO class to represent ‘customer’ table

Create a new class which represents the customer table – create a class Customer with the same name as the table name customer.

@Entity annotation

This @Entity annotation tells my Spring framework to treat this POJO class as an entity representation or a POJO representation for my table with the name customer.

Here, the class name ‘Customer’ matches with the table name ‘customer’.

@Table annotation

If the class name and table name doesn’t match, then we have to use @Table annotation.

And to this @Table annotation you can pass the name parameter, to which you can mention whatever table name that you have inside the database.

import jakarta.persistence.Entity;
import jakarta.persistence.Table;

// this is the entity class that represents customer table in the db
@Entity
@Table(name = "customer")
public class Customer {

}

Since right now the class name and table name are matching, I don’t want to use @Table annotation.

Customer entity POJO class

@Entity
public class Customer extends BaseEntity {
private Long customerId; // maps to column name : `customer_id`
private String name; // maps to column name : `name`
private String email; // maps to column name : `email`
private String mobileNumber; // maps to column name : `mobile_number`
}

We have created all the fields with the same names like we mentioned inside the table.

Now, since we also have metadata related columns inside our table, we need to extend the entity class that we have created previously, which is BaseEntity.

So once we define these details, all the fields that we have inside the BaseEntity will be considered by the Spring Data JPA framework whenever it is trying to insert a new record, or update a record, or delete a record.

We are also going to specify Lombok related annotations to my Entity class.

@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Customer extends BaseEntity {
private Long customerId; // maps to column name : `customer_id`
private String name; // maps to column name : `name`
private String email; // maps to column name : `email`
private String mobileNumber; // maps to column name : `mobile_number`
}

With the help of @AllArgsConstructor, my Lombok is going to generate a constructor which accepts all the fields that we have inside this POJO class, and very similarly with @NoArgsConstructor, it is going to generate an empty/default constructor.

When we are trying to create the object, obviously we need the constructor like all args constructor, and no args constructor.

How to specify the primary key column to the Spring framework?

@Id annotation

customerId field is going to map to the primary key column. But how do I communicate that to the Spring Data JPA framework?

Using this @Id annotation on a field, we tell Spring framework that this field will represent the primary key column.

@GeneratedValue annotation

Q) Are we going to provide the primary key value manually to the Spring Data JPA framework?

We want the Spring Data JPA framework to automatically generate a primary key value whenever it is trying to insert a new record into the table. It is a good idea to give the responsibility to the Spring Data JPA framework.

@GeneratedValue(strategy = GenerationType.IDENTITY)

Using this annotation, we are trying to tell the Spring Data JPA framework to please automatically generate the primary key values.

Customer.java Entity class

import jakarta.persistence.*;
import lombok.*;


// this is the entity class that represents customer table in the database
@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Customer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long customerId; // maps to column name : `customer_id`
private String name; // maps to column name : `name`
private String email; // maps to column name : `email`

@Column(name = "mobile_number")
private String mobileNumber; // maps to column name : `mobile_number`
}

Accounts.java Entity class

// this is the entity class that represents accounts table in the database
@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Accounts extends BaseEntity {
@Column(name="customer_id")
private Long customerId; // maps to column name : `customer_id`

@Column(name="account_number")
@Id
private Long accountNumber; // maps to column name : `account_number`

@Column(name="account_type")
private String accountType; // maps to column name : `account_type`

@Column(name="branch_address")
private String branchAddress; // maps to column name : `branch_address`
}

In the accounts table customer_id is not going to be the primary key value. It is only present to establish the link between accounts table and customer table.

For the accountNumber field, we are not going to use @GeneratedValue annotation to generate the primary key value automatically. The reason is I am going to write a small logic inside my REST API service to generate the account number manually and assign the same into the database.

We don’t want our bank account numbers to start with sequence numbers like 1, 2, 3, 4. That won’t look good. Account number usually will be a ten digit number. That’s why as a developer, it is my responsibility to generate the account number.

And with that reason I have not mentioned the annotations related to primary key generation to the accountNumber field.

So far, we have successfully created entity classes representing each of the table that we have created inside our database.

JPA Repository classes

With the Entity classes, we can only store our table data into the entity POJO class (or) to represent table data as POJO objects.

But we need some logic that will take the object of these entity classes and interact with the database tables that we have created.

For the same, we need to create the repository interfaces inside our web application.

We create a repository package and we put interfaces that allow it to work with the underlying database and each repository interface maps to each database table.

@Repository annotation

Whenever I’m mentioning this annotation on top of my interface class, behind the scenes Spring Data JPA framework will create a bean implementation of this interface based upon the configurations that I’m going to provide.

import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository {
}

This is just an empty interface. The Spring Data JPA framework cannot create an object or bean from it. 

→ In order to bring the magic of Spring Data JPA into this interface, we need to extend a class from the Spring Data JPA framework, which is JpaRepository.

@Repository
public interface CustomerRepository extends JpaRepository<> {
}

To this JPA repository, it is going to accept 2 parameters.

  • One is what is the entity class, which is going to be handled by this repository class.
  • The second parameter is what is the data type of your primary key field inside your entity class.

So if you open this customer table the primary key data type is long. That’s why we need to mention the same here.

import com.eazybytes.accounts.entities.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

So whenever I’m creating an interface with @Repository annotation and that is extending JpaRepository, behind the scenes, Spring Data JPA framework is going to generate the runtime code by providing many methods.

All these methods are coming from the framework.

You can execute CRUD operations like find, delete, update and insert. There are many find methods that will help me to find the records from the database.

Similarly, we have save methods to insert and update the records inside the database.

This way you are able to get all these methods from the Spring Data JPA framework without writing any SQL manually.

Similarly, create a repository interface for mapping to accounts table.

import com.eazybytes.accounts.entities.Accounts;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AccountsRepository extends JpaRepository<Accounts, Long> {
}

With the help of these interfaces, we can invoke the methods from the framework to perform the CRUD operations.

We have created the repository interfaces using which we can interact with the database tables by using the methods available inside the Spring Data JPA framework.

So far the file structure looks like this :