I'm having a problem with a mapstruct mapper. When running mvn clean install (or mvn clean compile), I get these errors:
[ERROR] /mapper/EntityMapper.java:[28,7] Can't map property "java.util.List<com.socomec.tseselector.model.Source> architecture.sources
" to "java.lang.Integer architecture.sources". Consider to declare/implement a mapping method: "java.lang.Integer map(java.util.List<com.socomec.tseselector.model.Source> value)".
[ERROR] /mapper/command/TSEProjectCommandMapper.java:[21,16] Can't map property "java.lang.Integer architecture.loads" to "java.util.List<com.socomec.tseselector.model.Load> architecture.loads". Consider to declare/implement a mapping method: "java.util.List<com.socomec.tseselector.model.Load> map(java.lang.Integer value)".
The problem is that I have no idea where mapstruct is getting this "java.lang.Integer architecture.loads" from.
I don't understand where this Integer is coming from, as you can see in my code there is no Integer. Also up until now I never ran into this error while using similar mapper.
Here is my entity Architecture :
public class Architecture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String imagePath;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="tse_architectures_loads",
joinColumns = {#JoinColumn(table = "tse_architecture", name = "architecture_id")},
inverseJoinColumns = {#JoinColumn(table = "tse_load", name = "load_id")}
)
private List<Load> loads;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="tse_architectures_sources",
joinColumns = {#JoinColumn(table = "tse_architecture", name = "architecture_id")},
inverseJoinColumns = {#JoinColumn(table = "tse_source", name = "source_id")}
)
private List<Source> sources;
private String techno;
public Architecture() {
this.loads = new ArrayList<>();
this.sources = new ArrayList<>();
}
The DTO
public class ArchitectureDto {
private Long id;
private String name;
private String imagePath;
private List<LoadDto> loadDtos;
private List<SourceDto> sourceDtos;
private String techno;
public ArchitectureDto() {
this.loadDtos = new ArrayList<>();
this.sourceDtos = new ArrayList<>();
}
}
My mapper:
#Mapper(componentModel = "spring")
public interface ArchitectureMapper extends EntityMapper<ArchitectureDto, Architecture> {
#Mapping(source = "loads", target = "loadDtos")
#Mapping(source = "sources", target = "sourceDtos")
ArchitectureDto toDto(Architecture architecture);
}
Entity Mapper:
public interface EntityMapper<D, E> {
/**
* Map a DTO to an Entity
*
* #param dto the dto to map
* #return an Entity
*/
E toEntity(D dto);
/**
* Map an Entity to a DTO
*
* #param entity to map to a DTO
* #return a DTO
*/
D toDto(E entity);
/**
* Map a List of DTOs to a List of Entities
*
* #param dtoList the list to map
* #return a list of Entities
*/
List<E> toEntity(List<D> dtoList);
/**
* Map a list of Entities to a list of DTOs
*
* #param entityList the list to map
* #return a list of DTOs
*/
List<D> toDto(List<E> entityList);
}
Looking through the documentation and other questions about mapstruct here, I have not been able to find a solution so far.
I'm addind my load and source classes
Load:
public class Load {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private String unity;
private Long sourcePriority1;
private Long SourcePriority2;
private Long SourcePriority3;
private Long kvaValue;
private Long kwValue;
private Long pfValue;
private Long aValue;
private Long iccValue;
#JsonIgnore
#ManyToMany(mappedBy = "loads")
private List<TSEProject> tseProjects;
#JsonIgnore
#ManyToMany(mappedBy = "loadList")
private List<Architecture> architectures;
public Load() {
this.architectures = new ArrayList<>();
this.tseProjects = new ArrayList<>();
}
Source:
public class Source {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private String unity;
private Long quantity;
private Long kvaValue;
private Long kwValue;
private Long pfValue;
private Long aValue;
#JsonIgnore
#ManyToMany(mappedBy = "sources")
private List<TSEProject> tseProjects;
#JsonIgnore
#ManyToMany(mappedBy = "sourceList")
private List<Architecture> architectures;
public Source() {
this.architectures = new ArrayList<>();
this.tseProjects = new ArrayList<>();
}
}
and the DTOs
#Data
public class LoadDto {
private Long id;
private String name;
private String type;
private String unity;
private Long sourcePriority1;
private Long SourcePriority2;
private Long SourcePriority3;
private Long kvaValue;
private Long kwValue;
private Long pfValue;
private Long aValue;
private Long iccValue;
private List<Long> tseProjectsId;
public LoadDto() {
this.tseProjectsId = new ArrayList<>();
}
}
#Data
public class SourceDto {
private Long id;
private String name;
private String type;
private String unity;
private Long quantity;
private Long kvaValue;
private Long kwValue;
private Long pfValue;
private Long aValue;
}
I guess you need to troubleshoot a bit more.. Try to aid MapStruct and define an additional mapping method like this:
Mapper(componentModel = "spring")
public interface ArchitectureMapper extends EntityMapper<ArchitectureDto, Architecture> {
#Mapping(source = "loads", target = "loadDtos")
#Mapping(source = "sources", target = "sourceDtos")
ArchitectureDto toDto(Architecture architecture);
// first try with all properties ignored
// #Mapping( target = "id", ignore = true ) etc.
SourceDto toDto(Source source);
// first try with all properties ignored
// #Mapping( target = "id", ignore = true ) etc.
LoadDto toDto(Load load);
}
The problem is that MapStruct tries to map these objects automatically based on property names. If it somewhere discovers the same name it tries to do type conversion or to use existing mapping methods to do its job. It even tries 2 steps (so conversion and then a method), which sometimes leads to these results.
You could provide some of the mappings MapStruct tries to generate itself and then again ignore all properties like indicated in the example above. One by one remove the ignore lines and see when MapStruct starts to complain.
If its again an object graph, you could apply a similar strategy.
It's a bit tedious debugging like this, but probably deep down in your object graphs you discover the problem. You could then remove all signatures again, except the one that solved your problem, making maximum use of code generation.
Related
So, I'm trying to persist an entity in the database that has a composite key, declared using the #IdClass annotation, which one of the ID keys I have turned into an object so ensure some validation of the data.
Before, when this ID was just a String, it was working without any problems, but now that I have changed it's type, it seens that Hibernate can't determine it's type in the database.
I found a question with a problem that was almost exactly the same as the mine, here. After I added the #Column annotation to the fields in the IdClass, I feel that the Hibernate could determine the type of the field in the database, but now it fails to perform the conversion.
I already have the converter class with the #Converter annotation and implementing the AttributeConverter interface, but I think that it isn't being reached by the Spring/Hibernate.
The involved classes bellow:
The converter
#Converter
public class ChapterNumberConverter implements AttributeConverter<ChapterNumber, String> {
#Override
public String convertToDatabaseColumn(ChapterNumber attribute) {
String value = attribute.getValue();
return value;
}
#Override
public ChapterNumber convertToEntityAttribute(String dbData) {
ChapterNumber chapterNumber = new ChapterNumber(dbData);
return chapterNumber;
}
}
The composite ID class
public class ChapterID implements Serializable {
private static final long serialVersionUID = 4324952545057872260L;
#Column
private Long id;
#Column
#Convert(converter = ChapterNumberConverter.class)
private String number;
#Column
private Long publisher;
#Column
private Long manga;
public ChapterID() {
}
public ChapterID(Long id, String number, Long publisher, Long manga) {
this.id = id;
this.number = number;
this.publisher = publisher;
this.manga = manga;
}
// ... getters and setters
}
The entity class
#Entity
#Table(name = "chapter", uniqueConstraints = #UniqueConstraint(columnNames = {"number", "publisher_id", "manga_id"}))
#IdClass(ChapterID.class)
public class Chapter {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Id
#Convert(converter = ChapterNumberConverter.class)
private ChapterNumber number;
#Id
#ManyToOne
#JoinColumn(name = "publisher_id")
private Publisher publisher;
#Id
#ManyToOne
#JoinColumn(name = "manga_id")
private Manga manga;
#Column(nullable = false)
#Convert(converter = ChapterLanguageEnumConverter.class)
private ChapterLanguage language;
public Chapter() {
}
public Chapter(ChapterNumber chapterNumber, Publisher publisher, Manga manga, ChapterLanguage language) {
this.number = chapterNumber;
this.publisher = publisher;
this.manga = manga;
this.language = language;
}
public Chapter(String chapterNumber, Publisher publisher, Manga manga, ChapterLanguage language) {
this(new ChapterNumber(chapterNumber), publisher, manga, language);
}
// ... getters and setters
}
I just want to validate the number field in the entity class, so, if there is another way to do this without using a custom type, otherwise, if anyone knows what I can do to teach correctly the Hibernate how to persist this field, tell me please 😢
[Java, Spring Reactive, MongoDB]
I'm currently trying to learn Reactive programming by doing and I found a challenge.
I have db object CategoryDB which looks like this:
#NoArgsConstructor
#Getter
#Setter
#Document(collection = DBConstraints.CATEGORY_COLLECTION_NAME)
class CategoryDB {
#Id
private String id;
private String name;
private String details = "";
#Version
private Long version;
private String parentCategoryId;
private Set<String> childCategoriesIds = new HashSet<>();
}
In a service layer I want to use model object Category.
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
}
I want to create Service with method Mono<Category getById(String id). In this case I want to fetch just one level of childCategories and direct parent Category. By default repository deliver Mono findById(..) and Flux findAllById(..) which I could use, but I'm not sure what would be the best way to receive expected result. I would be grateful for either working example or directions where can I find solution for this problem.
I've spent some time to figure out solution for this problem, but as I'm learning I don't know if it's good way of solving problems.
Added some methods to Category:
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
public void addChildCategory(Category childCategory) {
childCategory.updateParentCategory(this);
this.childCategories.add(childCategory);
}
public void updateParentCategory(Category parentCategory) {
this.parentCategory = parentCategory;
}
}
Function inside service would look like this:
#Override
public Mono<Category> findById(String id) {
return categoryRepository.findById(id).flatMap(
categoryDB -> {
Category category = CategoryDBMapper.INSTANCE.toDomain(categoryDB);
Mono<CategoryDB> parentCategoryMono;
if(!categoryDB.getParentCategoryId().isBlank()){
parentCategoryMono = categoryRepository.findById(categoryDB.getParentCategoryId());
}
else {
parentCategoryMono = Mono.empty();
}
Mono<List<CategoryDB>> childCategoriesMono = categoryRepository.findAllById(categoryDB.getChildCategoriesIds()).collectList();
return Mono.zip(parentCategoryMono, childCategoriesMono, (parentCategoryDB, childCategoriesDB) -> {
Category parentCategory = CategoryDBMapper.INSTANCE.toDomain(parentCategoryDB);
category.updateParentCategory(parentCategory);
childCategoriesDB.forEach(childCategoryDB -> {
Category childCategory = CategoryDBMapper.INSTANCE.toDomain(childCategoryDB);
category.addChildCategory(childCategory);
});
return category;
});
}
);
}
Where mapper is used for just basic properties:
#Mapper
interface CategoryDBMapper {
CategoryDBMapper INSTANCE = Mappers.getMapper(CategoryDBMapper.class);
#Mappings({
#Mapping(target = "parentCategoryId", source = "parentCategory.id"),
#Mapping(target = "childCategoriesIds", ignore = true)
})
CategoryDB toDb(Category category);
#Mappings({
#Mapping(target = "parentCategory", ignore = true),
#Mapping(target = "childCategories", ignore = true)
})
Category toDomain(CategoryDB categoryDB);
}
As I said I don't know if it's correct way of solving the problem, but it seem to work. I would be grateful for review and directions.
I am trying to make a database with these models
Report model
#DatabaseTable(tableName = "report")
public class Report {
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(columnName = "client_name")
private String client;
#ForeignCollectionField(maxEagerLevel = 2, eager = true)
private Collection<Product> productList;
Product model
#DatabaseTable(tableName = "product")
public abstract class Product implements Comparable<Product> {
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(foreign = true)
private Report report;
#DatabaseField(columnName = "instance")
private String instance;
#DatabaseField(columnName = "product_type")
private String productType;
So I am able to successfully insert in the tables,
but when trying out:
List<Report> reports = reportDao.queryBuilder().where().eq("client_name", clientName).query();
I get an exception
Could not create object for class model.product.Product
So it makes sense, because Product is abstract, but is there a way using the "Instance" attribute from Product interface which indicates what kind of implementation of product it is, to create the appropriate object while running the query?
Thank you
I've searched a lot in this forum and other websites, but I'm still stuck with my problem.
I'm actually using modelmapper to convert an entity to a DTO.
Here is the Entity :
#Entity
public class Candidate implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column (name = "name")
private String lastname;
#Column (name = "firstname")
private String firstname;
#Column (name = "phone")
private String phoneNumber;
#Column (name = "mail")
private String email;
#Column (name = "title")
private int title;
#OneToMany (mappedBy = "candidateId")
private Collection<Candidature> Interviews;
Here is Candidature Entity (that you find in the first Entity's collection):
public class Candidature implements Serializable {
#Id
#NotBlank
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn (name = "candidat_id")
private Candidate candidateId;
#Column(name = "interview")
#Temporal (TemporalType.DATE)
private Date dateInterview;
#Column(name ="status")
private String status;
And here is the DTO :
public class CandidateDTO {
private Long id;
private String lastname;
private String firstname;
private String phoneNumber;
private String email;
private String title;
private String dateLastInterview;
As you can see, there are some differences.
The problem I face is that the last attribute of DTO (dateLastInterview) comes from the Collection<Candidature> and more precisely it must be the last dateInterview converted into String.
Convert a Date into String is not a problem. Getting the last item of a Collection neither.
But I can't make it work with modelMapper.
Here is a sample code I tried :
modelMapper = new ModelMapper();
Converter<Candidate, CandidateDTO> converter = new Converter<Candidate, CandidateDTO>()
{
#Override
public CandidateDTO convert(MappingContext<Candidate, CandidateDTO> mappingContext) {
Candidate candidate = mappingContext.getSource();
CandidateDTO cdto = new CandidateDTO();
List<Candidature> list = (List) candidate.getInterviews();
Date date = list.get(list.size()-1).getDateInterview();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String dateInterviewConverted = df.format(date);
mappingContext.getDestination().setTitle(mappingContext.getSource().getTitle());
mappingContext.getDestination().setDateLastInterview(dateInterviewConverted);
return cdto;
}
};
modelMapper.createTypeMap(Candidate.class, CandidateDTO.class).setConverter(converter);
(and I tried, instead of the last line above : modelMapper.addConverter(converter); but same result)
But it doesn't work, I get all attributes at null.
I previously succeded using
map().setTitle(source.getTitle());
map().setDateLastInterview(dateInterviewConverted);
And then converting the Date to String in my DTO "set" method, but it seems that it shouldn't be here, but into the ModelMapper class or the class that is using it.
Do you have an idea ? I'm new with modelMapper, and I keep browsing google and I can't find (or maybe understand ?) any response that might help me.
Thanks
Ok I think I succeded.
Using the converter was the right thing, but I wasn't using it correctly. For the converter, the two objets that you put inside <> are the ones of the attributes concerned by the converter.
For example, for the first converter, I wanted to parameter the conversion of the Collection (coming from an object Candidate) to become a String (to match the attribute of the DTO).
So then you only have to create a PropertyMap with the Class and ClassDTO, and in the configure() method you only mention the attributes that will use special parameters (the other ones are correct since they respect the standard mapping).
Converter<Collection<Candidature>, String> convertLastDateToString = new Converter<Collection<Candidature>, String>() {
public String convert(MappingContext<Collection<Candidature>, String> context) {
List<Candidature> candidatureList = (List)context.getSource();
String dateInterviewConverted = "";
if (candidatureList.size() > 0) {
Date lastInterview = candidatureList.get(0).getDateInterview();
for (int i = 0; i < candidatureList.size(); i++) {
if (candidatureList.get(i).getDateInterview().after(lastInterview)) {
lastInterview = candidatureList.get(i).getDateInterview();
}
}
// converts the Date to String
DateFormat df = new SimpleDateFormat(DATE_FORMAT);
dateInterviewConverted = df.format(lastInterview);
}
return dateInterviewConverted;
}
};
// allows custom conversion for Title attribute
// the source (Candidate) has a title attribute in int type
// the destination (CandidateDTO) has a title attributes in String type
Converter<Integer, String> convertTitleToString = new Converter<Integer, String>(){
public String convert(MappingContext<Integer, String> context){
return Title.values()[context.getSource()].toString();
}
};
// define explicit mappings between source and destination properties
// does only concernes the attributes that will need custom mapping
PropertyMap<Candidate, CandidateDTO> candidateMapping = new PropertyMap<Candidate, CandidateDTO>()
{
protected void configure()
{
// to map these two attributes, they will use the corresponding converters
using(convertTitleToString).map(source.getTitle()).setTitle(null);
using(convertLastDateToString).map(source.getCandidatures()).setDateLastInterview(null);
}
};
// add the mapping settings to the ModelMapper
modelMapper.addMappings(candidateMapping);
I'm using spring data and I made this query:
#Query("SELECT DISTINCT u.pk.fleet.fleetName FROM FleetHasUser u WHERE u.pk.user.username = ?1")
List<FleetName> allFleetNameForUser(String username);
The aim of this query is find all FleetName for a specific user.
This is the part of database schema interested in:
The FleetHasUser class:
#Entity
#Table(name = "fleet_has_user", catalog = "dart")
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id_username")),
#AssociationOverride(name = "pk.fleet",
joinColumns = #JoinColumn(name = "id_fleet")) })
public class FleetHasUser implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private FleetHasUserKeys pk = new FleetHasUserKeys();
#EmbeddedId
public FleetHasUserKeys getPk() {
return pk;
}
the FleetHasUserKeys class:
#Embeddable
public class FleetHasUserKeys implements Serializable {
private static final long serialVersionUID = 1L;
protected User user;
protected Fleet fleet;
Fleet class:
#Entity
#Table(name = "fleet", catalog = "dart")
public class Fleet implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idFleet;
private Ecu ecu;
private String application;
private Cubic cubic;
private Integer power;
private String euroClass;
private String engineType;
private Traction traction;
private String transmission;
private String note;
private FleetName fleetName;
#JsonIgnore
private Set<Car> cars = new HashSet<Car>(0);
#JsonIgnore
private Set<FleetHasUser> fleetHasUsers = new HashSet<FleetHasUser>(0);
As you can see FleetName is a class. I tried without distinct and the list had duplicated elements, so before create a subquery I inserted distinct only for test and it worked. I didn't find information on google, but is it possible to use distinct on a object? How does it work(check the primary key fields)?
Maybe may I even create a Spring data query like findDistinctPkFleetFleetNameByPkUserUsername?
Thanks
As far as i know, the distinct is being applied on the query not on the mapping phase, as you can see #Query doc the annotation only executes jpql queries so a native distinct.
As you say other option to execute a distinct is
Spring data Distinct
You cannot apply "Distinct" to objects, but you can map then on Set's to avoid duplicated, but the better approach is filter properly the data on the database.