Introduction
In this example I will show you how to convert entity class to DTO (Data Transfer Object) class and DTO class to entity class using MapStruct. So, MapStruct will map entity class to DTO class or vice versa.
This example will test both from RESTful webservices and standalone main class. Spring Boot provides CommandLineRunner interface to run the project as a standalone.
Related Post:
MapStruct provides an easy way to convert DTO to entity class or vice versa. So, you don’t need to create any converter class for mapping the fields of the classes.
DTO is a design pattern used for aggregating data input and output to reduce the number of calls to remote API.
Prerequisites
Java 1.8+, Spring Boot 2.7.0, Maven 3.8.5, MapStruct 1.5.1.Final, MySQL 8.0.26
Project Setup
The following pom.xml file is used for the project. Here I have used the MapStruct dependency and MapStruct processor plugin for generating bean mapping classes. The annotationProcessorPaths
is configured under the maven-compiler-plugin
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>springboot-mapstruct-jpa-entity-dto-mapping</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.1.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.1.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
MySQL Table and Data
I am creating a table called event under roytuts database in MySQL server. I will also insert few sample data for testing the application once coding part is done.
CREATE TABLE IF NOT EXISTS `event` (
`id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`clasz` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`start_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`end_date` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `event` (`id`, `title`, `url`, `clasz`, `start_date`, `end_date`) VALUES
(1, 'Example', 'http://www.example.com', 'event-success', '2022-06-03 15:27:51', '2020-04-10 20:01:02'),
(2, 'Jee Tutorials', 'https://roytuts.com', 'event-important', '2022-06-11 19:00:00', '2020-03-12 19:42:45'),
(3, 'Roy Tutorial', 'https://roytuts.com', 'event-info', '2022-06-12 20:03:05', '2020-05-13 08:45:53');
Data Source Config
The following datasource configuration is done through standard spring boot naming conventions. The following content is written into src/main/resources/application.properties file.
#Spring Datasource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root
#SQL related
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto = none
The line spring.jpa.hibernate.ddl-auto = none
prevents creating table from the entity class.
I am logging the SQL queries using the following lines of configurations:
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
Entity Class
The corresponding entity class that maps table to Java object is given below:
@Entity
@Table
public class Event {
@Id
@Column
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String title;
@Column
private String url;
private String clasz;
@Column(name = "start_date")
@Temporal(TemporalType.TIMESTAMP)
private Date startDate;
@Column(name = "end_date")
@Temporal(TemporalType.TIMESTAMP)
private Date endDate;
// getters and setters
@Override
public String toString() {
return "Event [id=" + id + ", title=" + title + ", url=" + url + ", clasz=" + clasz + ", startDate=" + startDate
+ ", endDate=" + endDate + "]";
}
}
DTO Class
Here is the DTO class. DTO (Data Transfer Object) is a design pattern for aggregating input and output to reduce remote API calls.
public class EventDto {
private int id;
private String title;
private String url;
private String clasz;
private Date startDate;
private Date endDate;
public EventDto() {
}
public EventDto(int id, String title, String url, String clasz, Date startDate, Date endDate) {
this.id = id;
this.title = title;
this.url = url;
this.clasz = clasz;
this.startDate = startDate;
this.endDate = endDate;
}
// getters and setters
@Override
public String toString() {
return "EventDto [id=" + id + ", title=" + title + ", url=" + url + ", clasz=" + clasz + ", startDate="
+ startDate + ", endDate=" + endDate + "]";
}
}
Mapper Class
The mapper class is responsible for converting entity to DTO and DTO to entity class.
@Mapper(componentModel = "spring")
public interface EventMapper {
EventDto toEventDto(Event event);
List<EventDto> toEventDtos(List<Event> events);
Event toEvent(EventDto eventDto);
}
I have used annotation @Mapper
with componentModel = "spring"
. The componentModel = "spring"
means, the generated mapper is a Spring bean and can be retrieved via @Autowired
.
Repository Interface
The Spring Data JPA API provides built-in functions which can be used to perform database operations without writing any queries for building basic CRUD applications by extending JpaRepository interface.
public interface EventRepository extends JpaRepository<Event, Integer> {
}
Service Class
The service class is responsible for handling business logic for an application. Here I am querying the database using the repository interface and fetching the required data and using the mapper class I am converting entity class to DTO or DTO to entity class.
@Service
public class EventService {
@Autowired
private EventRepository eventRepository;
@Autowired
private EventMapper eventMapper;
public List<Event> getEvents() {
return eventRepository.findAll();
}
public List<EventDto> getEventDtos() {
return eventMapper.toEventDtos(eventRepository.findAll());
}
public EventDto getEventDto(Integer id) {
return eventMapper.toEventDto(eventRepository.findById(id).orElseThrow());
}
public Event getEvent(Integer id) {
return eventMapper.toEvent(getEventDto(id));
}
}
REST Controller API
The following REST controller class exposes REST endpoints which can be easily accessible through REST clients.
@RestController
public class EventRestController {
@Autowired
private EventService eventService;
@GetMapping("/events")
public ResponseEntity<List<Event>> getEvents() {
return new ResponseEntity<List<Event>>(eventService.getEvents(), HttpStatus.OK);
}
@GetMapping("/event/{id}")
public ResponseEntity<Event> getEvents(@PathVariable Integer id) {
return new ResponseEntity<Event>(eventService.getEvent(id), HttpStatus.OK);
}
@GetMapping("/eventDtos")
public ResponseEntity<List<EventDto>> getEventDtos() {
return new ResponseEntity<List<EventDto>>(eventService.getEventDtos(), HttpStatus.OK);
}
@GetMapping("/eventDto/{id}")
public ResponseEntity<EventDto> getEventDto(@PathVariable Integer id) {
return new ResponseEntity<EventDto>(eventService.getEventDto(id), HttpStatus.OK);
}
}
Spring Boot Main Class
The following spring boot main class implements CommandLineRunner interface to have the ability to run the app as a standalone also.
@SpringBootApplication
public class App implements CommandLineRunner {
@Autowired
private EventService eventService;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Event Objects");
System.out.println("---------------------------------------");
eventService.getEvents().stream().forEach(e -> System.out.println(e));
System.out.println("---------------------------------------");
System.out.println("Event DTO Objects");
System.out.println("---------------------------------------");
eventService.getEventDtos().stream().forEach(e -> System.out.println(e));
System.out.println("---------------------------------------");
System.out.println("Event Object");
System.out.println("---------------------------------------");
System.out.println(eventService.getEvent(2));
System.out.println("---------------------------------------");
System.out.println("Event DTO Object");
System.out.println("---------------------------------------");
System.out.println(eventService.getEventDto(2));
System.out.println("---------------------------------------");
}
}
Testing Entity DTO Conversion
Running the above main class will produce the following output for the application:
Event Objects
---------------------------------------
Hibernate:
select
event0_.id as id1_0_,
event0_.clasz as clasz2_0_,
event0_.end_date as end_date3_0_,
event0_.start_date as start_da4_0_,
event0_.title as title5_0_,
event0_.url as url6_0_
from
event event0_
Event [id=1, title=Example, url=http://www.example.com, clasz=event-success, startDate=2022-06-03 15:27:51.0, endDate=2020-04-10 20:01:02.0]
Event [id=2, title=Jee Tutorials, url=https://roytuts.com, clasz=event-important, startDate=2022-06-11 19:00:00.0, endDate=2020-03-12 19:42:45.0]
Event [id=3, title=Roy Tutorial, url=https://roytuts.com, clasz=event-info, startDate=2022-06-12 20:03:05.0, endDate=2020-05-13 08:45:53.0]
---------------------------------------
Event DTO Objects
---------------------------------------
Hibernate:
select
event0_.id as id1_0_,
event0_.clasz as clasz2_0_,
event0_.end_date as end_date3_0_,
event0_.start_date as start_da4_0_,
event0_.title as title5_0_,
event0_.url as url6_0_
from
event event0_
EventDto [id=1, title=Example, url=http://www.example.com, clasz=event-success, startDate=2022-06-03 15:27:51.0, endDate=2020-04-10 20:01:02.0]
EventDto [id=2, title=Jee Tutorials, url=https://roytuts.com, clasz=event-important, startDate=2022-06-11 19:00:00.0, endDate=2020-03-12 19:42:45.0]
EventDto [id=3, title=Roy Tutorial, url=https://roytuts.com, clasz=event-info, startDate=2022-06-12 20:03:05.0, endDate=2020-05-13 08:45:53.0]
---------------------------------------
Event Object
---------------------------------------
Hibernate:
select
event0_.id as id1_0_0_,
event0_.clasz as clasz2_0_0_,
event0_.end_date as end_date3_0_0_,
event0_.start_date as start_da4_0_0_,
event0_.title as title5_0_0_,
event0_.url as url6_0_0_
from
event event0_
where
event0_.id=?
Event [id=2, title=Jee Tutorials, url=https://roytuts.com, clasz=event-important, startDate=2022-06-11 19:00:00.0, endDate=2020-03-12 19:42:45.0]
---------------------------------------
Event DTO Object
---------------------------------------
Hibernate:
select
event0_.id as id1_0_0_,
event0_.clasz as clasz2_0_0_,
event0_.end_date as end_date3_0_0_,
event0_.start_date as start_da4_0_0_,
event0_.title as title5_0_0_,
event0_.url as url6_0_0_
from
event event0_
where
event0_.id=?
EventDto [id=2, title=Jee Tutorials, url=https://roytuts.com, clasz=event-important, startDate=2022-06-11 19:00:00.0, endDate=2020-03-12 19:42:45.0]
---------------------------------------
You can also test the application by accessing REST endpoints: http://localhost:8080/events, http://localhost:8080/eventDtos, http://localhost:8080/event/2, http://localhost:8080/eventDto/2