Generic Mapper function in MapStruct - java

Friends,
Here are my Java objects
#Data
public class CarDto {
private String make;
private String model;
private int year;
}
#Data
public class Car {
private MakeEnum make;
private String model;
private int year;
}
For consuming, I need to do something like this
#Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
Car toModel(CarDto carDto);
CarDto toDto(Car carModel);
}
// Using mapper
Car carModel = CarMapper.INSTANCE.toModel(carDto);
But I am looking a solution, where I could do this:
Car carModel = Mapper.map(carDto, Car.class);
How do do this? Didn't find an example where I can dynamically map based on a Type. I found this method very handy in both ModelMapper & Google gson. Appreciate your help!

If I understand you correctly, you require a kind of repo.
Another option would be to look at sprint and the new MapStruct spring integration, recently developed: https://github.com/mapstruct/mapstruct-spring-extensions. It was designed as a follow up of this question.
There's an example in the example repo. It's not straightforward though: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapper-repo

Before calling the mapping, you have to setup and interface of MapStruct like this:
#Mapper(componentModel = "spring")
public interface MapStructMapper {
ObjectDto objectToObjectDto(Object object);
}
Then an implementation of it :
#Component
public class MapStructMapperImpl implements MapStructMapper {
#Override
public ObjectDto objectToObjectDto(Object object) {
if ( object == null ) { return null; }
ObjectDto objectDto = new ObjectDto();
objectDto.setId( object.getId() );
objectDto.setName( object.getName() );
return objectDto;
}
and then, you just have to inject this interface in the controller and invoke the repository like this:
#RequestMapping("/objects")
public class ObjectController {
private MapStructMapper mapstructMapper;
private ObjectRepository objectRepository;
#Autowired
public ObjectController(
MapStructMapper mapstructMapper,
ObjectRepository objectRepository
) {
this.mapstructMapper = mapstructMapper;
this.objectRepository = objectRepository;
}
#GetMapping("/{id}")
public ResponseEntity<ObjectDto> getById(#PathVariable(value = "id") int id){
return new ResponseEntity<>(
mapstructMapper.objectToObjectDto(
objectRepository.findById(id).get()
),
HttpStatus.OK
);
}
}
Of course, you can call a service/serviceImpl instead of a direct call to the repository but it's to be as simple as possible. :)

Related

convert a DTO to Entity with using mapper class

I have a Entity class something like this:
#Entity
public class Website {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String url;
public Website() {
//Constructor
//getters and setters
}
here is the DTO class:
public class WebsiteDto {
private Integer id;
private String name;
private String url;
public WebsiteVo() {
//Constructor
//getters and setters
}
I have the WebsiteMapper something like this:
#Component
public class WebsiteMapper {
public List<WebsiteDto> getWebsiteList() {
return repository.findAll().stream().map(w -> {
WebsiteDto dto = new WebsiteVo(w.getId(), w.getName(), w.getUrl());
return dto;
}).collect(Collectors.toList());
I also have Repository Interface:
public interface WebsiteRepository extends JpaRepository<Website, Integer> {
}
I want now to convert DTO to entity using my class WebsiteMapper. Because I did the conversion in this class. How I can do it?
How about using BeanUtils provided by spring org.springframework.beans.BeanUtils, something like this
public List<WebsiteDto> getWebsiteList() {
return repository.findAll().stream().map(w -> {
WebsiteDto dto = new WebsiteVo();
BeanUtils.copyProperties(w, dto); // copys all variables with same name and type
return dto;
})
.collect(Collectors.toList());
}
Hi I guess you wish to converting your entity to DTO. It's quite simple. Create static methods in your DTO class or any util class. The return type should be your DTO type.
e.g.
public class WebsiteDto {
private Integer id;
private String name;
private String url;
public static WebsiteDto export(Website website) {
// Return a new instance of your website DTO
return new WebsiteDto(
website.getId(),
website.getName(),
website.getUrl()
);
}
public static List<WebsiteDto> export(List<Website> websites) {
// Return a new instance of your website DTO list
return websites.stream().map(website -> {
return new WebsiteDto(
website.getName(),
website.getUrl()
}).collect(Collectors.toList());
}
}
NOTE You can also convert your DTO to entity using similar method.

MapStruct issue while recursive mapping

I have trying to implement MapStruct mapping library. I have made samples and for simple mapping it works fine but I stucked in 1 issue.
I have 2 jpa entity classes which have two way relationships. One is in another and another is in one. It creates cyclic mapping issue so MapStruct throws StackOverflow error.
I have created minimal code to reproduce the case on github.
Sample code:
public class A {
private Long id;
private String name;
private B bData;
//getter-setter
}
public class B {
private Long id;
private String name;
private Set<A> aData;
//getter-setter
}
DataGenerator
public class DataGenerator {
public static A generateData(){
A a = new A();
a.setId(1L);
a.setName("foo");
B b = new B();
b.setId(2L);
b.setName("bar");
A a2 = new A();
a2.setId(3L);
a2.setName("john");
a2.setbData(b);
A a3 = new A();
a3.setId(4L);
a3.setName("doe");
a3.setbData(b);
Set<A> aData = new HashSet<A>();
aData.add(a2);
aData.add(a3);
b.setaData(aData);
a.setbData(b);
return a;
}
}
Mapper
#Mapper
public interface CustomMapper {
CustomMapper INSTANCE = Mappers.getMapper(CustomMapper.class);
ADto atoADto(A a);
}
App
public class AppMain {
public static void main(String[] args) {
A a = DataGenerator.generateData();
ADto aDto = CustomMapper.INSTANCE.atoADto(a);
System.out.println(aDto.getId());
}
}
Dto/Destination classes are same as original source classes.
The main is cyclic/recursive mapping issue which causes stackoverflow error.
Same thing working with spring BeanUtils.copyProperties but I want to implement MapStruct. Currently I am thinking to replace spring BeanUtils with MapStruct.
any suggestions?
See this mapstruct github issue for the solution, which is to ignore the field causing the recursion. I quote:
"You can achieve it with the #Qualifier. You can use #Named and qualifiedByName, or you can use your own custom #CountryWithoutCities qualifier with qualifiedBy.
Class country{
String id;
String name;
List<City> cities;
}
Class City{
String id;
String name;
Country country;
}
#Mapper(uses = CityMapper.class)
interface CountryMapper {
#Mapping( target = "cities", qualifiedByName = "noCountry")
CountryDto toDto(Country country);
#CountryWithoutCities
#Mapping( target = "cities", ignore = true)
CountryDto toDtoWithoutCities(Country country);
}
#Mapper(uses = CountryMapper.class)
interface CityMapper {
#Named( "noCountry" )
#Mapping( target = "country", ignore = true)
CityDto toDtoWithoutCountry(City city);
#Mapping( target = "country", qualifiedBy= CountryWithoutCities.class)
CityDto toDto(City city);
}
There's an example here in the MapStruct repo how to deal with cycles and recursion. Basically you need to keep track of state. The example makes use of a context object to do so.

Spring abstract controller with final field in endpoint paths

Let's say I want to build a rest api for storing car information. To make it simple for the sake of this post let's say I would like it to look like this:
/api/cars/{carmake}/save
/api/cars/{carmake}/edit
/api/cars/{carmake}/delete
Now, let's say I have multiple car makes and each of them would require different car make services eg. BmwService, MercedesService, AudiService.
So this is my idea: one abstract controller that would look something like this:
#RequestMapping(value="/api/cars/")
public abstract class CarController {
protected final String CAR_MAKE;
public CarController(String carMake){
this.CAR_MAKE = carMake;
}
#PostMapping(value = CAR_MAKE + "/save")
public abstract void save(#Valid #RequestBody Serializable car)
#DeleteMapping(value = CAR_MAKE + "/delete")
public abstract void save(#Valid #RequestBody Serializable car);
#PatchMapping(value = CAR_MAKE + "/edit")
public abstract void save(#Valid #RequestBody Serializable car)
}
And then an actual controller could look something like this:
#RestController
public class AudiController extends CarController {
private AudiService audiService;
#Autowired
public AudiController(AudiService audiService){
super("audi");
this.audiService = audiService;
}
#Override
public void save(#Valid #RequestBody Serializable car) {
audiService.save((Audi) car);
}
.
.
.
}
The problem is that spring does not allow me to have the value for request mappings with a final variable if it is initialized through the constructor (if the CAR_MAKE is initialized right on the field declaration eg. protected final String CAR_MAKE = "s" it works). So is there any way to work around this so that the paths can come from each subclass?
Not near a compiler but something like this.
Implement a CarService interface:
public interface CarService {
String getBrand();
void save(Car car);
// ...
}
Implement AudiCarService, BmwCarService (etc) types that implement CarService.
Implement a CarService repository something like:
public class CarServiceRepository {
private Map<String, CarService> carServicesByBrand;
public Optional<CarService> findFor(String brand) {
return Optional.ofNullable(carServicesByBrand.get(brand));
}
#Autowired
public void setCarServicesByBrand(List<CarService> carServices) {
this.carServicesByBrand = carServices.stream().collect(Collectors.toMap(CarService::getBrand, Function.identity()));
}
}
Implement a single controller "CarController":
#RequestMapping(value="/api/cars")
#Component
public class CarController {
#Autowired
private CarServiceRepository carServiceRepository;
#PostMapping(value = "/{brand}/save")
public void save(#Valid #RequestBody Serializable car, #PathParam String brand) {
carServiceRepository.findFor(brand).ifPresent(carService -> carService.save(car));
}
// ...
}
Consider also favoring HTTP verbs over explicit verbs in URLs e.g. Why does including an action verb in the URI in a REST implementation violate the protocol?

Avoid serialization of certain fields at runtime in Jackson

I have a controller which produces JSON, and from this controller, I return an entity object, which is automatically serialized by Jackson.
Now, I want to avoid returning some fields based on a parameter passed to the controller. I looked at examples where this is done using FilterProperties / Mixins etc. But all the examples I saw requires me to use ObjectMapper to serialize / de-serialize the bean manually. Is there any way to do this without manual serialization? The code I have is similar to this:
#RestController
#RequestMapping(value = "/myapi", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(value = "/test/{variable}",method=RequestMethod.GET)
public MyEntity getMyEntity(#PathVariable("variable") String variable){
return myservice.getEntity(variable);
}
}
#Service("myservice")
public class MyService {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity getEntity(String variable){
return myEntityRepository.findOne(1L);
}
}
#Entity
#Table(name="my_table")
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyEntity implements Serializable {
#Column(name="col_1")
#JsonProperty("col_1")
private String col1;
#Column(name="col_2")
#JsonProperty("col_2")
private String col2;
// getter and setters
}
Now, based on the value of "variable" passed to the controller, I want to show/hide col2 of MyEntity. And I do not want to serialize/deserialize the class manually. Is there any way to do this? Can I externally change the Mapper Jackson uses to serialize the class based on the value of "variable"?
Use JsonView in conjunction with MappingJacksonValue.
Consider following example:
class Person {
public static class Full {
}
public static class OnlyName {
}
#JsonView({OnlyName.class, Full.class})
private String name;
#JsonView(Full.class)
private int age;
// constructor, getters ...
}
and then in Spring MVC controller:
#RequestMapping("/")
MappingJacksonValue person(#RequestParam String view) {
MappingJacksonValue value = new MappingJacksonValue(new Person("John Doe", 44));
value.setSerializationView("onlyName".equals(view) ? Person.OnlyName.class : Person.Full.class);
return value;
}
Use this annotation and set the value to null, it will not be serialised:
#JsonInclude(Include.NON_NULL)

How To Configure MongoDb Collection Name For a Class in Spring Data

I have a collection called Products in my MongoDB database, which is represented by the interface IProductPrice in my Java code. The following repository declaration causes Spring Date to look to the collection db.collection: Intelliprice.iProductPrice.
I want it to configure it to look in db.collection: Intelliprice.Products using an external configuration rather than putting an #Collection(..) annotation on IProductPrice. Is this possible? How can I do this?
public interface ProductsRepository extends
MongoRepository<IProductPrice, String> {
}
The only way you can currently achieve this is by annotating your domain class with #Document using the collection property to define the name of the collection instances of this class shall be persisted to.
However, there's a JIRA issue open that suggests adding a pluggable naming strategy to configure the ways class, collection and property names are handled in a more global way. Feel free to comment your use case and vote it up.
using answer from Oliver Gierke above,
working on a project where I need to create multiple collections for one entity, I wanted to use the spring repositories and needed to specify the entity to use before using the repository.
I managed to modify the repository collection name on demand using this system, it using SPeL. You can only work on 1 collection at a time though.
Domain object
#Document(collection = "#{personRepository.getCollectionName()}")
public class Person{}
Default Spring Repository:
public interface PersonRepository
extends MongoRepository<Person, String>, PersonRepositoryCustom{
}
Custom Repository Interface:
public interface PersonRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
implementation:
public class PersonRepositoryImpl implements PersonRepositoryCustom {
private static String collectionName = "Person";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
To use it:
#Autowired
PersonRepository personRepository;
public void testRetrievePeopleFrom2SeparateCollectionsWithSpringRepo(){
List<Person> people = new ArrayList<>();
personRepository.setCollectionName("collectionA");
people.addAll(personRepository.findAll());
personDocumentRepository.setCollectionName("collectionB");
people.addAll(personRepository.findAll());
Assert.assertEquals(4, people.size());
}
Otherwise if you need to use configuration variables, you could maybe use something like this? source
#Value("#{systemProperties['pop3.port'] ?: 25}")
A little late,
but I've found you can set the mongo collection name dynamically in spring-boot accessing the application configuration directly.
#Document(collection = "#{#environment.getProperty('configuration.property.key')}")
public class DomainModel {...}
I suspect you can set any annotation attribute this way.
The only comment I can add is that you have to add # prefix to the bean name:
collection = "#{#beanName.method()}"
for the bean factory to inject the bean:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config {
}
I struggled to figure it out..
COMPLETE EXAMPLE:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config implements Serializable {
#Id
private String uuid;
private String profile;
private String domain;
private String label;
private Map<String, Object> data;
// get/set
}
public interface ConfigRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
#Component("configRepositoryCustom")
public class ConfigRepositoryCustomImpl implements ConfigRepositoryCustom {
private static String collectionName = "config";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
#Repository("configurations")
public interface ConfigurationRepository extends MongoRepository<Config, String>, ConfigRepositoryCustom {
public Optional<Config> findOneByUuid(String Uuid);
public Optional<Config> findOneByProfileAndDomain(String profile, String domain);
}
usage in serviceImpl:
#Service
public class ConfigrationServiceImpl implements ConfigrationService {
#Autowired
private ConfigRepositoryCustom configRepositoryCustom;
#Override
public Config create(Config configuration) {
configRepositoryCustom.setCollectionName( configuration.getDomain() ); // set the collection name that comes in my example in class member 'domain'
Config configDB = configurationRepository.save(configuration);
return configDB;
}
I use static class and method in SpEL;
public class CollectionNameHolder {
private static final ThreadLocal<String> collectionNameThreadLocal = new ThreadLocal<>();
public static String get(){
String collectionName = collectionNameThreadLocal.get();
if(collectionName == null){
collectionName = DataCenterApiConstant.APP_WECHAT_DOCTOR_PATIENT_COLLECTION_NAME;
collectionNameThreadLocal.set(collectionName);
}
return collectionName;
}
public static void set(String collectionName){
collectionNameThreadLocal.set(collectionName);
}
public static void reset(){
collectionNameThreadLocal.remove();
}
}
In Entity class ,#Document(collection = "#{T(com.test.data.CollectionNameHolder).get()}")
And then ,use
CollectionNameHolder.set("testx_"+pageNum)
in Service , and
CollectionNameHolder.reset();
Hope it helps you.

Categories