Mapping DTO inside DTO using ModelMapper - java

I have the next entities:
#Entity
#Data
#EqualsAndHashCode(callSuper = true)
public class Student extends BaseEntity {
private String identifier;
#NotNull
#Embedded
private UserInformation userInformation;
#Embeddable
#ToString
public class UserInformation {
#NotNull
#Size(min = 1, max = 40)
private String firstName;
#NotNull
#Size(min = 1, max = 40)
private String lastName;
#NotNull
private LocalDate dateOfBirth;
#OneToOne
private Address address;
#Enumerated(EnumType.STRING)
private Gender gender;
}
And I create a DTO for the Student class:
#Data
#ToString
public class CreateStudentDTO {
#NotNull
private UserInformationDTO userInformation;
}
#Data
#ToString
public class UserInformationDTO {
#NotNull
#Size(min = 1, max = 40)
private String firstName;
#NotNull
#Size(min = 1, max = 40)
private String lastName;
}
But because UserInformationDTO and UserInformation I did not find a solution to map them.
This is what I tried in the Controller:
#RestController
#RequestMapping("/student")
public class StudentController {
#Autowired
private StudentService studentService;
#Autowired
private ModelMapper modelMapper;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public CreateStudentDTO createStudent(#RequestBody CreateStudentDTO studentDTO) {
System.out.println("dto: " + studentDTO);
TypeMap<CreateStudentDTO, Student> propertyMapper = modelMapper
.createTypeMap(CreateStudentDTO.class, Student.class);
propertyMapper.addMapping(CreateStudentDTO::getUserInformation, Student::setUserInformation);
Student student = modelMapper.map(studentDTO, Student.class);
System.out.println(student);
return null;
}
}
But the field in Student after mapping will be: userInformation=UserInformation(firstName=null, lastName=null, dateOfBirth=null, address=null, gender=null)
I want to map only the firstName and lastName for first, then I can map others if this works.

Related

Spring Boot Entity to DTO ConverterNotFoundException

I have a simple API, built with Spring Boot, where I am trying to convert entity classes to the corresponding dtos.
Student Entity
#Entity(name = "students")
#AllArgsConstructor
#Getter
#Setter
public class Student extends AbstractUpdatable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private Long id;
#Column(name = "student_first_name", nullable = false)
#NotNull(message = "First name is mandatory")
private String firstName;
#Column(name = "student_last_name", nullable = false)
#NotNull(message = "Last name is mandatory")
private String lastName;
#Column(name = "student_ssn", unique = true, nullable = false)
#NotNull(message = "SSN is mandatory")
#Size(min = 10, max = 10)
private String ssn;
#Column(name = "student_age")
#Min(5)
#Max(100)
private Integer studentAge;
#Column(name = "student_email")
#Email
private String email;
#Column(name = "student_level")
private Integer studentLevel; // TODO could be enum or separate entity
#Column(name = "student_creation_date")
private Date creationDate; // TODO check spring's feature to manage creation and update dates (auditing)
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
public Student() {
this.creationDate = new java.util.Date(); // TODO this will be removed when spring's auditing is utilized
}
}
Group Entity
#Entity(name = "groups")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Group extends AbstractUpdatable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long id;
#Column(name = "group_name")
private String Name;
#OneToMany(mappedBy = "group")
#JsonIgnore
private List<Student> students;
}
Student DTO
public class StudentDto extends AbstractStudentDto implements Serializable {
private final Long id;
private final Date creationDate;
public StudentDto(String firstName, String lastName, String email, Long id, GroupDto group, Integer studentAge, Integer studentLevel,
Date creationDate) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.group = group;
this.studentAge = studentAge;
this.studentLevel = studentLevel;
this.creationDate = creationDate;
}
public Long getId() {
return id;
}
public Date getCreationDate() {
return creationDate;
}
}
Group DTO
public class GroupDto extends AbstractGroupDto{
private final Long id;
public GroupDto(Long id, String name, List<StudentDto> students) {
this.id = id;
this.name = name;
this.students = students;
}
public Long getId() {
return id;
}
}
GroupToGroupDtoConverter
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
private final ConversionService conversionService;
#Autowired
public GroupToGroupDtoConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
#Override
public GroupDto convert(Group source) {
var convertedStudents = new ArrayList<StudentDto>();
if (!CollectionUtils.isEmpty(source.getStudents())) {
source.getStudents().forEach(student ->
convertedStudents.add(conversionService
.convert(student, StudentDto.class)));
}
return new GroupDto(source.getId(), source.getName(), convertedStudents);
}
}
And a very similar StudentToStudentDtoConverter.
The issue is that when the code needs to do the conversion from any of the entities to their dtos I get
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[com.studentmanagement.model.Group] to type
[com.studentmanagement.dto.group.GroupDto]
Now if I try to remove the conversion of the students' list to a list of student dtos in the converter above, so the converter looks like this:
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
#Override
public GroupDto convert(Group source) {
return new GroupDto(source.getId(), source.getName(), new ArrayList<StudentDto>());
}
}
The conversion works with no issues (with a dummy students list of course). Am I missing something when I am adding the conversion service inside my converters?
I tried to replicate the issue and did a small working demo on this.
One thing I found while doing that, that could be relevant to your case, is that injecting a conversion service into a converter is not trivial (see e.g. this and this relevant issues).
Also, important as well, do not forget to register the converters, as shown below on the code samples, and e.g. here. From the error message you posted, seems like the service cannot find the needed converter.
For my demo, please note I removed the group field from the StudentDTO class to simplify things. I hope it helps, happy to share the full code as well in github.
I used the following converter for Group:
#Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
private ConversionService conversionService;
public GroupToGroupDtoConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
#Override
public GroupDto convert(Group group) {
List<StudentDto> studentDtoList =
group.getStudents().stream().map(a -> conversionService.convert(a, StudentDto.class)).collect(Collectors.toList());
GroupDto groupDto = new GroupDto(group.getId(), group.getName(), studentDtoList);
return groupDto;
}
}
But in order to successfully inject the conversion service and register the converters I added this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
#Lazy
ConversionService conversionService;
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new GroupToGroupDtoConverter(conversionService));
registry.addConverter(new StudentToStudentDtoConvervter());
}
}
If, for example, I comment out the first addConverter line, I get the Converter not found exception:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.conversion_entities.Group] to type [com.example.conversion_entities.GroupDto]
Once all is done, the following test passes:
#RunWith(SpringRunner.class)
#SpringBootTest
class GroupToGroupDtoConverterTest {
#Autowired
ConversionService conversionService;
#Test
void convert() {
Student studentA = new Student();
studentA.setFirstName("John");
studentA.setLastName("Doe");
Student studentB = new Student();
studentB.setFirstName("Jane");
studentB.setLastName("Doe");
List<Student> studentList = new ArrayList<>();
studentList.add(studentA);
studentList.add(studentB);
Group group = new Group(1L, "groupA", studentList);
GroupDto convertedGroupDto = conversionService.convert(group, GroupDto.class);
assertEquals("John", convertedGroupDto.getStudents().get(0).getFirstName());
assertEquals("Jane", convertedGroupDto.getStudents().get(1).getFirstName());
}
}
I think the problem is ConversionService can't convert your classes by default. Instead try to inject a class implementing ConversionService with correct convert method implementation inside.

QueryDsl Error eq(null) is not allowed. Use isNull(instead)

This is Controller Code
#PutMapping
#PreAuthorize("hasRole('USER')")
#RequestMapping("/update")
public ResponseEntity<?> userUpdate(MemberDto memberDto){
memberService.userUpdate(memberDto);
return new ResponseEntity<>("ok", HttpStatus.OK);
}
This is my Member Class code
#Entity
#Getter
#Setter
#Table(name = "member")
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
private String email;
private String password;
private String providerId;
#Enumerated(EnumType.STRING)
private MemberRole role;
#Enumerated(EnumType.STRING)
private AuthProvider authProvider;
private String imageUrl;
//추가
private String phoneNumber;
#CreationTimestamp
private LocalDateTime createTime;
#UpdateTimestamp
private LocalDateTime updateTime;
#JsonIgnore
#OneToMany(mappedBy = "member")
private List<Post> posts = new ArrayList<>();
#JsonIgnore
#OneToMany(mappedBy = "member")
private List<Save> save = new ArrayList<>();
// #JsonIgnore
// #OneToMany(mappedBy = "member")
// private List<Follow> follow = new ArrayList<>();
#JsonIgnore
#OneToMany(mappedBy = "member")
private List<Image> image = new ArrayList<>();
}
This is DTO code
#Data
#NoArgsConstructor
public class MemberDto {
private Long id;
private String email;
private String password;
private String name;
private String phoneNumber;
#QueryProjection
public MemberDto(Long id, String email, String password, String name, String phoneNumber){
this.id = id;
this.email = email;
this.password=password;
this.name = name;
this.phoneNumber = phoneNumber;
}
}
This is Service Code
#RequiredArgsConstructor
#Service
public class MemberService {
#Autowired
private MemberRepository memberRepository;
public void userUpdate(MemberDto updateValue) {
memberRepository.updateMember(updateValue);
}
}
This is Repository(impl) code
#RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
#Override
public void updateMember(MemberDto memberDto){
QMember qMember = QMember.member;
queryFactory.update(qMember)
.where(qMember.id.eq(memberDto.getId()))
.set(qMember.email, memberDto.getEmail())
.set(qMember.password, memberDto.getPassword())
.set(qMember.name, memberDto.getName())
.set(qMember.phoneNumber, memberDto.getPhoneNumber())
.execute();
}
}
And This is my frontend (react)code send as json
{id: 7, name: '111', email: '111#111.com1', password: '12313', phonenumber: '123123'}
email: "111#111.com1"
id: 7
name: "111"
password: "12313"
phonenumber: "123123"
I try to update myDB use member ID As you can see use querydsl with
.where(qMember.id.eq(memberDto.getId()))
This code in Repository class
so I expecte nativequery like
email: "111#111.com1"
id: 7
name: "111"
password: "12313"
phonenumber: "123123"
update member(tablename) set name = 111, password = 123123, ..... where id = 7
But querydsl throw Error
java.lang.IllegalArgumentException: eq(null) is not allowed. Use isNull() instead
I Think my code is something wrong but I can't find it.
And also chrome console throw error
error: "Internal Server Error"
path: "/user/update"
status: 500
timestamp: "2021-10-12T21:04:00.658+00:00"
Internal server error so I confident that my backend Code is wrong
please help me

How do I include a related object when serving JSON in Spring?

I have this basic controller method:
#GetMapping
List<Employee> all() {
return employeeRepository.findAll();
}
By default, it seems to serve the JSON representation of Employee just fine, except there's one problem: there's no associated Department in the JSON output.
I've googled and googled and googled, and I can't seem to find an answer.
Employee:
#Entity
#Getter
#Setter
public class Employee {
#Id
#GeneratedValue
private Long id;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
#Column(unique = true)
private String emailAddress;
#ManyToOne
#JsonBackReference
private Department department;
private String phoneNumber;
}
Department
#Entity
#Getter
#Setter
public class Department {
#Id #GeneratedValue private Long id;
private String name;
#OneToMany(mappedBy="department")
#JsonManagedReference
private Set<Employee> employees;
}
Your issue is probably the mix with JPA.
As you are already using Lombok (#Getter, #Setter).
You can do this:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Employee {
private Long id;
private String firstName;
private String lastName;
private String emailAddress;
private Department department;
private String phoneNumber;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Department {
...
}

Load single entity without relations jpa

I have two entities students and grades. There is a one to many relationship between them. But when I make an api call to get students, I get all grades with them. Is there a way to load only student entity ? I tried FetchType.LAZY but it did not work.
Student model:
#Entity
#Table
public class Student {
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
private Long id;
#NotBlank(message = "Name cannot be null")
private String name;
#NotBlank(message = "Lastname cannot be null")
private String lastname;
#NotNull(message = "Age cannot be null")
private int age;
#NotBlank(message = "Email cannot be null")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "student")
private Set<Grade> grades = new HashSet();
}
Grade model:
#Entity
#Table
public class Grade {
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
private Long id;
private String subject;
private double value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", nullable = false)
private Student student;
Student service :
#Service
public class StudentService {
private final IStudentRepository studentRepository;
public StudentService(IStudentRepository studentRepository){
this.studentRepository = studentRepository;
}
public List<Student> GetAll(){
return studentRepository.findAll();
}
Hibernate output:
Hibernate: select student0_.id as id1_1_, student0_.age as age2_1_, student0_.email as email3_1_, student0_.lastname as lastname4_1_, student0_.name as name5_1_ from student student0_
Hibernate: select grades0_.student_id as student_4_0_0_, grades0_.id as id1_0_0_, grades0_.id as id1_0_1_, grades0_.student_id as student_4_0_1_, grades0_.subject as subject2_0_1_, grades0_.value as value3_0_1_ from grade grades0_ where grades0_.student_id=?
To load only the student entity, you can create a separate projection like StudenDTO and use it to pass across repo-service-controller.
The relevant part of projection is here
interface StudentDTO {
String getName();
String getLastname();
Integer getAge();
String getEmail();
}
Now when you hit localhost:8080/students/create-dto-return?id=1
You won't see the next query being fired and this is triggered jackson during serialization.
Entire code is as below:
package com.example.demo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
#RestController
#RequestMapping("/students")
public class StudentsController {
private final StudentService studentService;
#Autowired
public StudentsController(StudentService studentService) {
this.studentService = studentService;
}
#GetMapping
public Iterable<Student> list() {
return studentService.list();
}
#PostMapping
public Student createEntityReturn(#RequestBody Student student) {
return studentService.save(student);
}
#GetMapping(value = "/create-dto-return")
public StudentDTO getByDto(#RequestParam("id") Integer id) {
return studentService.findStudentOnlyByIdDto(id);
}
#GetMapping(value = "/create-entity-return")
public Student getById(#RequestParam("id") Integer id) {
return studentService.findStudentById(id);
}
}
#Getter
#Setter
#ToString
#Entity
class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotBlank(message = "Name cannot be null")
private String name;
#NotBlank(message = "Lastname cannot be null")
private String lastname;
#NotNull(message = "Age cannot be null")
private int age;
#NotBlank(message = "Email cannot be null")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "student")
private Set<Grade> grades = new HashSet<>();
}
#Getter
#Setter
#Entity
#Table
class Grade {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String subject;
private double value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", nullable = false)
private Student student;
}
#Service
class StudentService {
private final StudentRepository studentRepository;
#Autowired
StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Transactional
public Student save(Student student) {
return studentRepository.save(student);
}
#Transactional(readOnly = true)
public Iterable<Student> list() {
return studentRepository.findAll();
}
#Transactional(readOnly = true)
public StudentDTO findStudentOnlyByIdDto(Integer id) {
return studentRepository.findStudentById(id);
}
#Transactional(readOnly = true)
public Student findStudentById(Integer id) {
return studentRepository.findById(id).orElseThrow(() -> new RuntimeException("Unable to find student"));
}
}
interface StudentDTO {
String getName();
String getLastname();
Integer getAge();
String getEmail();
}
#Repository
interface StudentRepository extends JpaRepository<Student, Integer> {
StudentDTO findStudentById(Integer id);
}

Spring Boot - how to validate nested enteties

So lets say I have User object like this
#Entity
public class User {
#Id
#GeneratedValue
private long id;
private String name;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address", referencedColumnName = "id")
private Address address;
}
#Entity
public class Address {
#Id
#GeneratedValue
private long id;
private String city;
private String country;
}
Now I don't want to write validation annotations in entities. What I would like to do is validate User in #RestController like this
#RestController
public class InvoiceController {
#RequestMapping(value="/users/add", method = RequestMethod.POST)
public Invoice addInvoice(#Validated #RequestBody ValidUser user) {
... do stuff
}
}
The validation annotations would be in ValidUser being like this.
public class ValidUser extends User {
#NotNull
private String name;
#Valid
private Address address;
}
public class ValidAddress extends Address{
#NotNull
private String city;
#NotNull
private String country;
}
The validation works when I remove the address field from the ValidUser but not when it is there. How can I make address validation also work?

Categories