Effortless Data Mapping in Spring Boot with MapStruct


When working with RESTful API's, there is always a clash for using DAO's to transfer data between microservices. The use of DAO's to transfer data between microservices opens up alot of issues, say for example there could be some fields in DAO which is not needed or there are fields that you need to add which is expected by other service. In either case, you need to update the DAO. The problem here is that DAO object should always be mapped according to the table it is working with. In the case explained above you need to update the DAO object. This is wrong. DAO should only be used for accessing data and no other purpose.

To answer this, we have DTO's which come into picture. Like DAO is used only for accessing the data say a mapping of your Database table, DTO's only purpose is to represent object while transferring it to any other service.

Now the question comes in of where the mapping and how the mapping should look like. To answer this question, we have MapStruct. 

MapStruct is a code generator that simplifies the implementation of mappings between Java bean types based on a convention-over-configuration approach. It automates the tedious and error-prone task of writing mapping code, making your code cleaner and easier to maintain. This guide will show you how to use the @Mapper and @Mapping annotations in Spring Boot for MapStruct with practical code examples.

1. Setup Your Spring Boot Project

First, ensure you have a Spring Boot project. If you don’t have one, you can create a new one using Spring Initializr or your preferred method. Include the following dependencies in your pom.xml:

xml
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.0.Final</version> <scope>provided</scope> </dependency>

2. Create Source and Destination Classes

Let's create two simple Java classes for source and destination objects.

Source Class:

java
package com.example.demo.model; public class Source { private String firstName; private String lastName; private int age; // Getters and Setters public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

Destination Class:

java
package com.example.demo.dto; public class Destination { private String fullName; private int age; // Getters and Setters public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

3. Define a Mapper Interface

Next, create a mapper interface and annotate it with @Mapper. Use the @Mapping annotation to specify the mapping between source and destination fields.

java
package com.example.demo.mapper; import com.example.demo.dto.Destination; import com.example.demo.model.Source; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper public interface SourceDestinationMapper { SourceDestinationMapper INSTANCE = Mappers.getMapper(SourceDestinationMapper.class); @Mapping(source = "firstName", target = "fullName") @Mapping(source = "lastName", target = "fullName", ignore = true) // Optional, to show ignoring field Destination sourceToDestination(Source source); @Mapping(source = "fullName", target = "firstName") Source destinationToSource(Destination destination); }

4. Use the Mapper in Your Service or Controller

Now, you can use the generated mapper in your service or controller to perform the mapping.

Example Service:

java
package com.example.demo.service; import com.example.demo.dto.Destination; import com.example.demo.mapper.SourceDestinationMapper; import com.example.demo.model.Source; import org.springframework.stereotype.Service; @Service public class MappingService { private final SourceDestinationMapper mapper = SourceDestinationMapper.INSTANCE; public Destination convertToDestination(Source source) { return mapper.sourceToDestination(source); } public Source convertToSource(Destination destination) { return mapper.destinationToSource(destination); } }

Example Controller:

java
package com.example.demo.controller; import com.example.demo.dto.Destination; import com.example.demo.model.Source; import com.example.demo.service.MappingService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/mapping") public class MappingController { @Autowired private MappingService mappingService; @PostMapping("/toDestination") public Destination toDestination(@RequestBody Source source) { return mappingService.convertToDestination(source); } @PostMapping("/toSource") public Source toSource(@RequestBody Destination destination) { return mappingService.convertToSource(destination); } }

5. Testing the Mapping

Run your Spring Boot application and use tools like Postman or curl to test the endpoints.

Convert Source to Destination:

bash
curl -X POST http://localhost:8080/api/mapping/toDestination -H "Content-Type: application/json" -d '{"firstName":"John","lastName":"Doe","age":30}'

Convert Destination to Source:

bash
curl -X POST http://localhost:8080/api/mapping/toSource -H "Content-Type: application/json" -d '{"fullName":"John","age":30}'

Conclusion

In this guide, you learned how to use MapStruct in a Spring Boot application to map between different data transfer objects using the @Mapper and @Mapping annotations. This approach significantly reduces the boilerplate code required for such mappings and helps maintain clean and maintainable code. Happy coding!

Post a Comment

0 Comments