I have an entity that gets "userType" during a query to the database.
public class OrderEntityXml {
#Id
#SequenceGenerator(name = "jpa.Sequence.t.order", sequenceName = "T_ORDER_SEQ", allocationSize = 1)
#GeneratedValue(generator = "jpa.Sequence.t.order", strategy = GenerationType.SEQUENCE)
private Long id;
private String customer;
#Type(type = "Order")
#Column(name = "order_xml")
private Order order;
public OrderEntityXml() {
}
There is an container for getting from an entity.
This container is using by Jackson.
This container also is using marshaller and unmarshaller
#XmlRootElement
#JacksonXmlRootElement(localName = "order")
public class Order implements Serializable {
private String customer;
#XmlElement(name = "orderItem")
#JacksonXmlProperty(localName = "orderItem")
#JacksonXmlElementWrapper(useWrapping = false)
private List<OrderItem> orderItems = new ArrayList<>();
public Order() {
}
public class OrderItem {
private String sku;
private Double price;
public OrderItem() {
}
I have set up a MapStruct.
CycleAvoidingMappingContext - ( This is necessary to avoid Cycling (and the appearance of stack overflow))
#Component
public class CycleAvoidingMappingContext {
private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();
#BeforeMapping
public <T> T getMappedInstance(Object source, #TargetType Class<T> targetType) {
T t = (T) knownInstances.get(source);
return t;
}
#BeforeMapping
public void storeMappedInstance(Object source, #MappingTarget Object target) {
knownInstances.put( source, target );
}
}
public interface CommonMapper<D, E> {
D toDto(E e, #Context CycleAvoidingMappingContext context);
E toEntity(D d, #Context CycleAvoidingMappingContext context);
Iterable<D> toListDto(Iterable<E> entityList);
Iterable<E> toListEntity(Iterable<D> dtoList);
}
MapperUtils - (This is utils for MapStruct)
public static Order convertToDto(OrderEntityXml orderEntityXml){
Order order = new Order();
String customer = orderEntityXml.getCustomer();
order.setCustomer(customer);
Order xmlOrder = orderEntityXml.getOrder();
List<OrderItem> orderItems = xmlOrder.getOrderItems();
order.setOrderItems(orderItems);
return order;
}
public static OrderEntityXml convertToEntity(Order order){
OrderEntityXml orderEntityXml = new OrderEntityXml();
String customer = order.getCustomer();
orderEntityXml.setCustomer(customer);
List<OrderItem> orderItems = order.getOrderItems();
Order orderInEntity = new Order();
orderInEntity.setOrderItems(orderItems);
orderInEntity.setCustomer(customer);
orderEntityXml.setOrder(orderInEntity);
return orderEntityXml;
}
OrderDtoMapper - It is basic interface for me, for classes generation
#Mapper(componentModel = "spring")
public interface OrderDtoMapper extends CommonMapper<Order, OrderEntityXml> {
#Override
default Order toDto(OrderEntityXml orderEntityXml, CycleAvoidingMappingContext context) {
return convertToDto(orderEntityXml);
}
#Override
default OrderEntityXml toEntity(Order order, CycleAvoidingMappingContext context) {
return convertToEntity(order);
}
#Override
default Iterable<Order> toListDto(Iterable<OrderEntityXml> entityList) {
Iterable<Order> collect = StreamSupport.stream(entityList.spliterator(), false)
.map(MapperUtils::convertToDto)
.collect(Collectors.toList());
return collect;
}
}
For each data type, you will have to make your own utilities, and this is cumbersome.
Сan I remove extra code and configure it via MapStruct interfaces ?
I suspect I've complicated the code.
Сan anyone tell you what needs to be tweaked or what technology can be applied to mapStruct to make the code unified
Looking at the posted entities I don't see why you need to use CycleAvoidingMappingContext. There is no cyclic dependency between your objects.
In order to avoid the manual code you've written you can use the MapStruct #Mapping annotation to customize how certain fields need to be mapped.
So in your case it would be something like:
#Mapper(componentModel = "spring")
public interface OrderDtoMapper extends CommonMapper<Order, OrderEntityXml> {
#Override
#Mapping(target = "orderItems", source = "order.orderItems")
Order toDto(OrderEntityXml orderEntityXml, #Context CycleAvoidingMappingContext context);
#Override
#Mapping(target = "order", source = "order")
OrderEntityXml toEntity(Order order, CycleAvoidingMappingContext context);
Order cloneOrder(Order order);
}
The only customizations you need are the following:
For toDto to tell MapStruct that when you are mapping from OrderEntityXml to Order you want to map the order.orderItems into the orderItems.
For toEntity to tell MapStruct that you when you are mapping from Order to OrderEntityXml you want to map the method order parameters into the order of the `OrderEntityXml
Additionally we add Order cloneOrder(Order) so that MapStruct creates a new object when mapping between Order otherwise the same object will be used.
The customer in both cases will be automatically mapped, since it matches on both sides.
There is no need to provide custom method for the Iterable mapping because MapStruct will do that automatically for you. It knows
Related
I have a composite primary key made of planId and planDate, when the user gives me both this attributes I cant find a way to retrieve it from my Repo. Should findById work like this?
public Plans assignPlansToMeds(Long id, Long planId, Date planDate) {
Set<Meds> medsSet = null;
Meds meds = medsRepo.findById(id).get();
Plans plans = plansRepo.findById(planId, planDate).get();
medsSet = plans.getAssignedMeds();
medsSet.add(meds);
plans.setAssignedMeds(medsSet);
return plansRepo.save(plans);
}
My Primary Key:
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class PlansPKId implements Serializable {
private long planId;
private Date planDate; // format: yyyy-mm-dd
}
Plans entity:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "plans")
public class Plans {
#EmbeddedId
private PlansPKId plansPKId;
#Column
private String planName;
#Column
private String weekday;
#ManyToMany
#JoinTable(name = "Plan_Meds", joinColumns = {
#JoinColumn(name = "planDate", referencedColumnName = "planDate"),
#JoinColumn(name = "planId", referencedColumnName = "planId") }, inverseJoinColumns = #JoinColumn(name = "id"))
private Set<Meds> assignedMeds = new HashSet<>();
}
where I ask for the planId and planDate:
#PutMapping("/medicine/{id}/assignToPlan/{planId}/date/{plandate}")
public Plans assignMedToPlan(#PathVariable Long id, #PathVariable Long planId, #PathVariable Date planDate){
return assignService.assignPlansToMeds(id, planId, planDate);
}
The Spring JpaRepository only allows one type as the ID-type, as can be seen in the javadoc. Therefore, findById will never accept two arguments.
You need to define your repository with your EmbeddedId-type as ID-type as follows:
#Repository
public interface PlansRepository extends JpaRepository<Plans, PlansPKId> {
}
You can then call the findById method as follows:
Plans plans = plansRepo.findById(new PlansPKId(planId, planDate))
.orElseThrow(PlansNotFoundException.idAndDate(planId, planDate));
If you dont want to create a new PlansPKId instance for every query, you could also define a repository method as follows and let Spring derive the query based on the method name:
Optional<Plans> findByPlansPKIdPlanIdAndPlansPKIdPlanDate(long planId, Date planDate);
If you don't like to have a cumbersome method name you could as well define a JPQL query and name the method as you like:
#Query("select p from Plans p where p.plansPKId.planId = :planId and p.plansPKId.planDate = :planDate")
Optional<Plans> findByCompositeId(#Param("planId) long planId, #Param("planDate") Date planDate);
On a side note, I strongly encourage you to use LocalDate, LocalDateTime or ZonedDateTime (depending on your needs) instead of the legacy Date class.
Moreover, you shouldn't call get() on an Optional without checking if it is present. I recently wrote an answer on SO describing how you can create an elegant error handling. If you stuck to my example, you had to create a NotFoundException and then create the PlansNotFoundException which extends NotFoundException. by this means, everytime when a PlansNotFoundException is thrown in thread started by a web request, the user would receive a 404 response and a useful message if you implement it like this:
public abstract class NotFoundException extends RuntimeException {
protected NotFoundException(final String object, final String identifierName, final Object identifier) {
super(String.format("No %s found with %s %s", object, identifierName, identifier));
}
protected NotFoundException(final String object, final Map<String, Object> identifiers) {
super(String.format("No %s found with %s", object,
identifiers.entrySet().stream()
.map(entry -> String.format("%s %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(" and "))));
}
}
public class PlansNotFoundException extends NotFoundException {
private PlansNotFoundException(final Map<String, Object> identifiers) {
super("plans", identifiers);
}
public static Supplier<PlansNotFoundException> idAndDate(final long planId, final Date planDate) {
return () -> new PlansNotFoundException(Map.of("id", id, "date", date));
}
}
For the Meds case:
public class MedsNotFoundException extends NotFoundException {
private MedsNotFoundException(final String identifierName, final Object identifier) {
super("meds", identifierName, identifier);
}
public static Supplier<MedsNotFoundException> id(final long id) {
return () -> new MedsNotFoundException("id", id);
}
}
Meds meds = medsRepo.findById(id).orElseThrow(MedsNotFoundException.id(id));
#Data
public class FilesDTO {
private int issue;
private String uniqueStr;
private StorageDomain xml;
private StorageDomain pdf;
private StorageDomain stop;
}
#Data
public class BackHalfDomain {
private int articleId;
private String uniqueStrr;
private long xmlContentId;
private long pdfContentId;
private long stopId;
private int issueNumber;
}
Using a repository class I have to fetch a StorageDomain object from the ID in BackHalfDomain. So I have to map StorageDomain object with respective fields.
like StorgeDomain sd = repo.findById(id).get(); and set this sd object in FilesDTO's xml field and so on.
This is my mapper
#Mapper(componentModel = "spring")
public interface FilesDTOMapper {
public static final FilesDTOMapper fileDTOMapper = Mappers.getMapper(FilesDTOMapper.class);
#Mapping(target = "issue", source = "domain.issueNumber")
#Mapping(target = "DOI", source = "domain.doi")
public FilesDTO map(BackHalfDomain domain);
}
I have used uses but wasn't successful.
I have used #Mapping(target="xyz", expression="java(repo.findById(id))")"
but all I got was NullPointerException
Spring injection isin't working.
Can someone have a solution for this? I am just started with mapstruct
Since mapstruct 1.2 you can use a combination of #AfterMapping and #Context.
#Mapper(componentModel="spring")
public interface FilesDTOMapper {
#Mapping(target = "xyz", ignore = true)
#Mapping(target = "issue", source = "domain.issueNumber")
#Mapping(target = "DOI", source = "domain.doi")
FilesDTO map( BackHalfDomain domain, #Context MyRepo repo);
#AfterMapping
default void map( #MappingTarget FilesDTO target, BackHalfDomain domain, #Context MyRepo repo) {
target.setXYZ(repo.findById(domain.getId()));
}
}
In 1.1 you would need to transform the mapper to a abstract class
#Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring",
uses = {})
public abstract class FilesDTOMapper {
#Autowired
protected MyRepo repo;
#Mapping(target = "issue", source = "domain.issueNumber")
#Mapping(target = "DOI", source = "domain.doi")
#Mapping(target="xyz", expression="java(repo.findById(domain.getId())))")
public FilesDTO map(BackHalfDomain domain);
}
I ran into this same problem. The solution was to use a Decorator as suggested in this answer. Following your code, the solution would be something like the following.
First, we have to specifiy the Decorator in the Mapper:
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
#Primary
#DecoratedWith(FilesDTOMapperDecorator.class)
public interface FilesDTOMapper {
// basic mapping here
public FilesDTO map(BackHalfDomain domain);
}
Then, implement the Decorator:
public abstract class FilesDTOMapperDecorator implements FilesDTOMapper {
#Autowired
#Qualifier("delegate")
private FilesDTOMapper delegate;
#Autowired
private SomeRepository someRepository;
#Override
public FilesDTO map(BackHalfDomain domain) {
// Delegate basic mapping
FilesDTO filesDTO = delegate.map(domain);
// Use the repository as needed to set additional mapping
filesDTO.setSomeValue(repo.findById(id).getSomeValue());
return filesDTO;
}
}
I use lambda to convert objects one type to objects another
Order
#JacksonXmlRootElement(localName = "order")
public class Order {
private String customer;
#JacksonXmlProperty(localName = "orderItem")
#JacksonXmlElementWrapper(useWrapping = false)
private List<OrderItem> orderItems = new ArrayList<>();
public Order() {
}
OrderDto
public class OrderDto {
private String customer;
private String orderXml;
public OrderDto() {
}
service
#Service
public class OrderReadServiceImpl implements OrderReadService {
private OrderEntityRepository repository;
private OrderDtoMapper mapper;
private CycleAvoidingMappingContext context;
#Autowired
public OrderReadServiceImpl(OrderEntityRepository repository,
OrderDtoMapper mapper,
CycleAvoidingMappingContext context) {
this.repository = repository;
this.mapper = mapper;
this.context = context;
}
#Override
public Iterable<Order> getListOrder() {
Iterable<OrderEntity> orderEntities = this.repository.findAll();
Iterable<Order> orders = convertXmlToListObj(orderEntities);
return orders;
}
private Iterable<Order> convertXmlToListObj(Iterable<OrderEntity> entities) {
Iterable<OrderDto> dtoList = toListDto(entities);
Iterable<Order> orders = convertListToList(dtoList);
return orders;
}
/**
* There is convert a collection of objects one type to another type
* #param dtoList
* #return
*/
private static Iterable<Order> convertListToList(Iterable<OrderDto> dtoList) {
List<OrderDto> list = new ArrayList<>();
dtoList.forEach(list::add);
List<Order> collect = list.stream()
.map(orderDto -> {
Order order = convertXmlToObj(orderDto);
return order;
}).collect(Collectors.toList());
return collect;
}
/**
* there is got a string that xml. This xml is convert to java object
* #param orderDto
* #return
*/
private static Order convertXmlToObj(OrderDto orderDto) {
String orderXml = orderDto.getOrderXml();
StringReader reader = new StringReader(orderXml);
Order order = JAXB.unmarshal(reader, Order.class);
return order;
}
/**
* transform objects of entity type to objects of dto types
* #param entities
* #return
*/
private Iterable<OrderDto> toListDto(Iterable<OrderEntity> entities) {
return this.mapper.toListDto(entities);
}
}
The resulting list of entities is converted to a dto collection. The collection of the converted dto list is iterated over and retrieved from there xml from the field of each collection element and then the structure of this xml it will be umarshall (that is, the list of xml elements will be converted to the collection of java objects)
List<OrderDto> list = new ArrayList<>();
dtoList.forEach(list::add);
List<Order> collect = list.stream()
.map(orderDto -> {
Order order = convertXmlToObj(orderDto);
return order;
}).collect(Collectors.toList());
return collect;
I would desire to do simplest. I want the code to will be yet lesser.
Сan you remove the code somewhere, how to reduce it.
I mean . Where do I create the 'method references' yet.
Who has any ideas how to do this ?
You can convert the Iterable to Stream directly, without creating a List:
StreamSupport.stream(dtoList.spliterator(), false)
Your code can become
private static Iterable<Order> convertListToList(Iterable<OrderDto> dtoList)
{
return StreamSupport.stream(dtoList.spliterator(), false)
.map(orderDto -> convertXmlToObj(orderDto))
.collect(Collectors.toList());
}
Or with method reference:
private static Iterable<Order> convertListToList(Iterable<OrderDto> dtoList)
{
return StreamSupport.stream(dtoList.spliterator(), false)
.map(OrderReadServiceImpl::convertXmlToObj)
.collect(Collectors.toList());
}
BTW, since your method is named convertListToList(), perhaps it should accept and return Lists instead of Iterables.
If you want to solve the problem of mapping objects in general and are not looking exactly for an optimization of your lambda/stream solution, you could give MapStruct a look. Simplified description: It generates mappers with the help of annotations at compile time.
I wonder if and how Mapstruct could help with mapping ojects with bi-directional relations (in my case one to many):
public class A{
private Set<B> listB;
}
public class B{
private A a;
}
Mapping from/to an entity yields a StackOverflowError. (i would expect this to happen).
On the other hand the closed Mapstruct issues 469 and 1163 seem to imply that mapstruct will not directly support it.
I tried this example:
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapping-with-cycles
But this is just not working. With or without applying the "CycleAvoidingMappingContext" the stackoverflowerror keeps the same.
So how to map objects with cycles and leveraging mapstruct? (i am want to avoid complete manual mapping)
In order for the mapping to work you can try with the following mapper:
#Mapper
public interface MyMapper {
A map(A a, #Context CycleAvoidingMappingContext context);
Set<B> map(Set<B> b, #Context CycleAvoidingMappingContext context);
B map(B b, #Context CycleAvoidingMappingContext context);
}
If the methods for mapping from Set<B> into Set<B> is not there then the set in A won't be mapped.
In meantime i found a solution: First i ignore the field in question and map it in "AfterMapping". In my case i want to map also the child independently. In my case the #Context only contains a Boolean (boolean is not working) that tells where i entered the cycle (startedFromPru pru is the parent):
Mapper for parent (contains set of licenses):
#Mapper(componentModel = "spring", uses = {DateTimeMapper.class, LicenseMapper.class, CompanyCodeMapper.class, ProductHierarchyMapper.class})
public abstract class ProductResponsibleUnitMapper {
#Autowired
private LicenseMapper licenseMapper;
#Mappings({#Mapping(target = "licenses", ignore = true)})
public abstract ProductResponsibleUnit fromEntity(ProductResponsibleUnitEntity entity, #Context Boolean startedFromPru);
#Mappings({#Mapping(target = "licenses", ignore = true)})
public abstract ProductResponsibleUnitEntity toEntity(ProductResponsibleUnit domain, #Context Boolean startedFromPru);
#AfterMapping
protected void mapLicenseEntities(ProductResponsibleUnit pru, #MappingTarget ProductResponsibleUnitEntity pruEntity, #Context Boolean startedFromPru){
if(startedFromPru) {
pruEntity.getLicenses().clear(); //probably not necessary
pru.getLicenses().stream().map(license -> licenseMapper.toEntity(license, startedFromPru)).forEach(pruEntity::addLicense);
}
}
#AfterMapping
protected void mapLicenseEntities(ProductResponsibleUnitEntity pruEntity, #MappingTarget ProductResponsibleUnit pru, #Context Boolean startedFromPru){
if(startedFromPru) {
pru.getLicenses().clear(); //probably not necessary
pruEntity.getLicenses().stream().map(licenseEntity -> licenseMapper.fromEntity(licenseEntity, startedFromPru)).forEach(pru::addLicense);
}
}
}
Mapper for child:
#Mapper(componentModel = "spring", uses = { DateTimeMapper.class, CompanyCodeMapper.class, ProductHierarchyMapper.class,
CountryCodeMapper.class })
public abstract class LicenseMapper {
#Autowired
private ProductResponsibleUnitMapper pruMapper;
#Mappings({ #Mapping(source = "licenseeId", target = "licensee"), #Mapping(target = "licensor", ignore = true) })
public abstract License fromEntity(LicenseEntity entity, #Context Boolean startedFromPru);
#Mappings({ #Mapping(source = "licensee", target = "licenseeId"), #Mapping(target = "licensor", ignore = true) })
public abstract LicenseEntity toEntity(License domain, #Context Boolean startedFromPru);
#AfterMapping
protected void mapPru(LicenseEntity licenseEntity, #MappingTarget License license,
#Context Boolean startedFromPru) {
//only call ProductResponsibleUnitMapper if mapping did not start from PRU -> startedFromPru is false
if (!startedFromPru) {
license.setLicensor(pruMapper.fromEntity(licenseEntity.getLicensor(), startedFromPru));
}
}
#AfterMapping
protected void mapPru(License license, #MappingTarget LicenseEntity licenseEntity,
#Context Boolean startedFromPru) {
//only call ProductResponsibleUnitMapper if mapping did not start from PRU -> startedFromPru is false
if (!startedFromPru) {
licenseEntity.setLicensor(pruMapper.toEntity(license.getLicensor(), startedFromPru));
}
}
Haven't figured out how to pass in additional arguments or an alternative.
Currently I'm mapping an Order and OrderLines. Both objects are different and need #mappings. Example I have an Order and OrderRequest object both are different and need #Mappings annotation to map the values, same with OrderLines.
I've created the following mappers
OrderMapper (uses = OrderLineMappers.class)
OrderLineMappers (uses = OrderLineMapper.class)
OrderLineMapper
So my issue is the OrderLine needs the OrderId from the Order object. However in the OrderLineMapper it's passing in the OrderLine and not the Order. How can I send the OrderId to the OrderLineMapper? Currently I have the OrderMapper doing an #AfterMapper, looping through the orderLines and populating with the OrderId.
Any help would be great.
Class OrderMapper
#Mapper(componentModel = "spring", uses = {OrderLineMappers.class})
public abstract class OrderMapper {
#AfterMapping
protected void orderRequestFromOrder( Order order, #MappingTarget
OrderRequest orderRequest ) {
//Wanting to do this at the OrderLineMapper class and not here
String orderId = order.getId();
List<OrderLineRequest> lines = orderRequest.getOrderLines();
List<OrderLineRequest> updatedLines = new ArrayList<>();
for (OrderLineRequest line : lines) {
line.setOrderId(orderId);
updatedLines.add(line);
}
orderRequest.setOrderLines(updatedLines);
}
#Mappings({
#Mapping( source = "orderId", target = "id" ),
#Mapping( source = "orderNumber", target = "order_number" ),
#Mapping( source = "orderLines", target = "orderLines")
})
public abstract Order orderRequestToOrder(OrderRequest orderRequest);
Class OrderLineMappers
#Mapper(componentModel = "spring", uses = {OrderLineMapper.class})
public interface OrderLineMappers {
List<OrderLine> orderLines(List<OrderLineRequest> orderLineRequest);
#InheritInverseConfiguration
List<OrderLineRequest> orderLineRequests(List<OrderLine> orderLine);
}
Class OrderLineMapper
#Mapper(componentModel = "spring")
public abstract class OrderLineMapper {
#Mappings({
#Mapping( target = "orderId", source = "orderLineId" ),
#Mapping( target = "order_line_number", source = "orderLineNumber")
})
public abstract OrderLine orderLineRequestToOrderLine(OrderLineRequest orderLineRequest);
}
Again just trying to pass in the OrderId to the OrderLineMapper. Not sure how to do this.
You can't really do this. What you can do instead is to use the #Context and perform your logic in it. You can have #AfterMapping and #BeforeMapping in your context where you can store the Order id and use that during its execution
e.g.
public class OrderMappingContext {
protected String currentOrderId;
#BeforeMapping
public void startMappingOrder(#MappingTarget Order order, OrderRequest request) {
this.currentOrderId = request.getId();
}
#AfterMapping
public void finishOrderMapping(#MappingTarget Order order) {
this.currentOrderId = null;
}
#AfterMapping
public void afterOrderLineMapping(#MappingTarget OrderLine orderLine) {
orderLine.setOrderId(this.currentOrderId);
}
}
Your mappers will then look like:
#Mapper(componentModel = "spring", uses = {OrderLineMappers.class})
public abstract class OrderMapper {
#Mapping( source = "orderId", target = "id" ),
#Mapping( source = "orderNumber", target = "order_number" ),
#Mapping( source = "orderLines", target = "orderLines")
public abstract Order orderRequestToOrder(OrderRequest orderRequest, #Context OrderMappingContext context);
#Mapper(componentModel = "spring", uses = {OrderLineMapper.class})
public interface OrderLineMappers {
List<OrderLine> orderLines(List<OrderLineRequest> orderLineRequest, #Context OrderMappingContext context);
#InheritInverseConfiguration
List<OrderLineRequest> orderLineRequests(List<OrderLine> orderLine);
}
#Mapper(componentModel = "spring")
public abstract class OrderLineMapper {
#Mapping( target = "orderId", source = "orderLineId" ),
#Mapping( target = "order_line_number", source = "orderLineNumber")
public abstract OrderLine orderLineRequestToOrderLine(OrderLineRequest orderLineRequest, #Context OrderMappingContext context);
}
On the invocation side you will do something like
orderMapper.orderRequestToOrder(request, new OrderMappingContext());