I have a basic Spring boot app and I am trying to map a list of entities to list of DTOs using Mapstruct (version 1.3.0.Final).
Source:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.sql.Timestamp;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "source")
#Entity(name = "Source")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Source implements Serializable {
private static final long serialVersionUID = 964150155782995534L;
#Id
#JsonIgnore
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SourceSeq")
#SequenceGenerator(sequenceName = "source_id_seq", allocationSize = 1, name = "SourceSeq")
private long id;
#NotNull
#Size(min = 36, max = 36)
#Column(name = "uuid", nullable = false, length = 36)
private String uuid;
#Column(name = "user_id")
private Long userId;
#Column(name = "username")
private String username;
#Column(name = "user_org_id")
private Long userOrgId;
#Column(name = "user_org_name")
private String userOrgName;
#Column(name = "account_number")
private Integer accountNumber;
#Column(name = "account_name")
private String accountName;
#Column(name = "billing_delay")
private Integer billingDelay;
#Column(name = "default_billing_delay")
private Integer defaultBillingDelay;
#Column(name = "billing_enabled")
private Boolean billingEnabled = true;
#JsonIgnore
#CreationTimestamp
#Column(name = "created_date")
private Timestamp createdDate;
#JsonIgnore
#UpdateTimestamp
#Column(name = "updated_date")
private Timestamp updatedDate;
}
Target:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Target implements Serializable {
private static final long serialVersionUID = 8939532280496355293L;
#ApiModelProperty(hidden = true)
private String uuid;
#ApiModelProperty(value = "user ID", example = "123456", dataType = "int64", position = 1)
private Long userId;
#ApiModelProperty(value = "username", example = "myUser", position = 2)
private String username;
#ApiModelProperty(hidden = true)
private String firstName;
#ApiModelProperty(hidden = true)
private String lastName;
#ApiModelProperty(value = "user organization ID", example = "71836", dataType = "int64", position = 3)
private Long userOrgId;
#ApiModelProperty(value = "user organization name", example = "Org Inc", position = 4)
private String userOrgName;
#ApiModelProperty(value = "account number", example = "987654", position = 5)
private Integer accountNumber;
#ApiModelProperty(value = "account name", example = "My Mapping Acc", position = 6)
private String accountName;
#ApiModelProperty(value = "billing delay (in days)", example = "60", position = 7)
private Integer billingDelay;
#ApiModelProperty(value = "default billing delay (in days)", example = "30", position = 8)
private Integer defaultBillingDelay;
#ApiModelProperty(value = "is billing enabled?", example = "true", position = 9)
private Boolean billingEnabled = true;
#ApiModelProperty(hidden = true)
private Date createdDate;
}
Mapper:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
#Mapper
public interface MyMapper {
MyMapper MAPPER = Mappers.getMapper(MyMapper.class);
// Target toTarget(Source source);
// I have tried using this as well but my target mapped list only contains billingEnabled = true for every object in the response list. MapperImpl class also included below. Without toTarget method get a compilation error (also included below)
// Response:
/*[
{
"billingEnabled": true
},
{
"billingEnabled": true
},
{
"billingEnabled": true
}
]*/
List<Target> toTargets(List<Source> sources);
}
MapperImpl:
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-09-16T00:06:14-0700",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
public class MyMapperImpl implements MyMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
return target;
}
#Override
public List<Target> toTargets(List<Source> sources) {
if ( sources == null ) {
return null;
}
List<Target> list = new ArrayList<Target>( sources.size() );
for ( Source source : sources ) {
list.add( toTarget( source ) );
}
return list;
}
}
Error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.5.1:compile (default-compile) on project my-project: Compilation failure
[ERROR] /Users/user8812/workspace/my-project-/src/main/java/MyMapper.java:[17,23] Can't map Collection element "Source source" to "Target target". Consider to declare/implement a mapping method: "Target map(Source value)".
I'm looking to have Target list with the same field names mapped without another individual toTarget method as that has worked for me in another project with an older Mapstruct version.
The exception thrown by MapStruct during compilation is telling you how to fix it:
Can't map Collection element "Source source" to "Target target". Consider to declare/implement a mapping method: "Target map(Source value)".
You could even place this method signature inside the same interface you've shown us.
Edit
It seems like the default global configuration for MapStruct has been changed in the application. Try applying this annotation to you "source to target" method inside the interface:
#BeanMapping(ignoreByDefault = false)
Or you can explicitly map their fields with the #Mapping annotation.
Since the field names and types in Target type and in Source type are the same, wouldn't it be more beneficial and easier to use BeanUtils.copyProperties(source, target)? It is very straightforward and allows stating of ignored fields.
I think declaration should have annotation in MyMapper interface.
#Mapping(source = "sources", target = "targets")
List<Target> toTargets(List<Source> sources);
Reference: https://mapstruct.org/
Related
package com.springboot.blog.entity;
import lombok.*;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "posts")
public class Post {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description", nullable = false)
private String description;
#Column(name = "content", nullable = false)
private String content;
#OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Comment> comments = new HashSet<>();
}
Here is the Post class
package com.springboot.blog.payload;
import lombok.Data;
import java.util.Set;
#Data
public class PostDTO {
private Long id;
private String title;
private String description;
private String content;
private Set<CommentDTO> comments;
}
Here is PostDTO class.
#Override
public PostDTO getPost(Long id) {
Post temp_post = postRepository.findById(id).
orElseThrow(() -> new ResourceNotFoundException("Post", "Id", id));
return mapToDTO(temp_post);
}
private PostDTO mapToDTO(Post post) {
return modelMapper.map(post, PostDTO.class);
}
I used mapToDTO method to convert post to postDTO.
But When I test it, the error message appeared.
{"timestamp":"2022-04-30T14:03:03.369+00:00","status":500,"error":"Internal Server Error","trace":"org.modelmapper.MappingException: ModelMapper mapping errors:\n\n1) Error mapping com.springboot.blog.entity.Post to com.springboot.blog.payload.PostDTO\n\n1 error\n\tat org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:380)\n\tat org.modelmapper.internal.
========================================================================================
** But when I replace the #Data annotation in class Post with #Getter, #Setter. It works perfectly! **
I'm trying to make a bidirectional manytomany relationship work with Hibernate and Maven, but it always either throws org.hibernate.LazyInitializationException: could not initialize proxy - no Session or a Stackoverflowerror. I already tried #JSONIdentityInfo ,which resulted in Maven automatically adding a "PK" Variable which disrupted mainly my Frontend, #JSONManagedReference and #JSONBackreference, which caused the entity that received the Backreference to be ignored completly, #JSONIgnore, same issue with the Backreference, #JsonView, which changed seemingly nothing, and #JsonSerialize with a custom serializer, which when implemented also causes problems with the frontend. I also tried wirting the Query myself direclty into the Repository but it caused the same issue. I am really at the end of my abilties and dont know what else to do.
Node
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import de.sadgmbh.spring.angular.backenddemo.model.AbstractAuditingEntity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
#Getter
#Setter
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
#Entity
#Table(name = "knotenpunkte")
//#JsonSerialize(using = CustomKnotenpunktSerializer)
public class Knotenpunkt extends AbstractAuditingEntity<Long> {
#NotNull
#Column(length = 50, unique = true, nullable = false)
#JsonView(Views.Public.class)
private int knotennr;
#NotNull
#Column(length = 50, unique = true, nullable = false)
#JsonView(Views.Public.class)
private String strasse;
#NotNull
#JsonView(Views.Public.class)
private boolean codierung;
#NotNull
#JsonView(Views.Public.class)
private boolean bake;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "knotenpunkt_linie",
joinColumns = {#JoinColumn(name = "knotenpunkt_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "linie_id", referencedColumnName = "id")})
#JsonView(Views.Internal.class)
Set<Linie> linienSet = new HashSet<>();
}
Line
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import de.sadgmbh.spring.angular.backenddemo.model.AbstractAuditingEntity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
#Getter
#Setter
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
#Entity
#Table(name = "linien")
//#JsonSerialize(using = CustomLineSerializer.class)
public class Linie extends AbstractAuditingEntity<Long> {
#NotNull
#Column(length = 50, unique = true, nullable = false)
#JsonView(Views.Public.class)
private String linienNR;
#ManyToMany( mappedBy = "linienSet")
#JsonView(Views.Internal.class)
private Set<Knotenpunkt> knotenpunktSet = new HashSet<>();
}
Generated Interfaces by Maven
export interface Knotenpunkt extends AbstractAuditingEntity<number> {
id: number;
knotennr: number;
strasse: string;
codierung: boolean;
bake: boolean;
linienSet: Linie[];
}
export interface Linie extends AbstractAuditingEntity<number> {
id: number;
linienNR: string;
knotenpunktSet: Knotenpunkt[];
}
export interface Views {
}
export interface Internal extends Public {
}
export interface Public {
}
export interface JsonSerializer<T> extends JsonFormatVisitable {
unwrappingSerializer: boolean;
delegatee: JsonSerializer<any>;
}
In the end the solution was to add #Transactional to the controller. Hope this might help someone.
I am working on a project using Springboot to create API to call all provinces in the list. So first i create an entity class
package example.parameter.entity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.List;
#Data
#Entity
#Table(name = "provinces",indexes = {
#Index(name = "PROVINCES_INDX_0", columnList = "name")
})
public class Province extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "provinces_generator")
#SequenceGenerator(name = "provinces_generator", sequenceName = "provinces_seq", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column(name = "is_deleted")
private Boolean isDeleted;
#OneToMany(mappedBy = "province", cascade=CascadeType.ALL, orphanRemoval = true)
private List<Regency> regencies;
#NotBlank
#Column(name = "name")
private String name;
}
and then I created this responseDTO
package example.parameter.api.response;
import lombok.Data;
#Data
public class ProvinceResponseDTO {
private String id;
private String name;
}
after that I create the repository
package example.parameter.repository;
import example.parameter.api.response.ProvinceResponseDTO;
import example.parameter.entity.Province;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProvinceRepository extends JpaRepository<Province,Long> {
public List<ProvinceResponseDTO> findAllByIsDeleted(Boolean isDeleted);
}
when I am trying to hit the API I am getting this error on data layer.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [example.parameter.entity.Province] to type [example.api.response.ProvinceResponseDTO]
I don't know thy this is happening, any solution to fix this issue?
Part:1
JpaRepository Query creation from method name should have the default return type of the entity object. In your case, your repository is for the Province entity. So, it should be like
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public List<Province> findAllByIsDeleted(Boolean isDeleted);
}
Reference:-
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
Part: 2
If you need to return a custom object from the query creation from a method name, then you can use dynamic projections like below:-
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public <T> List<T> findAllByIsDeleted(Boolean isDeleted, Class<T> dynamicClassType);
}
while calling this method you define like
provinceRepository.findAllByIsDeleted(true, ProvinceResponseDTO.class)
Reference:- https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
I've two entities which are named Airport and Route. Route has two field which named startPoint and endPoint. Both of them will be id value of Airport entity. I'm adding two airport entity, after that, I want to add Route by using id values of these airport records. I got an error like that
"message": "JSON parse error: Cannot construct instance of com.finartz.airlines.entity.Airport (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.finartz.airlines.entity.Airport (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)\n at [Source: (PushbackInputStream); line: 2, column: 18] (through reference chain: com.finartz.airlines.entity.Route[\"startPoint\"])"
these are my entities:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
#Data
#Builder
#Entity
#Table(name = "airport")
#NoArgsConstructor
#AllArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class Airport implements Serializable {
private static final long serialVersionUID = -3762352455412752835L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "code")
private String code;
#Column(name = "name")
private String name;
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
#Column(name = "description")
private String description;
#CreationTimestamp
#Column(name = "created_on", nullable = false, updatable = false)
private LocalDateTime createdOn;
#UpdateTimestamp
#Column(name = "updated_on")
private LocalDateTime updatedOn;
}
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
#Data
#Builder
#Entity
#Table(name = "route")
#NoArgsConstructor
#AllArgsConstructor
#EntityListeners(AuditingEntityListener.class)
#ApiModel(value = "route")
public class Route implements Serializable {
private static final long serialVersionUID = -8451228328106238822L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_start_point",referencedColumnName = "id")
private Airport startPoint;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_end_point",referencedColumnName = "id")
private Airport endPoint;
#CreationTimestamp
#Column(name = "created_on", nullable = false, updatable = false)
private LocalDateTime createdOn;
#UpdateTimestamp
#Column(name = "updated_on")
private LocalDateTime updatedOn;
}
And repository of the Route is below:
import com.finartz.airlines.entity.Route;
import com.finartz.airlines.repository.RouteRepository;
import com.finartz.airlines.util.HibernateUtil;
import lombok.AllArgsConstructor;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
#AllArgsConstructor
public class RouteRepositoryImpl implements RouteRepository {
public Long add(Route route){
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx;
Long routeId;
tx = session.beginTransaction();
routeId = (Long) session.save(route);
tx.commit();
session.close();
return routeId;
}
}
How can I add new Route by using request which provided below?
{
"startPoint":1,
"endPoint":2
}
I would use the session's entity manager to get a reference to an airport from the DB.
Construct using this code whenever you want to create a Route this way:
EntityManagerFactory emf = session.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
Airport startPoint = em .getReference(Airport.class, startPointID);
Airport endPoint = em .getReference(Airport.class, endPointID);
It should be something like this
session.beginTransaction();
session.save(route);
tx.commit();
Long id = route.getId();
session.close();
return id;
You should not use begin/close transaction. Use Spring Data JPA, it manage transaction session automatically. Your way is not best practices since you use Spring Boot (Let's see https://github.com/donhuvy/library/blob/master/src/main/java/com/example/library/controller/BookController.java#L29 very simple and easy).
When I try to map the Phone on a Phone in the Contact class it gives the following error:
Initial creation of the SessionFactory object failed. Error: org.hibernate.MappingException: Could not determine type for: com.livro.capitulo3.crudannotations.Telephone, at table: contact, for columns: [org.hibernate.mapping.Column (numPhone)]
Error closing insert operation. Message: null
java.lang.ExceptionInInitializerError
The mapping class is as follows:
//Class Contato
package com.livro.capitulo3.crudannotations;
import java.sql.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.ManyToAny;
#Entity
#Table(name = "contato")
public class ContatoAnnotations {
#Id
#GeneratedValue
#Column(name = "codigo")
private Integer codigo;
#Column(name = "nome", length = 50, nullable = true)
private String nome;
#Column(name = "telefone", length = 50, nullable = true)
private String telefone;
#Column(name = "email", length = 50, nullable = true)
private String email;
#Column(name = "dt_cad", nullable = true)
private Date dataCadastro;
#Column(name = "obs", nullable = true)
private String observacao;
//Como ficaria a annotation aqui???? Só vou persistir esta tabela
#OneToMany
private Telefone numTelefone;
...
//Getters e Setters
}
//Class Telefone:
package com.livro.capitulo3.crudannotations;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
#Entity
#Table(name = "contato")
public class Telefone {
#Id
#GeneratedValue
#Column(name = "numero")
private String numero;
#Column(name = "tipo")
private String tipo;
public String getNumero() {
return numero;
}
public void setNumero(String numero) {
this.numero = numero;
}
public String getTipo() {
return tipo;
}
public void setTipo(String tipo) {
this.tipo = tipo;
}
}
I do not know how to do this mapping. Help! Thanks!!!
Because you did not mapped it properly. There are several ways how to do it: https://en.m.wikibooks.org/wiki/Java_Persistence/OneToMany
If you use #OneToMany annotation, it should be some collection like List or Set:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contacto")
private List<Telefone> numTelefone = new ArrayList<>();
Also change the table name for Telephone entity, it must be different than contacto:
#Entity
#Table(name = "tel")
public class Telefone {...}