.png)
ModelMapper е библиотека за автоматично преобразуване между обекти, често използвана за преобразуване между DTO (Data Transfer Object) и ентити класове.
Стъпки за реализация:
- Добавяме зависимостта за ModelMapper в pom.xml:
За да се използва ModelMapper, е необходимо да се добави следната зависимост в pom.xml:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.2</version>
</dependency>
В Spring приложението, ModelMapper обикновено се дефинира като Spring bean. Това позволява той да бъде автоматично инжектиран чрез @Autowired. Конфигурацията може да се извърши чрез клас, анотиран с @Configuration
За автоматичното конфигуриране на bean можете да предприемете една от двете стъпки:
- в проекта добавете следния метод, анотиран с @Bean:
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
За да работи @Bean, методът трябва да бъде в клас, анотиран с @Configuration или друг Spring компонент (@Component, @Service, @RestController), за да може Spring да го разпознае и управлява като Bean.
- или създайте клас-наследник на ModelMapper, анотиран с @Component
@Component
public class CustomMapper extends ModelMapper {
}
След конфигурирането, ModelMapper може да бъде използван за преобразуване между обекти. Например:
- Създаваме entity клас:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String email;
private String password;
private Role role;
}
UserService.java
public interface UserService {
public UserDto createUser(UserDto userDto);
public UserDto getUser(int userId);
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final ModelMapper modelMapper;
public UserServiceImpl(UserRepository userRepository,
ModelMapper modelMapper){
this.userRepository = userRepository;
this. modelMapper = modelMapper;
}
@Override
public UserDto createUser(UserDto userDto) {
User user = modelMapper.map(userDto, User.class);
User userSavedToDB = userRepository.save(user);
UserDto userResponse = modelMapper.map(userSavedToDB, UserDto.class);
return userResponse;
}
@Override
public UserDto getUser(int userId) {
User user = userRepository.findById(userId).get();
UserDto userDto = modelMapper.map(user, UserDto.class);
return userDto;
}
}
UserController.java
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserServiceImpl userServiceImpl;
public UserController(UserServiceImpl userServiceImpl) {
this.userServiceImpl = userServiceImpl;
}
@PostMapping("/create")
public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto){
UserDto userCreated = this.userServiceImpl.createUser(userDto);
return new ResponseEntity<>(userCreated, HttpStatus.CREATED);
}
@GetMapping("/get/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable("id") int userId){
UserDto userDto = this.userServiceImpl.getUser(userId);
return new ResponseEntity<>(userDto, HttpStatus.OK);
}
}
Работа с Enum типове в ModelMapper
ModelMapper автоматично разпознава и мапва стойности от тип enum, когато имената съвпадат между изходния и целевия обект, но при несъвпадение между полета (напр. String ↔ Enum), може да се направи ръчна трансформация:
modelMapper.typeMap(UserDto.class, User.class).addMappings(mapper ->
mapper.map(src -> Role.valueOf(src.getRole()), User::setRole)
);
ModelMapper позволява по-прецизна конфигурация при нужда, като например:
- Пропускане на определени полета - ModelMapper по подразбиране мапва всички съвпадащи по име полета. В някои случаи може да е необходимо определени полета да не се мапват. Това се постига чрез игнориране на дадено свойство.
modelMapper.typeMap(Source.class, Destination.class)
.addMappings(mapper -> mapper.skip(Destination::setIgnoredField));
Когато се използва skip(), ModelMapper няма да зададе стойност за посоченото поле в целевия обект (Destination). Това е полезно например при чувствителни данни или изчисляеми стойности, които не трябва да се презаписват.
- Кастъм правила за мапване - Когато изходното и целевото поле не съвпадат по име или се нуждаят от трансформация (например дата във формат String), може да се дефинира ръчно правило за мапване.
modelMapper.typeMap(Source.class, Destination.class)
.addMappings(mapper ->
mapper.map(src -> src.getFullName(), Destination::setName));
В този пример fullName от Source се мапва към name от Destination, въпреки че имената не съвпадат. Това позволява пълна гъвкавост при различни модели на данни.
- Условия за мапване - Позволява се задаване на условие, което трябва да бъде изпълнено, за да се извърши мапването на дадено поле – например да не е null, да е по-дълго от определена дължина и др.
Condition<String, String> notNullOrEmpty = ctx ->
ctx.getSource() != null && !ctx.getSource().isEmpty();
modelMapper.getConfiguration().setPropertyCondition(notNullOrEmpty);
Така ModelMapper ще пропусне полетата, които са null или празни. Това е полезно при partial updates (частично обновяване), когато не трябва да се презаписват стойности с празни или null данни.
Промяна на конфигурацията на ModelMapper
Метода getConfiguration() на класа ModelMapper връща текущата конфигурация на инстанцията. Това е начин да се достъпи, промени или разшири поведението на ModelMapper чрез настройките, които той поддържа.
getConfiguration() - Връща обект от тип org.modelmapper.config.Configuration; Служи за фина настройка на начина, по който ModelMapper извършва мапването между обекти; Позволява да се задават глобални правила, които важат за всички мапвания в дадената инстанция на ModelMapper.
Модифицирането на обекта се извършва със следните метод:
- setSkipNullEnabled(true/false)
- Ако е true, полетата със null стойност в source няма да се мапват към целта.
- Пример: Полето email = null няма да изтрие стойността в целевия обект.
- setFieldMatchingEnabled(true/false)
- Ако е true, ModelMapper може да мапва по имена на полета, дори ако няма getter/setter.
- По подразбиране ModelMapper работи само с getter/setter методи (JavaBeans стил).
- setFieldAccessLevel(Configuration.AccessLevel) - Определя нивото на достъп до полетата:
- PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE
- Например PRIVATE позволява достъп до частни полета директно.
- setSourceNameTokenizer(…) и setDestinationNameTokenizer(…)
- Позволяват персонализиране как се „разбиват“ имената на полета (напр. camelCase → отделни думи)
- Използва се при по-сложни конвенции за именуване.
- setAmbiguityIgnored(true/false)
- Ако е true, няма да се хвърля изключение при нееднозначно мапване.
- Пример: Когато две полета изглеждат подходящи, но ModelMapper не знае кое да избере.
- setMatchingStrategy(…) - Определя стратегията за мапване:
- STANDARD – по подразбиране
- STRICT – по-строго съвпадение (само ако имената съвпадат точно)
- LOOSE – по-свободно съвпадение, по-гъвкаво
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT);
Цялостен пример
mapper.getConfiguration()
.setSkipNullEnabled(true)
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE)
.setAmbiguityIgnored(true);