Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False warning for @Mapper #3510

Closed
eix128 opened this issue Jan 9, 2024 · 3 comments
Closed

False warning for @Mapper #3510

eix128 opened this issue Jan 9, 2024 · 3 comments

Comments

@eix128
Copy link

eix128 commented Jan 9, 2024

Expected behavior

I have Entity

package com.manage.jpa;

import com.manage.jpa.auth.UserEntity;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class GenericEntity extends BaseEntity {

    @LastModifiedDate
    @Column(columnDefinition = "timestamp default '2020-04-10 20:47:05.967394'")
    protected LocalDateTime lastModifiedDate;

    @CreatedDate
    @Column(columnDefinition = "timestamp default '2020-04-10 20:47:05.967394'", updatable = false)
    protected LocalDateTime createdDate;


    @CreatedBy
    protected Integer createSicil;

    @LastModifiedBy
    protected Integer modifierSicil;


    @Basic(fetch = FetchType.EAGER)
    @Column(name = "isEnabled", unique = false, nullable = false)
    protected boolean enabled;
}

The columns on entity auto generated by spring boot.

One class extends that mappedsuperclass

@Entity
@Getter
@Setter
public class ItemTypeEntity extends GenericEntity {

    private String itemType;

    @ManyToOne
    private ItemCategoryEntity itemCategory;

    @Override
    public DTOObject<?,?> toDTOObject() {
        return null;
    }
}

and

package com.manage.jpa;

import com.manage.model.DTOObject;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity {

    @Id
    @Column(name = "idValue", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    protected Long idValue;



    public abstract DTOObject<?,?> toDTOObject();
}

my base entity for all...

my DTO Object:

package com.manage.service.admin.category.dto;

import com.manage.jpa.inventory.ItemCategoryEntity;
import com.manage.model.DTOObject;
import com.manage.service.admin.category.CategoryMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
@Data
public class ItemCategoryDTO extends DTOObject<ItemCategoryEntity,String> {

    private String kategori;
    private Integer kategoriId;

    @Override
    public ItemCategoryEntity toJPA() {
        return CategoryMapper.INSTANCE.toEntity(this );
    }

    @Override
    public String getUpdateValue() {
        return null;
    }
}
package com.manage.service.admin.category;

import com.manage.jpa.inventory.ItemCategoryEntity;
import com.manage.service.admin.category.dto.ItemCategoryDTO;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.util.List;

@Mapper(componentModel = "spring")
public interface CategoryMapper {
    public CategoryMapper INSTANCE = Mappers.getMapper(CategoryMapper.class);

    @Mapping(source = "categoryName", target = "kategori")
    @Mapping(source = "idValue", target = "kategoriId")
    @Mapping(source = "idValue", target = "key")
    public ItemCategoryDTO toDto(ItemCategoryEntity categoryEntity);

    @InheritInverseConfiguration
    @Mapping(source = "key", target = "idValue")
    public ItemCategoryEntity toEntity(ItemCategoryDTO itemCategoryDTO);


    public List<ItemCategoryDTO> toAllDTOS(List<ItemCategoryEntity> authors);
}

Actual behavior

/media/xxxx/yyyy/Files/Works/DeepAssets/src/main/java/com/manage/service/admin/category/CategoryMapper.java:23: warning: Unmapped target properties: "lastModifiedDate, createdDate, createSicil, modifierSicil, enabled".
public ItemCategoryEntity toEntity(ItemCategoryDTO itemCategoryDTO);
^
1 warning

Well i know i can ignore the field by

@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)

But these fields are not ignored.
They are auto generated annotated strings.
lastModifiedDate, createdDate, createSicil etc..

They are annotated and auto generated by spring boot.
@createdby

I think your warning should check them auto generated fields at runtime or not....

Steps to reproduce the problem

package com.manage.service.admin.category;

import com.manage.jpa.inventory.ItemCategoryEntity;
import com.manage.service.admin.category.dto.ItemCategoryDTO;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.util.List;

@Mapper(componentModel = "spring")
public interface CategoryMapper {
    public CategoryMapper INSTANCE = Mappers.getMapper(CategoryMapper.class);

    @Mapping(source = "categoryName", target = "kategori")
    @Mapping(source = "idValue", target = "kategoriId")
    @Mapping(source = "idValue", target = "key")
    public ItemCategoryDTO toDto(ItemCategoryEntity categoryEntity);

    @InheritInverseConfiguration
    @Mapping(source = "key", target = "idValue")
    public ItemCategoryEntity toEntity(ItemCategoryDTO itemCategoryDTO);


    public List<ItemCategoryDTO> toAllDTOS(List<ItemCategoryEntity> authors);
}

MapStruct Version

1.5.5.Final

@eix128 eix128 added the bug label Jan 9, 2024
@thunderhook
Copy link
Contributor

thunderhook commented Jan 10, 2024

Hi @eix128

MapStruct has no knowledge how and what properties are getting generated by some other framework. This could be some magic from spring boot, but possibly due to other frameworks, even a self-written annotation processor handling custom annotations.

MapStruct is looking at properties that can be modified (set) by looking at the setters (or possibly a builder method when using the builder pattern).

In your example the GenericEntity has a @Setter annotation above the class. Therefore every field is mutable and can be modified by MapStruct (or even yourself).

@Getter
@Setter // <--
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class GenericEntity extends BaseEntity {

    @LastModifiedDate
    @Column(columnDefinition = "timestamp default '2020-04-10 20:47:05.967394'")
    protected LocalDateTime lastModifiedDate;

    @CreatedDate
    @Column(columnDefinition = "timestamp default '2020-04-10 20:47:05.967394'", updatable = false)
    protected LocalDateTime createdDate;

    @CreatedBy
    protected Integer createSicil;

    @LastModifiedBy
    protected Integer modifierSicil;

    @Basic(fetch = FetchType.EAGER)
    @Column(name = "isEnabled", unique = false, nullable = false)
    protected boolean enabled;
}

Therefore I would suggest to remove the mutability of the fields that you don't want to be modified afterwards by removing the setter (which is generally good practice).

If this is not possible (because it is not your own code, but it seems it is) you can either ignore them at the mapping method directly:

    @Mapping(target = "lastModifiedDate", ignore =true)
    @Mapping(target = "createdDate", ignore =true)
    // ...
    @Mapping(source = "categoryName", target = "kategori")
    @Mapping(source = "idValue", target = "kategoriId")
    @Mapping(source = "idValue", target = "key")
    public ItemCategoryDTO toDto(ItemCategoryEntity categoryEntity);

Or provide a MapperConfig with a prototype mapping where those fields are ignored. You can find an example in the reference documentation, see: Example 92. Mapper configuration class with prototype methods

@thunderhook thunderhook closed this as not planned Won't fix, can't repro, duplicate, stale Jan 10, 2024
@eix128
Copy link
Author

eix128 commented Jan 11, 2024

@thunderhook
But there could be some annotation you can add to notify MapStruct like

@MapStructIgnore
@createdby

So you just check 'your own' struct.
That can be easy for you...

we just add @MapStructIgnore manually

@thunderhook
Copy link
Contributor

@eix128 Using annotations to ignore certain setters was decided against in #1152 (4 years ago). However, this may be getting more attention now, see #3222 (comment) (maybe leave a 👍 there)

With the current release, it is possible to write a custom accessor naming strategy that checks if a method is annotated with a given annotation. However, this is quite cumbersome as it needs to be built as a separate Maven dependency. See https://mapstruct.org/documentation/stable/reference/html/#_custom_accessor_naming_strategy for more information.

But specifically in your example, is there any particular reason to expose the setters of these fields? IMHO the setters should just not be there. Even if MapStruct currently supported an annotation to ignore the setter, would you rather use it and provide more code/annotations and bloat your models?

To be clear, I'm not trying to badmouth the code, I'm just trying to figure out if there's a meaningful use case behind it.

@filiphr filiphr removed the bug label Jan 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants