HelloKoding

Practical coding guides

Spring Boot MapStruct Example of Mapping JPA and Hibernate Entity to DTO

In this tutorial, you will learn using MapStruct for mapping/converting Java objects, JPA and Hibernate entity to DTO and vice versa. Let’s get started to build a RESTful APIs example with Spring Boot and MySQL to see how MapStruct playing in the practice context

What you’ll need

  • JDK 8+ or OpenJDK 8+
  • Maven 3+
  • MySQL Server 5+ or Docker CE 18+

Init project structure and MapStruct

Project structure

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── hellokoding
│       │           └── springboot
│       │               └── restful
│       │                   ├── product
│       │                   │   ├── Product.java
│       │                   │   ├── ProductAPI.java
│       │                   │   ├── ProductDTO.java
│       │                   │   ├── ProductMapper.java
│       │                   │   ├── ProductRespository.java
│       │                   │   └── ProductService.java
│       │                   └── Application.java
│       └── resources
│           └── application.properties
├── Dockerfile
├── docker-compose.yml
└── pom.xml

Setting up MapStruct

MapStruct is a Java annotation processor for generating bean mapping classes

To set up, add MapStruct dependency into pom.xml and configure annotationProcessorPaths of maven-compiler-plugin. Full project dependencies and build plugins as below

pom.xml

<?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.hellokoding.springboot</groupId>
    <artifactId>springboot-mapstruct</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.2</org.projectlombok.version>
        <org.apache.maven.plugins.version>3.6.0</org.apache.maven.plugins.version>
    </properties>

    <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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${org.apache.maven.plugins.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <arg>-Amapstruct.suppressGeneratorTimestamp=true</arg>
                        <arg>-Amapstruct.defaultComponentModel=spring</arg>
                    </compilerArgs>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Define JPA and Hibernate Entity, DTO, Repository, and Service

Product.java

package com.hellokoding.springboot.restful.product;

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;

@Data

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String description;

    private BigDecimal price;

    @CreationTimestamp
    private Date createdAt;

    @UpdateTimestamp
    private Date updatedAt;
}

ProductDTO.java

package com.hellokoding.springboot.restful.product;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class ProductDTO {
    private String name;
    private String description;
    private BigDecimal price;
}

DTO, stands for Data Transfer Object, is a design pattern used for customizing/aggregating data input and output so reducing the number of call to remote API.

ProductRespository.java

package com.hellokoding.springboot.restful.product;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRespository extends JpaRepository<Product, Long> {
}

ProductService.java

package com.hellokoding.springboot.restful.product;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor

@Service
public class ProductService {
    private final ProductRespository productRespository;

    public List<Product> findAll() {
        return productRespository.findAll();
    }

    public Optional<Product> findById(Long id) {
        return productRespository.findById(id);
    }

    public Product save(Product stock) {
        return productRespository.save(stock);
    }

    public void deleteById(Long id) {
        productRespository.deleteById(id);
    }
}

Using MapStruct

Define MapStruct Mapper

To using MapStruct, you have to define a @Mapper interface which declares any required mapping methods to map between entity and DTO

ProductMapper.java

package com.hellokoding.springboot.restful.product;

import org.mapstruct.Mapper;

import java.util.List;

@Mapper
public interface ProductMapper {
    ProductDTO toProductDTO(Product product);

    List<ProductDTO> toProductDTOs(List<Product> products);

    Product toProduct(ProductDTO productDTO);
}

Here you only define simple method signatures, converting Entity to DTO, DTO to Entity, List of Entity to List of DTOs. MapStruct will generate implementation code for you during build time.

Using MapStruct on REST API

Inject your previous defined @Mapper interface into REST API and start using its methods.

ProductAPI.java

package com.hellokoding.springboot.restful.product;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;


@Slf4j
@RequiredArgsConstructor

@RestController
@RequestMapping("/api/v1/products")
public class ProductAPI {
    private final ProductService productService;
    private final ProductMapper productMapper;

    @GetMapping
    public ResponseEntity<List<ProductDTO>> findAll() {
        return ResponseEntity.ok(productMapper.toProductDTOs(productService.findAll()));
    }

    @PostMapping
    public ResponseEntity<ProductDTO> create(@RequestBody ProductDTO productDTO) {
        productService.save(productMapper.toProduct(productDTO));

        return ResponseEntity.status(HttpStatus.CREATED).body(productDTO);
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> findById(@PathVariable Long id) {
        Optional<Product> product = productService.findById(id);

        return ResponseEntity.ok(productMapper.toProductDTO(product.get()));
    }

    @PutMapping("/{id}")
    public ResponseEntity<ProductDTO> update(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
        Product product = productMapper.toProduct(productDTO);
        product.setId(id);

        productService.save(product);

        return ResponseEntity.status(HttpStatus.ACCEPTED).body(productDTO);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity delete(@PathVariable Long id) {
        productService.deleteById(id);

        return ResponseEntity.status(HttpStatus.ACCEPTED).build();
    }
}

Wow! what a clean REST API without writing a single mapping/converting logic :)

Config and Run

Application Configurations

application.properties

spring.datasource.url=jdbc:mysql://hk-mysql:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=hellokoding
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

hk-mysql refers to Docker Compose service defined in the below docker-compose.yml file

spring.jpa.hibernate.ddl-auto=create allows JPA/Hibernate auto create database and table schema for you.

In practice, you may like to disable the DDL Auto feature by using spring.jpa.hibernate.ddl-auto=validate or spring.jpa.hibernate.ddl-auto=none (default). Check out this example as one of the approaches Spring Boot Flyway Example of Database Evolution

Application.java

package com.hellokoding.springboot.restful;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Run with Docker

Prepare Dockerfile for Java/Spring Boot application and docker-compose.yml for MySQL Server

Dockerfile

FROM maven:3.5-jdk-8

docker-compose.yml

version: '3'
services:
  hk-mysql:
    container_name: hk-mysql
    image: mysql/mysql-server:5.7
    environment:
      MYSQL_DATABASE: test
      MYSQL_ROOT_PASSWORD: hellokoding
      MYSQL_ROOT_HOST: '%'
    ports:
    - "3306:3306"
    restart: always

  app:
    build: .
    volumes:
    - .:/app
    - ~/.m2:/root/.m2
    working_dir: /app
    ports:
    - 8080:8080
    command: mvn clean spring-boot:run
    depends_on:
    - hk-mysql

Type the below command at the project root directory, make sure your local Docker is running

docker-compose up

Access to MySQL Server docker container by issuing below bash command and key in hellokoding on Enter password:

docker exec -it hk-mysql mysql -p

Run with JDK/OpenJDK, Maven and MySQL Server local

Update hk-mysql on application.properties to localhost and type the below command at the project root directory

mvn clean spring-boot:run

Test with curl

Create a new product

curl -i -H "Content-Type: application/json" -X POST -d '{"name":"Hello Koding","description": "Simple coding examples and tutorials","price":"1"}' http://localhost:8080/api/v1/products

Find all products

curl -i http://localhost:8080/api/v1/products

Source code

https://github.com/hellokoding/hellokoding-courses/tree/master/springboot-examples/springboot-mapstruct

Follow HelloKoding