π― Objective
Separate internal models from external API contracts using DTOs (Data Transfer Objects) and simplify object mapping using ModelMapper
.
π¦ Step 1: Create a ProductDTO Class
public class ProductDTO {
private Long id;
@NotBlank(message = "Product name is required")
private String name;
@NotNull(message = "Price is required")
@Min(value = 0, message = "Price must be positive")
private Double price;
// Getters and Setters
}
π Explanation
- This class mirrors the Product entity, but is designed for API interaction.
- Keeps entity class decoupled from client-facing code.
π§ Step 2: Add ModelMapper Bean
@Configuration
public class AppConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
π Explanation
- Configures
ModelMapper
as a Spring bean so it can be autowired anywhere.
π Step 3: Update Service to Use DTOs
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ModelMapper modelMapper;
public List<ProductDTO> getAll() {
return productRepository.findAll().stream()
.map(product -> modelMapper.map(product, ProductDTO.class))
.collect(Collectors.toList());
}
public ProductDTO getById(Long id) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product not found: " + id));
return modelMapper.map(product, ProductDTO.class);
}
public ProductDTO save(ProductDTO dto) {
Product product = modelMapper.map(dto, Product.class);
Product saved = productRepository.save(product);
return modelMapper.map(saved, ProductDTO.class);
}
public void delete(Long id) {
productRepository.deleteById(id);
}
}
π Explanation
- Converts between entity and DTO using
ModelMapper.map()
. - Maintains a clear separation between internal model and external contract.
π Step 4: Update Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<ProductDTO> getAllProducts() {
return productService.getAll();
}
@GetMapping("/{id}")
public ResponseEntity<ProductDTO> getProductById(@PathVariable Long id) {
return ResponseEntity.ok(productService.getById(id));
}
@PostMapping
public ResponseEntity<ProductDTO> createProduct(@Valid @RequestBody ProductDTO productDTO) {
return new ResponseEntity<>(productService.save(productDTO), HttpStatus.CREATED);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
}
π Explanation
- Uses
ProductDTO
instead of entity in controller layer. - Keeps HTTP interface separate from database logic.
π οΈ Benefits of Using DTOs
- π Hides sensitive fields (like passwords or internal IDs).
- π‘ Allows custom formats for clients.
- π§± Clean separation of concerns.
- π Prevents accidental data overwrites from external input.
β‘οΈ Next Up: Part 6 β Entity Relationships and JPA Associations