Introduction
In this tutorial, we will learn how to use the DTO (Data Transfer Object) pattern in a Spring Boot application. Martin Fowler describes the Data Transfer Object in his famous book Patterns of Enterprise Application Architecture. The main idea of DTOs is to reduce the number of remote calls that are expensive.
Data Transfer Object Design Pattern is a frequently used design pattern. It is basically used to pass data with multiple attributes in one shot from client to server to avoid multiple calls to a remote server.
Benefits of Using DTO Pattern in Spring Boot
Loose Coupling: Using DTOs decouples the API layer from the persistence layer. This prevents direct exposure of entity classes and promotes a more modular and flexible architecture.
Improved Security: DTOs help in preventing the overexposure of sensitive data by including only the necessary fields required by the client. This minimizes the risk of exposing sensitive information like passwords or internal IDs.
Better Performance: By transferring only the required data, DTOs reduce the amount of data being sent over the network. This leads to better performance, especially in distributed systems where network latency and bandwidth are critical factors.
Simplified API Design: DTOs provide a clear and concise API design by exposing only the necessary data fields. This makes the API easier for clients to understand and use.
Validation and Transformation: DTOs can be used to validate and transform data before it reaches the business logic layer. This ensures that the data is in the correct format and meets the required constraints before being processed.
Compatibility with Different Clients: DTOs can be customized to fit the needs of different clients, such as web browsers, mobile apps, or other services. This allows the API to serve different clients with tailored data representations without changing the underlying business logic.
Let’s implement the Spring Boot DTO example step by step.
Technologies Used
- Java 21
- Spring Boot
- Spring Data JPA (Hibernate)
- H2 in-memory database
- Maven
- Lombok
- Eclipse STS IDE
1. Create a Spring Boot Project
We’ll use Spring Initializr to bootstrap our application.
- Go to Spring Initializr
- Select Java in the language section.
- Enter Artifact as spring-dto-tutorial
- Add Spring Web, Lombok, Spring Data JPA, and H2 dependencies.
- Click Generate to generate and download the project.
- Once the project is generated, unzip it and import it into your favorite IDE.
2. Maven Dependencies
Open the pom.xml
file and confirm the following dependencies are present:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.4.4</version>
</dependency>
</dependencies>
3. Model Layer
Let’s create User
and Location
JPA entities and establish a many-to-one relationship between them.
User JPA Entity
package net.javaguides.springboot.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String email;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String password;
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "location_id")
private Location location;
}
Explanation
@Entity
and@Table
: Indicate that this class is a JPA entity and maps to theusers
table.@Id
and@GeneratedValue
: Specify the primary key and its generation strategy.@Column
: Customizes the mapping between the field and the column.@ManyToOne
and@JoinColumn
: Define the many-to-one relationship with theLocation
entity.
Location JPA Entity
package net.javaguides.springboot.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "locations")
public class Location {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String place;
private String description;
private double longitude;
private double latitude;
}
Explanation
@Entity
and@Table
: Indicate that this class is a JPA entity and maps to thelocations
table.@Id
and@GeneratedValue
: Specify the primary key and its generation strategy.
4. Repository Layer
Let’s create Spring Data JPA repositories to interact with the database.
UserRepository
package net.javaguides.springboot.repository;
import net.javaguides.springboot.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
Explanation
JpaRepository
: Provides CRUD operations for theUser
entity.
LocationRepository
package net.javaguides.springboot.repository;
import net.javaguides.springboot.model.Location;
import org.springframework.data.jpa.repository.JpaRepository;
public interface LocationRepository extends JpaRepository<Location, Long> {
}
Explanation
JpaRepository
: Provides CRUD operations for theLocation
entity.
5. DTO Layer
Let’s create a DTO to transfer data between the server and the client.
UserLocationDTO
package net.javaguides.springboot.dto;
import lombok.Data;
@Data
public class UserLocationDTO {
private long userId;
private String email;
private String place;
private double longitude;
private double latitude;
}
Explanation
@Data
: Lombok annotation that generates getters, setters, and other utility methods.
6. Service Layer
Let’s create a service class and keep the entity-to-DTO conversion logic in the service class.
UserService
package net.javaguides.springboot.service;
import net.javaguides.springboot.dto.UserLocationDTO;
import net.javaguides.springboot.model.User;
import net.javaguides.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<UserLocationDTO> getAllUsersLocation() {
return userRepository.findAll()
.stream()
.map(this::convertEntityToDto)
.collect(Collectors.toList());
}
private UserLocationDTO convertEntityToDto(User user) {
UserLocationDTO userLocationDTO = new UserLocationDTO();
userLocationDTO.setUserId(user.getId());
userLocationDTO.setEmail(user.getEmail());
userLocationDTO.setPlace(user.getLocation().getPlace());
userLocationDTO.setLongitude(user.getLocation().getLongitude());
userLocationDTO.setLatitude(user.getLocation().getLatitude());
return userLocationDTO;
}
}
Explanation
@Service
: Marks the class as a service provider.convertEntityToDto
: Converts aUser
entity to aUserLocationDTO
.
7. Controller Layer
Let’s create a controller and expose a simple REST API that returns a list of DTO objects.
UserController
package net.javaguides.springboot.controller;
import net.javaguides.springboot.dto.UserLocationDTO;
import net.javaguides.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users-location")
public List<UserLocationDTO> getAllUsers
Location() {
return userService.getAllUsersLocation();
}
}
Explanation
@RestController
: Indicates that the class is a REST controller.@GetMapping("/users-location")
: Maps the/users-location
endpoint to thegetAllUsersLocation
method.
8. Insert Data into DB
Let’s write a code to insert a few records into the H2 database.
SpringbootDtoTutorialApplication
package net.javaguides.springboot;
import net.javaguides.springboot.model.Location;
import net.javaguides.springboot.model.User;
import net.javaguides.springboot.repository.LocationRepository;
import net.javaguides.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootDtoTutorialApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringbootDtoTutorialApplication.class, args);
}
@Autowired
private UserRepository userRepository;
@Autowired
private LocationRepository locationRepository;
@Override
public void run(String... args) throws Exception {
Location location = new Location();
location.setPlace("Pune");
location.setDescription("Pune is a great place to live");
location.setLongitude(40.5);
location.setLatitude(30.6);
locationRepository.save(location);
User user1 = new User();
user1.setFirstName("Ramesh");
user1.setLastName("Fadatare");
user1.setEmail("ramesh@gmail.com");
user1.setPassword("secret");
user1.setLocation(location);
userRepository.save(user1);
User user2 = new User();
user2.setFirstName("John");
user2.setLastName("Cena");
user2.setEmail("john@gmail.com");
user2.setPassword("secret");
user2.setLocation(location);
userRepository.save(user2);
}
}
Explanation
@SpringBootApplication
: Marks the class as a Spring Boot application.CommandLineRunner
: Interface that indicates arun
method should be invoked when the application starts.
9. Run and Test the Spring Boot Application
Go to the root directory of the application and type the following command to run it:
$ mvn spring-boot:run
The application will start at Spring Boot’s default Tomcat port, 8080.
Hit the below URL in the browser to get the response of the REST API:
http://localhost:8080/users-location
Conclusion
In this tutorial, we have seen how to use the DTO (Data Transfer Object) pattern in a Spring Boot application. DTOs allow us to transfer just the required information, reducing coupling between the API and the persistence model and making the service easier to maintain and scale.