Nested fields mapping not working in Orika Mapper - java

Find below entity classes :
#Entity
#Table(name="rooms")
public class RoomEntity {
#Column(name="mr_code", length=50, nullable=false)
private String code;
#Column(name="mr_roomtype", nullable=false, length=50)
private String type;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "mr__hotelId", nullable = false)
private HotelEntity hotel;
//getters and setters
}
#Entity
#Table(name="hotels")
public class HotelEntity{
#Column(name="mh_name", nullable=false)
private String name;
#Column(name="mh_description")
private String description;
#OneToMany(mappedBy = "hotel", fetch=FetchType.EAGER)
private Set<RoomEntity> rooms = new HashSet<>(0);
//getters and setters
}
Find below DTO class
public class RoomDTO{
private String hotelName;
private String code;
private String type;
//getters and setters
}
I written the below mapper code to copy data from RoomEntity to RoomDTO
public class BeanMapper {
private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
public static RoomDTO toRoomDTO(RoomEntity roomEntity) {
mapperFactory.classMap(RoomEntity.class, RoomDTO.class).field("hotel.name","hotelName").byDefault().register();
MapperFacade mapper = mapperFactory.getMapperFacade();
return mapper.map(roomEntity,RoomDTO.class);
}
}
The properties - code and type values are getting copied from RoomEntity to RoomDTO.
But the nested properties values(hotel.name -> hotelName) are not getting copied.
Please help to resolve this issue.

Related

get API in spring boot with one to many relation

I have two models that are having one to many relation (customers have many invoices)
so i create one - many relation on it, this is my customer class :
#Entity
#Table(name = "customer")
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "is_deleted")
private boolean isDeleted;
#OneToMany
private Set <Invoice> invoices;
}
and this is invoices class :
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
}
and then i create GET API ( get customers ) but it's nor working and return this error :
nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.invoices.model.Customer["invoices"]), path=/customer/viewList}]
and this is my api :
public List<Customer> getAllCustomers() {
List<Customer> customers = cutomerRepository.findAll();
return customers;
}
and controller :
#GetMapping("/viewList")
public ResponseEntity<List<Customer>> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers = customerService.getAllCustomers();
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers, HttpStatus.OK);
}
You have a Bidirectional relation and therefore an endless loop if json tries to deserialize the Object.
You can use #JsonIgnore to break the loop or use DTOs to return at the endpoint
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JsonIgnore
#JoinColumn(name = "customer_id")
private Customer customer;
}
DTO would look something like this (I like to use records for this but since I don't know if you use Java 17 I still use class):
Customer:
#Data
public class CustomerDTO {
private final int id;
private final long serialNumber;
private final String firstName;
private final String lastName;
private final String email;
private final String mobileNumber;
private final boolean isDeleted;
private final Set <Invoice> invoices;
public static CustomerDTO fromModel(Customer customer) {
return new CustomerDTO(
customer.getId(),
customer.getSerialNumber(),
customer.getFirstName(),
customer.getLastName(),
customer.getEmail(),
customer.getMobileNumber(),
customer.isDeleted(),
customer.getInvoices()
.stream()
.map(InvoiceDTO::fromModel)
.collect(Collectors.toSet())
);
}
}
Invoice (here you don't show the customer again):
#Data
public class InvoiceDTO {
private final int id;
private final String status;
private final Timestamp createdDate;
private final boolean isDeleted;
public static InvoiceDTO fromModel(Invoice invoice) {
return new InvoiceDTO(
invoice.getId(),
invoice.getStatus(),
invoice.getCreatedDate(),
invoice.isDeleted()
);
}
}
Controller:
#GetMapping("/viewList")
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
List<CustomerDTO> customers = new ArrayList<>();
customers = customerService.getAllCustomers()
.stream()
.map(CustomerDTO::fromModel)
.toList() //Depending on Java Version .collect(Collectors.toList());
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers., HttpStatus.OK);
}
Do not open the entity class directly to the outside world
As DTO use for example:
public class InvoiceDTO {
private int id;
private long serialNumber;
private String status;
private Timestamp createdDate;
private boolean isDeleted;
private CustomerDTO customer;
}
See it applied in my GitHub repo FurnitureStoreApplication, example DTO classes in package dto:

Deserialize inner json string property with GSON

I'm using Springboot and Gson for my backend.
I have these two classes in a many to many relation:
Order class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table( name = "orders")
public class Order {
#Id
#Expose
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Expose
#NotBlank
private String poNumber;
#Expose
#NotBlank
private String dimension;
#Expose
#NotBlank
private int initialQuantity;
#Expose
#NotBlank
private int leftQuantity;
#Expose
#NotBlank
private Date startDate;
#Expose
#NotBlank
private Date endDate;
#Expose
#SerializedName("status")
#Enumerated(EnumType.STRING)
#Column(length = 20)
private PoStatus status;
#OneToMany(mappedBy="order")
private Set<Log> logs;
#Expose
#SerializedName("products")
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "orders_products",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "sap_code"))
private Set<Product> products = new HashSet<>();
Product Class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table( name = "products")
public class Product {
#Expose
#Id
#NotBlank
private String sapCode;
#Expose
#NotBlank
private String sapCodeDescription;
#Expose
private String productData;
}
And this is the service that I use to serve the data to my rest endpoint
public String getAllOrders() {
List<Order> allOrders = orderRepository.findAll();
String allOrdersResult = gson.toJson(allOrders);
return allOrdersResult;
}
And this is the response:
[
{
"id": 1,
"poNumber": "003100059361",
"dimension": "INTBKGR",
"initialQuantity": 200000,
"leftQuantity": 200000,
"startDate": "17/08/2022 00:00",
"endDate": "17/08/2022 00:00",
"status": "READY",
"products": [
{
"sapCode": "000000000000416234",
"sapCodeDescription": "1.STUFE 15X",
"productData": "{\"pieces\": 85, \"mark\": true, \"description\": \"elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget\"}"
}
]
}
]
My aim is to deserialize/escape the productData String property.
I've tried by creating a ProductData class and using the #JsonAdapter annotation, but as far as I understood this annotation Is used when you need to give a custom behaviour to your deserialization, the JSON string in my example is very simple and I don't need any particular logic behind it.
I think you have to declare a class for that and change productData type from string to that class.
I resolved in this way, I think this is NOT a good approach and I hope that there is a more automatic approach to solve this.
Product Class
public class Product {
#Expose
#Id
#NotBlank
private String sapCode;
#Expose
#NotBlank
private String sapCodeDescription;
//THIS PROPERTY IS TO SAVE THE JSON IN THE DB
#NotBlank
#Column(columnDefinition="TEXT")
#JsonProperty
private String productDataColumn;
//THIS PROPERTY IS TO EXPOSE THE DATA TO THE API
#Transient
#Expose
private ProductData productData;
public void createProductData() {
this.productData = new Gson().fromJson(productDataColumn, ProductData.class);
}
}
ProductData Class
public class ProductData {
#Expose
public int pieces;
#Expose
public boolean marcatura;
#Expose
public String description;
}
OrderService
public String getAllOrders() {
List<Order> allOrders = orderRepository.findAll();
for(Order o : allOrders){
Product orderProduct = o.getProducts().stream().findFirst().get();
orderProduct.createProductData();
}
String allOrdersResult = gson.toJson(allOrders);
return allOrdersResult;
}

trying to write a proper Junit 5 test case for mapper

#Mapper(componentModel = "spring")
public interface ApiBeanMapper {
ApiBeanMapper mapper = Mappers.getMapper(ApiBeanMapper.class);
ApiEntity apiModelToEntity(Api user);
Api apiEntityToModel(ApiEntity user);
List<Api> apiEntityToModel(List<ApiEntity> user);
}
Trying to find a good junit5 test using Jacoco coverage report.Unable to get coverage on ApiBeanMapper mapper= Mappers.getMapper(ApiBeanMapper.class);
Api
public class Api {
private long id;
private String name;
private List<ApiResponseField> fields;
}
ApiEntity
public class ApiEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(name = "privilege_name")
private String privilegeName;
#OneToMany(mappedBy = "api", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<ApiResponseFieldEntity> apiResponseFields;
#Column(name = "field_access")
private boolean fieldAccess;
#Column(name = "field_mapping")
private boolean fieldMapping;
}

Create a product in a category hibernate - Transcient issue

I use two class as models to build a JSON :
The productCreateRequestModel:
#Getter #Setter
public class ProductCreateRequestModel {
private Long id;
private String name;
private double price;
private int qty;
private String imgPath;
private CategoryRequestCreateProductModel category;
}
My CategoryRequestCreateProductModel
#Getter #Setter
public class CategoryRequestCreateProductModel {
private Long id;
private String name;
private String categoryKeyId;
}
I created 2 entities to manage categories and products.
#Entity
#Table(name="products")
#Getter #Setter
public class ProductEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String productKeyId;
// many to one relationship with category
#ManyToOne
#JoinColumn(name = "category_id")
private CategoryEntity category;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private double price;
#Column(nullable = false)
private int qty;
private String imgPath;
}
And :
#Entity
#Table(name="categories")
#Getter #Setter
public class CategoryEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 30, nullable = false)
private String categoryKeyId;
#Column(nullable = false)
private String name;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name="parent_id", nullable=true)
private CategoryEntity parentCategory;
// allow to delete also subcategories
#OneToMany(mappedBy="parentCategory", cascade = CascadeType.ALL)
private List<CategoryEntity> subCategories;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.REMOVE)
private List<ProductEntity> products;
}
This generated this table in the database.
In my controller:
public ProductRestResponseModel createProduct(#RequestBody ProductCreateRequestModel productCreateRequestModel) throws Exception {
ProductRestResponseModel returnValue = new ProductRestResponseModel();
if(productCreateRequestModel.getName().isEmpty() || productCreateRequestModel.getPrice() <= 0)
throw new ApplicationServiceException(ErrorMessages.MISSING_REQUIRED_FIELDS.getErrorMessage());
ModelMapper modelMapper = new ModelMapper();
ProductDto productDto = modelMapper.map(productCreateRequestModel, ProductDto.class);
ProductDto createdProduct = productService.createProduct(productDto);
returnValue = modelMapper.map(createdProduct, ProductRestResponseModel.class);
return returnValue;
}
In my Service I use the DTO:
#Override
public ProductDto createProduct(ProductDto productDto) {
ProductDto returnValue = new ProductDto();
if (productRepository.findByName(productDto.getName()) != null)
throw new ApplicationServiceException("Record already in Database");
ModelMapper modelMapper = new ModelMapper();
ProductEntity productEntity = modelMapper.map(productDto, ProductEntity.class);
String productKeyId = utils.generateProductKeyId(30);
productEntity.setProductKeyId(productKeyId);
ProductEntity storedProduct = productRepository.save(productEntity);
returnValue = modelMapper.map(storedProduct, ProductDto.class);
return returnValue;
}
My issue is when I send a post request with this object :
{
"name": "Pizza",
"price": 344.0,
"qty": 15,
"imgPath": "new/pathImage",
"category": {
"categoryKeyId": "23ume70Fu6yqyGUWfQkW110P4ko3gZ",
"name": "CatName"
}
}
When i send this request I obtain an error message : org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.app.ws.io.entity.ProductEntity.category -> com.app.ws.io.entity.CategoryEntity
My problem is that the Category already exists in the database and that i just need to set the foreign key in the product table

How to return only specific field in spring

In the following code when I used projection it returns me the whole object of Group , But I want to get only roleName of class Group
how can I do this?
Projection: UserWithProfile
#Projection(name="UserWithProfile",types=User.class)
public interface UserWithProfile extends UserGetters {
UserPhoto getProfilePhoto();
}
UserGetters
public interface UserGetters{
Long getId();
String getName();
String getLogonEmail();
boolean isEmailVerified();
Group getGroup();
}
User.class
#Entity
public class User implements Serializable, UserGetters {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, targetEntity = Property.class)
private Set<Property> favouriteProperty;
#OneToOne
private AcceptanceLetter acceptanceLetter;
#Column(unique=true)
private String logonEmail;
private String name;
#JsonProperty(access = Access.WRITE_ONLY)
#Column(updatable=false)
private String password;
private boolean emailVerified;
#ManyToOne
private Group group;
#OneToOne
private Business business;
private String address;
private String postcode;
private String phoneNo;
private String passportNo;
#Column(length=1000)
private String description;
#JsonIgnore
private float meanRating;
#OneToOne(mappedBy="user",targetEntity=UserPhoto.class)
private UserPhoto profilePhoto;
#ManyToOne
private Country country;
Getter and setters... }
First I tried #RestResource(exported = false) its not worked but then I tried
#JsonIgnore it finally works :'D

Categories

Resources