I'm trying to use a mapstruct and I need to mapping Entity with a sub Entity list, I have relationship oneToMany and manyToOne and I need to mapping in both cases:
#Data
#Entity
public class EmailEntity {
private int id;
... // some fields
#ManyToOne
private DeliveredEmailInfoEntity deliveredEmailInfo;
}
.
#Data
#Entity
public class DeliveredEmailInfoEntity {
private int id;
... // some fields
#OneToMany
private List<EmailEntity> emails;
}
mapping to:
#Data
public class EmailDTO {
private int id;
... // some fields
private DeliveredEmailInfoDTO deliveredEmailInfo;
}
.
#Data
public class DeliveredEmailInfoDTO {
private int id;
... // some fields
private List<EmailDTO> emails;
}
How to do it in the best way ?
To avoid infinite cross setting of nested fields you should limit this dependency, for example on the second nested level, i.e. your root EmailDTO will have one nested DeliveredEmailInfoDTO object (many-to-one relationship), while your root DeliveredEmailInfoDTO will have the list of nested EmailDTO objects (one-to-many relationship) and nothing on the next nesting level:
#Mapper(uses = DeliveredEmailInfoMapper.class)
public interface EmailMapper {
#Mapping(target = "deliveredEmailInfo.emails", ignore = true)
EmailDTO toDTO(EmailEntity entity);
// other methods omitted
#Named("emailDTOList")
default List<EmailDTO> toEmailDTOList(List<EmailEntity> source) {
return source
.stream()
.map(this::toDTO)
.peek(dto -> dto.setDeliveredEmailInfo(null))
.collect(Collectors.toList());
}
}
#Mapper(uses = EmailMapper.class)
public interface DeliveredEmailInfoMapper {
#Mapping(target = "emails", source = "emails", qualifiedByName = "emailDTOList")
DeliveredEmailInfoDTO toDTO(DeliveredEmailInfoEntity entity);
// other methods omitted
}
(Also see other answer)
It should be straightforward, there is nothing challenging in your case:
#Mapper
public interface EmailInfoMapper {
EmailDTO entityToDTO(EmailEntity duration);
EmailEntity dtoToEntity(EmailDTO price);
DeliveredEmailInfoDTO entityToDTO(DeliveredEmailInfoEntity duration);
DeliveredEmailInfoEntity dtoToEntity(DeliveredEmailInfoDTO price);
}
You should include your mapper in your question and what the problem you have with it.
Related
I want to create a unit test that will use reflection to find all missing fields in dto that implement BaseDto by their persistence entities. This is what I did.
#Slf4j
public class EntityAuditDtoTest {
#Test
public void find_MissingAndExtraFieldsThatUsedInAuditDtosByEntity_ReturnMissingAndExtraFields() throws ClassNotFoundException {
// Arrange
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(AuditEntityType.class));
// Find all classes annotated with #AuditEntityType in the package com.example.dto
Set<BeanDefinition> auditDtoBeans = scanner.findCandidateComponents("com.example.dto");
// Act
for (BeanDefinition auditDtoBean : auditDtoBeans) {
Class<?> auditDtoClass = Class.forName(auditDtoBean.getBeanClassName());
// Make sure the DTO class implements BaseAuditDto
if (!BaseAuditDto.class.isAssignableFrom(auditDtoClass)) {
continue;
}
Class<?> entityClass = getEntityClassForDto(auditDtoClass);
Field[] dtoFields = auditDtoClass.getDeclaredFields();
Field[] entityFields = entityClass.getDeclaredFields();
List<String> missingFields = Arrays.stream(entityFields).map(Field::getName)
.filter(field -> Arrays.stream(dtoFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!missingFields.isEmpty()) {
log.error("Missing fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), missingFields);
}
List<String> extraFields = Arrays.stream(dtoFields).map(Field::getName)
.filter(field -> Arrays.stream(entityFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!extraFields.isEmpty()) {
log.error("Extra fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), extraFields);
}
}
}
}
But the problem is that the dto may have a field that is in the entity class, but the test will think that this is a missing field.
For example:
Dto class: ContractAudit has customerId field (customerId). And ContractEntity has public CustomerEntity customer.
This is the same fields. But of course for test they are different. I don't understand how to ignore them. I also don't want to hardcode filter that skip all endings with 'id' prefix.
#Data
#AuditEntityType("Contract")
public class ContractAudit implements BaseAuditDto {
private Long id;
private String ref;
private String status;
private Long customerId;
}
#Entity
#Table(name = "contract")
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class ContractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#ToString.Include
private Long id;
#Column(name = "ref", updatable = true)
#ToString.Include
private String ref;
#Column(name = "status")
#ToString.Include
#Enumerated(value = EnumType.STRING)
private ContractStatusEnum status;
#ManyToOne
#JoinColumn(name = "customer_id")
public CustomerEntity customer;
#Column(name = "deleted")
#ToString.Include
private boolean deleted;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "contract_id")
private List<ContractDocumentEntity> documents;
}
Output:
Missing fields in DTO class: ContractAudit for entity class: ContractEntity : [customer, deleted, documents]
Extra fields in DTO class: ContractAudit for entity class: ContractEntity : [customerId]
I want to have missing fields: [deleted, documents]
If you have any other ideas on how to do this, I'd love to hear it. I am not asking for implementation. Suggestions only)
Lol. I found solution for my case.
My previous approach was incorrect. Because it's impossible to find 'missing' and 'extra' fields by name correctly for every case. I decided to use:
assertThat(entityClass.getDeclaredFields()).hasSameSizeAs(auditDtoClass.getDeclaredFields());
So this code is checking if the entityClass and the DtoClass have the same number of fields (properties) declared. If not it fail test and print all fields from each classes. If anyone has better ideas I'll be happy to hear.
In my DB schema I have a Post entity which can have a list of PostComment entities and every PostComment entity can have a list of PostCommentUpvote entities and a list of PostCommentDownvote entities (all self-explanatory I suppose).
Post:
public class Post {
...
private List<PostComment> postComments;
...
}
PostComment:
public class PostComment {
...
private List<PostCommentUpvote> postCommentUpvotes;
private List<PostCommentDownvote> postCommentDownvotes;
...
}
PostCommentUpvote and PostCommentDownvote have the same fields (but semantically are different):
public class PostCommentUpvote {
private Long id;
...
}
The end goal is to get all Post comments (List<PostComment>)
Target response DTO:
public class PostCommentsResponseDto {
private List<PostCommentResponseDto> comments;
private Integer count; // count is the size of the list of PostComments or PostCommentsResponseDto - they are semantically the same, PostCommentsResponseDto just has less fields
}
PostCommentResponseDto:
public class PostCommentResponseDto {
private Long id;
private String comment;
private String username;
private List<PostCommentUpvoteResponseDto> postCommentUpvotes;
private List<PostCommentDownvoteResponseDto> postCommentDownvotes;
private Timestamp createdAt;
private Timestamp updatedAt;
}
PostCommentUpvoteResponseDto and PostCommentDownvoteResponseDto are the same:
public class PostCommentUpvoteResponseDto {
private Long id;
}
So I'm basically doing mapping from Post to PostCommentsResponseDto.
PostMapper:
#Mapper(componentModel = "spring", uses = { PostCommentMapper.class })
public interface PostMapper {
#Named("postCommentsMapper")
default List<PostCommentResponseDto> postCommentsMapper(List<PostComment> postComments) {
// how to map List<PostComment> to List<PostCommentResponseDto> ?
}
#Named("postCommentsQuantityMapper")
default Integer postCommentsQuantityMapper(List<PostComment> postComments) {
return postComments.size();
}
#Mapping(source = "postComments", target = "comments", qualifiedByName = "postCommentsMapper")
#Mapping(source = "postComments", target = "count", qualifiedByName = "postCommentsQuantityMapper")
PostCommentsResponseDto postPostCommentsResponseDtoMapper(Post post);
}
PostCommentMapper:
#Mapper(componentModel = "spring", uses = { PostCommentUpvoteMapper.class, PostCommentDownvoteMapper.class })
public interface PostCommentMapper {
#Mapping(source = "user.username", target = "username")
public PostCommentResponseDto postCommentPostCommentResponseDtoMapper(PostComment postComment);
}
PostCommentUpvoteMapper and PostCommentDownvoteMapper are the same:
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PostCommentUpvoteMapper {
PostCommentUpvoteResponseDto postcommentUpvotePostCommentUpvoteResponseDto(PostCommentUpvote postCommentUpvote);
}
The problem: How to map List<PostComment> to List<PostCommentResponseDto> which requires mapping User entity to just String username and more importantly mapping nested collections PostCommentUpvoteResponseDto and PostCommentDownvoteResponseDto? With the presented setup the final result is comments are null (which is bad because it should be empty array/list) and counter is 0 (which is okay because selected post doesn't have any comments)
Please ask if more clarification are needed.
hope you found solution, but if you are still need help, this is how I would handle it.
Basically, you should provide a mapper method for mapping an object and also method for mapping list of the same objects and MapStruct will do the rest.
#Mapping(source = "user.username", target = "username")
PostCommentResponseDto postCommentToPostCommentResponseDto(PostComment postComment);
List<PostCommentResponseDto> postCommentToPostCommentResponseDto(List<PostComment> postComment);
PostCommentUpvoteResponseDto postCommentUpvoteToPostCommentUpvoteResponseDto(PostCommentUpvote postCommentUpvote);
List<PostCommentUpvoteResponseDto> postCommentUpvoteToPostCommentUpvoteResponseDto(List<PostCommentUpvote> postCommentUpvote);
In example above MapStruct will automatically call method postCommentUpvoteToPostCommentUpvoteResponseDto inside the postCommentToPostCommentResponseDto method.
I have created small working example on GitHub so you can check it.
https://github.com/fpecek/MapstructDemo
I'm dealing with framework code that I cannot modify and which ends up throwing a NullPointerException during mapping because MapStruct thinks it should use a getter defined in a superclass.
Is there a way to tell MapStruct to ignore all getters marked with #JsonIgnore (a jackson library annotation) ?
Some more context
To provide a bit of code, here is part of the generated implementation by MapStruct:
if ( target.getChangedProperties() != null ) {
target.getChangedProperties().clear();
List<Property> list = src.getChangedProperties();
if ( list != null ) {
target.getChangedProperties().addAll( list );
}
}
The NPE is thrown from within target.getChangedProperties() because there are some uninitialized variables being accessed. However, in reality, I don't even want this getter to be part of MapStruct's implementation. (In fact, that getter isn't a getter for a specific variable, but more of a "utility getter", so I do wonder why MapStruct is trying to use it.)
My mapped class would look like:
#Entity
#Table(name = "myentity")
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyEntity extends TheFrameworkSuperClass {
#Id
private String id;
private String foo;
}
#MappedSuperclass
#JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT)
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class TheFrameworkSuperClass {
#Version
#JsonProperty(value = "Version")
private Long version;
#Transient
#JsonIgnore
protected UnitOfWorkChangeSet changes;
#Override
#JsonIgnore
public List<Property> getChangedProperties() {
// stuff happening before
this.changes.getObjectChangeSetForClone(this); // throws NPE
// stuff happening after
}
}
My MapStruct interface
I have no customization of the mapper's configs. And my interface is:
#Mapper(componentModel = "spring")
public interface MyMapper {
MyEntity boToBo(MyEntity destination);
void updateBo(MyEntity src, #MappingTarget MyEntity target);
}
I contemplated using #BeanMapping(ignoreByDefault = true) and then listing each field individually to make sure no extra getters are used, but that is far from being a satisfying solution due to the amount of times I'll have to do that.
Well, turns out even though the changedProperties field does not exist, since MapStruct picks up getChangedProperties() as a getter, you can nonetheless tell MapStruct to ignore that non-existing field...
#Mapper(componentModel = "spring")
public interface MyMapper {
#Mapping(target = "changedProperties", ignore = true)
MyEntity boToBo(MyEntity destination);
#Mapping(target = "changedProperties", ignore = true)
void updateBo(MyEntity src, #MappingTarget MyEntity target);
}
I have a class:
#Entity
#Table(name = "restaurants")
public class Restaurant extends AbstractNamedEntity implements Serializable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "restaurant")
private Set<Meal> meals = Collections.emptySet();
//other fields, getters, setters, constructors
}
I'm getting my data with Spring Data:
#Repository
public interface RestaurantRepository extends CrudRepository<Restaurant, Integer> {
}
I have REST-controller, which produces JSON data:
#RestController
#RequestMapping(value = RestaurantController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")
public class RestaurantController {
static final String REST_URL = "/restaurants";
#Autowired
private RestaurantRepository repository;
#GetMapping("{id}")
public List<Restaurant> getOne(#PathVariable Integer id) {
return repository.findById(id);
}
}
How to avoid including that LAZY data (set of Meals) to get them to a SQL request?
As I know I need to write a custom JacksonObjectMapper, but I don't know how to do it
You can use #JsonIgnore annotation in order to ignore the mapping of a field. Then you should do this:
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy = "restaurant")
private Set<Meal> meals = Collections.emptySet();
UPDATED
Based what you want to do "Ignore field dynamically when getting one or not getting alls" you can use #NamedEntityGraphs annotation to specific what fields you want to join, then by using #NamedEntityGraph you specify the path and boundaries for a find operation or query and you should use in your custom Repository the #EntityGraph annotation who allows to customize the fetch-graph based what you want to do.
So you should add the following code:
#Entity
#Table(name = "restaurants")
#NamedEntityGraphs({
#NamedEntityGraph(name="Restaurant.allJoins", includeAllAttributes = true),
#NamedEntityGraph(name="Restaurant.noJoins")
})
public class Restaurant extends AbstractNamedEntity implements Serializable {
}
#Repository
public interface RestaurantRepository extends CrudRepository<Restaurant, Integer> {
#EntityGraph(value = "Restaurant.allJoins", type = EntityGraphType.FETCH)
#Override
List<Restaurant> findAll();
#EntityGraph(value = "Restaurant.noJoins", type = EntityGraphType.FETCH)
#Override
Optional<Restaurant> findById(Integer id);
}
Hi I have a two tables like below .
1) Task - id,name
2) Resource - id,name,defaultTask(foreign key to Task.id)
The mapping is one to Many - one task can have many resource.
The code for Task is like below.
#Entity
public class Task implements Serializable {
private long m_id;
private String m_name;
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
public long getId() {
return this.m_id;
}
public void setId(long id) {
this.m_id = id;
}
public String getName() {
return this.m_name;
}
public void setName(String name) {
this.m_name = name;
}
#OneToMany
#JoinColumn(
name = "defaultTask"
)
private List<Resource> m_relatedResources;
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
public void setrelatedResources(List<Resource> relatedResources) {
m_relatedResources = relatedResources;
}
And the code for Resource class is like below.
#Entity
public class Resource implements Serializable {
private Long m_id;
private String m_name;
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
public Long getId() {
return this.m_id;
}
public void setId(Long id) {
this.m_id = id;
}
public String getName() {
return this.m_name;
}
public void setName(String name) {
this.m_name = name;
}
Task m_task;
#ManyToOne
#JoinColumn(
name = "defaultTask"
)
public Task getTask() {
return this.m_task;
}
public void setTask(Task task) {
this.m_task = task;
}
}
When i execute it I am getting an error like
Initial SessionFactory creation failed.org.hibernate.MappingException: Could not determine type for: java.util.List, for columns: [org.hibernate.mapping.Column(relatedResources)]
What have i done wrong ?How can i fix the problem ?
You can't apply annotations to methods or fields randomly. Normally, you should apply your annotations the same way as #Id..
In Task class OneToMany should be like
#OneToMany
#JoinColumn(
name = "defaultTask"
)
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
Field access strategy (determined by #Id annotation). Put any JPA related annotation right above each method instead of field / property as for your id it is above method and it will get you away form exception.
Also there appears to be an issue with your bidrectional mapping metntioned by #PredragMaric so you need to use MappedBy which signals hibernate that the key for the relationship is on the other side. Click for a really good question on Mapped by.
Many mistakes here:
you're annotating fields sometimes, and getters sometimes. Half of the annotation will be ignored: you must be consistent. It's one or the other.
You're not respecting the Java Bean naming conventions. The getter must be getRelatedResources(), not getrelatedResources().
A bidirectional association must have an owner side and an inverse side. In a OneToMany, the One is always the inverse side. The mapping should thus be:
.
#ManyToOne
#JoinColumn(name = "defaultTask")
public Task getTask() {
return this.m_task;
}
and
#OneToMany(mappedBy = "task")
public List<Resource> getRelatedResources() {
return m_relatedResources;
}
I also strongly advise you to respect the Java naming conventions. Variables should be named id and name, not m_id and m_name. This is especially important if you choose to annotate fields.
You're mixing annotating fields and getters in the same entity, you should move your #OneToMany to a getter
#OneToMany
#JoinColumn(mappedBy = "task")
public List<Resource> getrelatedResources() {
return m_relatedResources;
}
and yes, as the others mentioned, it should be mappedBy = "task". I'll upvote this teamwork :)
#JoinColumn is only used on owner's side of the relation, ToOne side, which is Resource#task in your case. On the other side you should use mappedBy attribute to specify bidirectional relation. Change your Task#relatedResources mapping to this
#OneToMany(mappedBy = "task")
private List<Resource> m_relatedResources;
Also, as #Viraj Nalawade noticed (and others, obviously), mapping annotations should be on fields or properties, whatever is used for #Id takes precedence. Either move #Id to field, or move #OneToMany to getter.