Skipping Fields with ModelMapper

Configuring ModelMapper to Skip Fields when Mapping

In a previous post, I covered how to create Custom ModelMapper Converters and Mappings. In this post, I’m going to demonstrate how to configure ModelMapper to skip fields when mapping.  Sometimes it’s desirable to skip fields when mapping from a DTO to an entity or vice versa.  The example project is going to use modified by and modified date fields as the example.  Spring JPA provides functionality for annotating fields as audit fields.  In the example, we’re going to skip mapping the modifiedBy and modifidDate fields from the DTO to the entity on entry.

Prerequisites:

  • Java 8
  • Preferred IDE
  • Basic understanding of Spring Boot and Spring JPA

The Item Entity

This example has a simple Item entity that contains a name, description, quantity and audit fields.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Item {
   @Id
   @GeneratedValue(strategy=GenerationType.IDENTITY)
   private Long id;
   private String name;
   private String description;
   private Integer quantity;
   @CreatedBy
   private String createdBy;
   @LastModifiedBy
   private String modifiedBy;
   @CreatedDate
   private LocalDate createdDate;
   @LastModifiedDate
   private LocalDate modifiedDate;

   ... getters, setters and human readable toString method

The ItemDto

The ItemDto class is almost identical to the Item entity with the exception of the added auditFields field.  The getAuditFields() method, constructs a nicely formatted string that the application can display when the user hovers over an item on the list.

public class ItemDto {
   private Long id;
   private String name;
   private String description;
   private Integer quantity;
   private String createdBy;
   private String modifiedBy;
   private LocalDate createdDate;
   private LocalDate modifiedDate;
   private String auditFields;
 
   ... getters, setters and human readable toString method 

   /**
    * Gets auditFields.
    * @return the auditFields
    */
    public String getAuditFields() {
       if (auditFields == null || auditFields.length() == 0) {
          auditFields = "Created By: " + (createdBy != null ? createdBy : "");
          auditFields += " Created Date: " + (createdDate != null ? createdDate.format(DateTimeFormatter.ISO_DATE) : "");
          auditFields += " Modified By: " + (modifiedBy != null ? modifiedBy : "");
          auditFields += " Modified Date: " + (modifiedDate != null ? modifiedDate.format(DateTimeFormatter.ISO_DATE) : "");
       }
       return auditFields;
    }

The ItemController class

The actual skipping is done in the ItemController class.  We define a custom PropertyMap for doing the mapping from ItemDto to Item called skipModifiedFieldsMap.  Implementing the configure() method is required and that’s where we do our skipping.  We only need to deal with the fields for which we’re modifying the default mapping, so only the modifiedBy and modifiedDate fields are mentioned there.  Once we’ve created the custom property map, we need to apply it.  In order to apply it only in one place, we pass it into a constructor that takes a ModelMapper instance and is annotated with @Autowired.  This line this.modelMapper.addMappings(skipModifiedFieldsMap); provides our custom mapping to the modelMapper instance.  Now that custom mapping will be in use anywhere in the controller that the modelMapper instance is called upon to map from an ItemDto to an Item.

@Controller
public class ItemController {
   @Autowired
   private ItemRepository itemRepository;
   private ModelMapper modelMapper;
 
   @Autowired
   public ItemController(ModelMapper modelMapper) {
      this.modelMapper = modelMapper;
      this.modelMapper.addMappings(skipModifiedFieldsMap);
   }

   PropertyMap<ItemDto, Item> skipModifiedFieldsMap = new PropertyMap<ItemDto, Item>() {
      protected void configure() {
         skip().setModifiedBy(null);
         skip().setModifiedDate(null);
     }
   };
 
   @RequestMapping("/")
   public String listAll(Model model) {
      List<Item> items = itemRepository.findAll();
      List<ItemDto> itemDtos = items.stream().map(item -> modelMapper.map(item, ItemDto.class)).collect(Collectors.toList());
      model.addAttribute("items", itemDtos);
      return "item/list";
   }
 
   @RequestMapping(value="/item/add", method=RequestMethod.GET)
   public String addItem(ItemDto itemDto, Model model) {
      model.addAttribute("action", "item/add");
      return "item/entry";
   }
 
   @RequestMapping(value="/item/add", params={"save"}, method=RequestMethod.POST)
   public String saveNewItem(@Valid ItemDto itemDto, BindingResult bindingResult, Model model) {
      if (bindingResult.hasErrors()) {
         model.addAttribute("action", "item/add");
         return "item/entry";
      }
 
      Item item = modelMapper.map(itemDto, Item.class);
      itemRepository.save(item);
      return "redirect:/";
   }
 
   @RequestMapping(value="/item/add", params={"cancel"}, method=RequestMethod.POST)
   public String cancelNewItem() {
      return "redirect:/";
   }
 
   @RequestMapping(value="/item/edit", method=RequestMethod.GET)
   public String editItem(ItemDto itemDto, Model model, @RequestParam("id") Long id) {
      model.addAttribute("action", "item/edit");
      Item item = itemRepository.getOne(id);
      itemDto = modelMapper.map(item, ItemDto.class);
      model.addAttribute("itemDto", itemDto);
      return "item/entry";
   }
 
   @RequestMapping(value="/item/edit", params={"save"}, method=RequestMethod.POST)
   public String saveItem(@Valid ItemDto itemDto, BindingResult bindingResult, Model model) {
      if (bindingResult.hasErrors()) {
         model.addAttribute("action", "item/edit");
         return "item/entry";
      }
 
      System.out.println("Item DTO from entry form: " + itemDto.toString());
      Item item = modelMapper.map(itemDto, Item.class);
      System.out.println("Item after mapping from form with Modified fields skipped: " + item.toString());
      itemRepository.save(item);
      return "redirect:/";
   }
 
   @RequestMapping(value="/item/edit", params={"cancel"}, method=RequestMethod.POST)
   public String cancelItem() {
      return "redirect:/";
   }
}

Skipping in Action

In order to illustrate the skipping, I added some System.out calls to the saveItem controller method.  If we edit an Item, we can see that the original modifiedBy and modifiedDate are provided and they are not mapped into the entity being saved.

Item DTO from entry form: ItemDto [id=3, name=Wocket 12mm, description=Wocket (12mm), quantity=6, createdBy=demo_user, modifiedBy=demo_user, createdDate=2018-05-23, modifiedDate=2018-05-23]
Item after mapping from form with Modified fields skipped: Item [id=3, name=Wocket 12mm, description=Wocket (12mm), quantity=6, createdBy=demo_user, modifiedBy=null, createdDate=2018-05-23, modifiedDate=null]

This simple example demonstrates how to skip one or more fields when mapping between objects using ModelMapper.  The full example code is available on GitHub.

References:

 

Related Posts

 

Advertisements

4 thoughts on “Skipping Fields with ModelMapper

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s