Mapstruct Bug with nested mapper and unmappedSourcePolicy - java

I want to map a EmployeeDto to a EmployeeValue. Consider the following classes:
public class EmployeeDto {
private String telephoneNumber;
private Integer companyId;
public String getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public Integer getCompanyId() {
return companyId;
}
public void setCompanyId(Integer companyId) {
this.companyId = companyId;
}
}
public class EmployeeValue {
private String telephoneNumber;
private Company company;
public String getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
public class Company {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
I am trying to map those classes using a mapstruct-mapper:
#Mapper(componentModel = "cdi")
public interface EmployeeDto2EmployeeValueMapper {
#Mapping(source ="companyId", target = "company.id")
EmployeeValue map(EmployeeDto dto);
}
This is working just perfectly fine, as this test runs green:
class EmployeeDto2EmployeeValueMapperTest {
private static final String TELEPHONE_NUMBER = "telephoneNumber";
private static final int COMPANY_ID = 1;
private EmployeeDto2EmployeeValueMapper classUnderTest = Mappers.getMapper(EmployeeDto2EmployeeValueMapper.class);
#Test
void map() {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setTelephoneNumber(TELEPHONE_NUMBER);
employeeDto.setCompanyId(COMPANY_ID);
EmployeeValue outcome = classUnderTest.map(employeeDto);
assertThat(outcome.getTelephoneNumber(), is(TELEPHONE_NUMBER));
assertThat(outcome.getCompany().getId(), is(COMPANY_ID));
}
}
Now, if I add unmappedSourcePolicy = ReportingPolicy.ERROR to the mapper, that is
#Mapper(componentModel = "cdi", unmappedSourcePolicy = ReportingPolicy.ERROR)
public interface EmployeeDto2EmployeeValueMapper {
#Mapping(source ="companyId", target = "company.id")
EmployeeValue map(EmployeeDto dto);
}
the build fails with the following error message:
Unmapped source properties: "telephoneNumber".
To me this seems like a bug, because those field actually got mapped (as my test proved before).
Do you have any ideas on this?

With respect to the mapstruct issue tracker the following entry seems to cover your question.
Issue: unmappedSourcePolicy set to ERROR leads to a nested Bean issue #1881
https://github.com/mapstruct/mapstruct/issues/1881
According to issue comments a fix will be included in the upcoming release 1.4.0. However, I could not discover when this release will happen.

Related

Sending JSON data over post man giving the Failed to evaluate Jackson deserialization for type in Spring Boot Jpa project

Course.java
package com.example.jpa_training.JPAD.model;
#Entity
#Table(name = "COURSE")
public class Course implements Serializable{
public Course() {}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String description;
#ManyToOne(targetEntity=Department.class)
#JsonIgnore
private Department department;
#ManyToMany(mappedBy="courses", targetEntity=Student.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
private Set<Student> students = new HashSet<Student>();
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name="professor_id")
#JsonManagedReference
private Professor professor;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
public Set<Student> getStudents() {
return students;
}
public void addStudent(Student student) {
this.students.add(student);
}
public void removeStudent(Student student) {
this.students.remove(student);
}
#OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
private Set<Review> reviews;
}
Review.java
#Entity
public class Review implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long reviewId;
#ManyToOne
private Course course;
private String reviewDescription;
private double courseRating;
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public String getReviewDescription() {
return reviewDescription;
}
public void setReviewDescription(String reviewDescription) {
this.reviewDescription = reviewDescription;
}
public double getCourseRating() {
return courseRating;
}
public void setCourseRating(double courseRating) {
this.courseRating = courseRating;
}
}
Postman Input
{
"course": {
"id": 4,
"name": "Data Analysis",
"description": "Just take it",
"professor": {
"name": "Kapil Dev",
"qualification": "M.Tech",
"department": {
"deptId": 1,
"deptName": "Big Data",
"buildingName": "DS-04"
}
}
},
"reviewDescription": "Good course, nice teaching",
"courseRating": 0.0
}
Error Log
Failed to evaluate Jackson deserialization for type [[simple type, class com.example.jpa_training.JPAD.model.Review]]: com.fasterxml.jackson.databind.JsonMappingException:
2020-12-30 11:45:00.869 WARN 11152 --- [nio-8080-exec-2] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.example.jpa_training.JPAD.model.Review]]: com.fasterxml.jackson.databind.JsonMappingException:
2020-12-30 11:45:00.869 WARN 11152 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type
'application/json;charset=UTF-8' not supported]
Tried solutions
Using #JsonBackReference and #JsonManagedReference
Using #JsonIdentityInfo and #JsonIgnore
but the error is the same
I can save and retrieve the data from Java but when I send data over postman or using curl command I get the above error, I tried many ways but couldn't fix it
I wouldn't suggest exposing entities directly to your controller. Entities should only contain JPA annotations in your case. You can expose a DTO (Data Transfer Object) to your controller and then map the DTO to the corresponding entity.
ReviewDto
public class ReviewDto {
private String reviewDescription;
private double courseRating;
private CourseDto course;
// getters, setters, etc
}
CourseDto
public class CourseDto {
private Long id;
private String name;
private String description;
// professorDto, getters, setters, etc
}
An example demonstrating how your controller class will be
#RestController
public class DemoController {
private final ReviewDtoMapper reviewDtoMapper;
private final ReviewService reviewService;
public DemoController(ReviewDtoMapper reviewDtoMapper,
ReviewService reviewService) {
this.reviewDtoMapper = reviewDtoMapper;
this.reviewService = reviewService;
}
#PostMapping(value = "demo")
public ResponseEntity<String> postReview(#RequestBody ReviewDto reviewDto) {
final Review review = reviewDtoMapper.mapFrom(reviewDto);
reviewService.save(review);
return ResponseEntity.ok("");
}
}
The class to map from reviewDto to review entity and the opposite.
#Component
public class ReviewDtoMapper {
public ReviewDto mapTo(final Review entity) {
ReviewDto reviewDto = new ReviewDto();
reviewDto.setReviewDescription(entity.getReviewDescription());
// set all the properties you want
return reviewDto;
}
public Review mapFrom(ReviewDto dto) {
Review review = new Review();
review.setReviewDescription(dto.getReviewDescription());
// set all the properties you want
return review;
}
}
Of course, you have to make adjustments according to your needs.
If you like this way of doing things I would suggest you check
MapStruct, it will automatically make the mappers for you.

Orika - Map object to null if all fields of object are null

I got 2 classes A and B which both got a company. The company of A has got a little more information than the company of B (Company has an id while CNCompany doesn´t). I want to map all fields using orika. If all fields an object are null, I want the object to be null!
I tried to express this with an unit test. What has to be done to get this running?
public class A {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
public class B {
private CNCompany company;
public CNCompany getCompany() {
return company;
}
public void setCompany(CNCompany company) {
this.company = company;
}
}
public class Company {
private Id id;
private AccountId accountId;
public Id getId() {
return id;
}
public void setId(Id id) {
this.id = id;
}
public AccountId getAccountId() {
return accountId;
}
public void setAccountId(AccountId accountId) {
this.accountId = accountId;
}
}
public class CNCompany {
private AccountId accountId
public AccountId getAccountId() {
return accountId;
}
public void setAccountId(AccountId accountId) {
this.accountId = accountId;
}
}
public class MyMapper extends ConfigurableMapper {
#Override
protected void configure(MapperFactory factory) {
factory.classMap(A.class, B.class) //
.mapNulls(false).mapNullsInReverse(false) //
.byDefault() //
.register();
}
}
#Test
public void testMap() throws Exception {
A a = new A();
Company company = new Company();
Id id = new Id();
id.setValue("1");
company.setId(id);
a.setCompany(company);
MyMapper myMapper = new MyMapper();
B outcome = myMapper.map(a, B.class);
assertThat(outcome.getCompany(), is(nullValue()));
}
If I understand correctly you want getCompany to return null if the Company object contains only null values.
In Orika you can control conversion with a custom converter. For your example that might look something like:
public class CompanyConverter extends CustomConverter<Company, CNCompany> {
public CNCompany convert(Company source, Type<? extends CNCompany> destinationType) {
if (isNothingButNulls(source)) {
return null;
}
final CNCompany newCompany = new CNCompany();
// do your thing
return newCompany;
}
}
I've never written a CustomConverter that can return null so I'm not 100% sure this will work but it should. Note that the converter will still need to be registered. The documentation I linked shows how to register depending on what level you want the converter at.

Hibernate mapping exception - Could not determine type for:

Im trying to configure my entities but hibernate throws the following exception:
org.hibernate.MappingException: Could not determine type for: com.sd.entity.SDUserProductAcess, at table: SDUser, for columns: [org.hibernate.mapping.Column(productAccess)]
[PersistEngine] Failed to initialize persistence engine!java.lang.NullPointerException
These are my Entities:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class SDObject
{
#Id
#GeneratedValue
private long sdId;
private String sdType;
public long getSdId()
{
return sdId;
}
public void setSdId(long sdId)
{
this.sdId = sdId;
}
public String getSdType()
{
return sdType;
}
public void setSdType(String sdType)
{
this.sdType = sdType;
}
}
The next one:
#Entity
public class SDUser extends SDObject
{
#Column(unique = true)
private String code;
private String password;
private SDUserProductAcess productAccess;
public String getCode()
{
return code;
}
public void setCode(String code)
{
this.code = code;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public SDUserProductAcess getProductAccess()
{
return productAccess;
}
public void setProductAccess(SDUserProductAcess productAccess)
{
this.productAccess = productAccess;
}
The last one:
#Entity
public class SDUserProductAcess extends SDObject
{
private boolean adm;
public boolean isAdm()
{
return adm;
}
public void setAdm(boolean adm)
{
this.adm = adm;
}
}
Hibernate can't determine the type for column productAccess, located in SDUser entity.
I'm new to Hibernate and I can't figure out what is happening.
Should I provide some kind of ID?
Thanks!!
In SDUser you need to add the association info on the SDUserAccess:
#ManyToOne
#JoinColumn(name = "sdId")
private SDUserProductAcess productAccess;

serializing object using gson and getting LazyInitializationException

This is my Controller method, i am trying to read my database by providing zip, cityname and province name.
#RequestMapping(value = "/retrieve", method = RequestMethod.GET)
public #ResponseBody String retrieveObjectThroughAjax(ModelMap model){
//Calling Service Method to read data according to zip,cityName and province provide
PropertyItems propertyItems=getPropertyTypeandAddressService.readAddressFromZip("H2H-
2N3","Montreal","Quebec");
Gson gson = new Gson();
String json = null;
try{
json = gson.toJson(propertyItems); // serializing object
}catch(Exception e){
logger.error(Constants.METHOD_INSIDE_MESSAGE +"getAuthors",e);
}
logger.debug(json);
return json;
}
}
Service Method
#Service
public class GetPropertyTypeandAddressServiceImpl implements GetPropertyTypeandAddressService{
#Autowired
private GetPropertyTypeandAddressDAO getPropertyTypeandAddressDAO;
#Transactional
public PropertyItems readAddressFromZip(String zipCode,String cityName,String provinceName){
PropertyItems propertyItems=getPropertyTypeandAddressDAO.getAddressFromZip(zipCode, cityName, provinceName);
Hibernate.initialize(propertyItems);
return propertyItems;
}
}
DAO Method
#Repository
public class GetPropertyTypeandAddressDAOimp implements GetPropertyTypeandAddressDAO{
#Autowired
private SessionFactory sessionFactory;
#Override
public PropertyItems getAddressFromZip(String zipCode,String cityName,String provinceName) {
PropertyItems propertyitems = new PropertyItems();
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PropertyItems.class,"propertyItemsClass");
if(zipCode != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.zip",zipCode));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
else if(cityName != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.city","city"));
criteria.add(Restrictions.eq("city.cityname",cityName));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
else if(provinceName != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.city","city"));
criteria.add(Restrictions.eq("city.provinces","provinces"));
criteria.add(Restrictions.eq("provinces.provinceName",provinceName));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
return propertyitems;
}
}
Console Error
09:53:56,988 ERROR HelloController:567 - Inside Method: getAuthors org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.agilemaple.common.entity.Property.propertyType, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375)
As requested my Property Items Look like this
Entity:
Propert Items
#Entity
#Table(name="web_property_item")
public class PropertyItems {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name="property_type_id")
private PropertyType propertyType;
#OneToOne(mappedBy="propertyItems",cascade=CascadeType.ALL)
private Address address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PropertyType getPropertyType() {
return propertyType;
}
public void setPropertyType(PropertyType propertyType) {
this.propertyType = propertyType;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
Entity : Property Type
#Entity
#Table(name="web_property_type")
public class PropertyType {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name="property_id")
private Property property;
#OneToMany(fetch = FetchType.LAZY,mappedBy="propertyType", cascade=CascadeType.ALL)
private Set<PropertyItems> propertyItems;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Property getProperty() {
return property;
}
public void setProperty(Property property) {
this.property = property;
}
public Set<PropertyItems> getPropertyItems() {
return propertyItems;
}
public void setPropertyItems(Set<PropertyItems> propertyItems) {
this.propertyItems = propertyItems;
}
}
The problem in hibernate. Your field Set of properties has Lazy fetch method, it means that it will try to get when you call method get of this set. When u calling tojson methods, gson calls all get methods of object but in this moment hibernate session is close and hibernate can't open it in controller. I've faced with the same problem but directly on JSP. In a three weeks i resolved it by one more property for hibernate ( in your case) and I write code to opening session in view interceptor. I'm underground just right now, so I can't show property, but in a hour I will edit this answer and add property.
Added:
I remembered ! property is: hibernate.enable_lazy_load_no_trans = true
If it won't help, I will add code of opensessioninviewinterceptor.
#Override
public void addInterceptors(InterceptorRegistry registry) {
OpenSessionInViewInterceptor sessionInViewInterceptor = new OpenSessionInViewInterceptor();
sessionInViewInterceptor.setSessionFactory(sessionFactory());
}

Jackson exception during serialization

I've faced the problem, which is a little bit close to this issue, but when I've done all the steps, i still have such an exception:
org.codehaus.jackson.map.JsonMappingException: No serializer found for
class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and
no properties discovered to create BeanSerializer (to avoid exception,
disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through
reference chain:
java.util.ArrayList[0]->com.myPackage.SomeEntity["mainEntity"]->com.myPackage.MainEntity["subentity1"]->com.myPackage.Subentity1_$$_javassist_8["handler"])
here is code with my entities:
#JsonAutoDetect
public class MainEntity {
private Subentity1 subentity1;
private Subentity2 subentity2;
#JsonProperty
public Subentity1 getSubentity1() {
return subentity1;
}
public void setSubentity1(Subentity1 subentity1) {
this.subentity1 = subentity1;
}
#JsonProperty
public Subentity2 getSubentity2() {
return subentity2;
}
public void setSubentity2(Subentity2 subentity2) {
this.subentity2 = subentity2;
}
}
#Entity
#Table(name = "subentity1")
#JsonAutoDetect
public class Subentity1 {
#Id
#Column(name = "subentity1_id")
#GeneratedValue
private Long id;
#Column(name = "name", length = 100)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "subentity1")
private List<Subentity2> subentities2;
#JsonProperty
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonProperty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//here I didin't add #JsonProperty, cause it leads to cycling during serialization
public List<Subentity2> getSubentity2s() {
return subentity2s;
}
public void setSubentity2s(List<Subentity2> subentity2s) {
this.subentity2s = subentity2s;
}
}
#Entity
#Table(name = "subentity2")
#JsonAutoDetect
public class Subentity2 {
#Id
#Column(name = "subentity2_id")
#GeneratedValue
private Long id;
#Column(name = "name", length = 50)
private String name;
#ManyToOne
#JoinColumn(name = "subentity1_id")
private Subentity1 subentity1;
#JsonProperty
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonProperty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonProperty
public Subentity1 getSubentity1() {
return subentity1;
}
public void setSubentity1(Subentity1 subentity1) {
this.subentity1 = subentity1;
}
here is code of my method for transformation:
private String toJSON(Object model) {
ObjectMapper mapper = new ObjectMapper();
String result = "";
try {
result = mapper.writeValueAsString(model);
} catch (JsonGenerationException e) {
LOG.error(e.getMessage(), e);
} catch (JsonMappingException e) {
LOG.error(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return result;
}
I'll very grateful for any help, pieces of advice or code :)
UPD
alsp, I forgot to add piece of code from my controller:
String result = "";
List<SomeEntity> entities = someEntityService.getAll();
Hibernate.initialize(entities);
for (SomeEntity someEntity : entities) {
Hibernate.initialize(someEntity.mainEntity());
Hibernate.initialize(someEntity.mainEntity().subentity1());
Hibernate.initialize(someEntity.mainEntity().subentity2());
}
result = this.toJSON(entities);
I can't ignore any fields, cause I need them
Basically some of your fields are wrapped into lazy hibernate proxies.
Call Hibernate.initialize(model) before serializing your object, it will load your lazy collections and references.
But I would not mix database and view models, this is a bad practice. Create set of classes for your restful model and convert database entities to them before serialization.
I created a Bean with plane fields(String, Boolean, Double, etc.), which are in my classes and made method for transformation
If you are using lazy loading add this
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})

Categories

Resources