I have three entities namely
product
product details
stock
category
reference is given below
when I try to get the details of product it works fine when I try to save it shows below error in the console
2020-08-12 13:17:22.279 WARN 18612 --- [nio-9002-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.eMart.main.entity.Product]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (java.util.List) not compatible with managed type (com.eMart.main.entity.Product)
My question is how to add the product into the database and how did I need to optimize my entities
Input
{
"skuId": "2",
"category": {
"categoryId": 2,
"categoryName": "food"
},
"description": "cow milk",
"stock": {
"stockId": 1,
"inventoryCount": 5,
"selfCount": 5
},
"productDetails": [
{
"productDetailId": 1,
"cost": 10.0,
"currency": "inr",
"expiryDate": "2020-08-11T18:30:00.000+00:00",
"supplierCode": 1
}
]
}
controller method
#PostMapping(value = "/test")
public ResponseEntity<Product> test(#RequestBody Product product) throws Exception {
productRepositry.save(product);
return new ResponseEntity(productRepositry.findAll(),OK);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Product {
#Id
#Column(name = "SKU_ID")
String skuId=null;
#ManyToOne
#JoinColumn(name = "category_id")
#JsonManagedReference
Category category;
#Column(name = "description")
String description=null;
#JsonManagedReference
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "stock_id", referencedColumnName = "id")
Stock stock=null;
#JsonManagedReference
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
Set<ProductDetails> productDetails;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Stock {
#Id
#Column(name = "id")
Integer stockId;
#Column(name = "inventory_count")
Integer inventoryCount;
#Column(name = "self_count")
Integer selfCount;
#JsonBackReference
#OneToOne(mappedBy = "stock",cascade = CascadeType.ALL)
Product product;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Category {
#Id
#Column(name = "category_id")
Integer categoryId;
#Column(name = "category_name")
String categoryName;
#OneToMany(mappedBy = "category", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonBackReference
List<Product> product;
#Override
public String toString() {
return "Category{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", product=" + product +
'}';
}
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class ProductDetails {
#Id
#Column(name = "id")
Integer productDetailId;
#Column(name = "cost")
Double cost;
#Column(name = "currency")
String currency;
#Column(name = "expiry_date")
Date expiryDate;
Integer supplierCode;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
Product product;
}
I think you have missed adding #JoinColumn on Product in ProductDetails entity
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SKU_ID")
Product product;
Also, you can try be removing #JsonManagedReference from Category or Stock
Related
I am using SpringBoot, so lets say first, I want to make a Country, and after doing that POST with JSON how can I do other POST to create a City and adding it to the Country created?
Or I cant do it with JSON?
And idk if is a good idea having the FK pointing the name instead of the ID, in my head it works the same bc is an unique key, right?
Thanks!
Country code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "country_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Country implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<City> cities = new HashSet<>();
}
City code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "city_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class City implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "country_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_country_city"))
private Country country;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Neighborhood> neighborhoods = new HashSet<>();
}
Neiborhood code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "neighborhood_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Neighborhood implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#NonNull
private String neighborhoodType;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_city_neighborhood"))
private City city;
}
So first you need to do the POST request to create Country object:
{
"name": "USA",
"cities": []
}
Second you need to do the POST request to create the City object and put the field country with the Primary Key (PK):
{
"name": "Huston",
"country": 1,
"neighborhoods": []
}
That's pretty much it actually.
I have a database with some entities, ( in parent child relationship )I can say and when When I try to make a query to child table to get all the rows, I only get the fields which are not foreign keys. How can I include the foreign key in the response json. Can anyone help me figure this out ?
Parent Class which is working fine when I try to repository.findAll() and it works as expected.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}
Child class:
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "emp_id", nullable = false)
#JsonBackReference
private Employee employee;
}
Here is the repository class for Address Entity
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
}
When I try AddressRepository.findAll()
What I get:
[{
"id": 1,
"city": "new york",
"state": "new york"
}]
what I want to get:
"id": 1,
"city": "new york",
"state": "new york",
"emp_id": 1 //which is the foreign key referring to Employee table
}]
What I tried is I updated my Employee column in Address Entity as follow but no luck
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.EAGER)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "client_id", scope = Client.class)
#JsonIdentityReference(alwaysAsId = true)
#JoinColumn(name = "client_id", nullable = false)
#JsonBackReference
#JsonProperty("clientId")
private Employee employee;
}
You could use a JPA projection:
public class AddressDto {
private long id;
private String city;
private String state;
private long employeeId;
public AddressDto(long id, String city, String state, Employee employee) {
this.id = id;
this.city = city;
this.state = state;
this.employeeId = employee.getId();
}
// getters etc..
}
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
List<AddressDto> findAllProjectedBy();
}
Use #JsonProperty on each Foreign Key.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#JsonProperty
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#JsonProperty
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}
I'm having problem with mapping two classes with composite keys.
The first class is Product:
#Entity
#Table(name = "Products")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#SuperBuilder
public class Product {
#EmbeddedId
private ProductKey prodPK;
#Column(name = "name", length = 50, nullable = false)
private String name;
#Column(name = "description", length = 80)
private String description;
#Column(name = "totalStock", columnDefinition = "double(8,2) default 0")
private double totalStock;
#ManyToOne
#JoinColumn(name = "companyId", referencedColumnName = "id", nullable = false)
private Company company;
}
With this #EmbeddedId:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class ProductKey implements Serializable {
#Column(name = "sku", length = 50)
private String sku;
#Embedded
private LotKey lot;
}
At the same time, this embedded class has as part of its composite key another composite key "LotKey"
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class LotKey implements Serializable {
#Column(name = "lot")
private String lot;
#ManyToOne
#JoinColumn(name = "company", referencedColumnName = "id")
private Company company;
}
which belongs to the class:
#Entity
#Table(name = "Lots")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#SuperBuilder
public class Lote {
#EmbeddedId
private LotKey lotpk;
#Column(name = "stock")
private double stock;
#Column(name = "expirationDate", columnDefinition = "default current_timestamp()")
private Date expirationDate;
}
But I'm having trouble referencing to them:
#Entity
#Table(name = "quantityProduct")
public class QuantityProduct{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#ManyToOne
#JoinColumns({
#JoinColumn(
name = "sku",
referencedColumnName = "sku"),
#JoinColumn(
name = "lot")
})
private Product product;
#Column(name = "quantity", columnDefinition = "double(8,2) default 0")
private double quantity;
}
I am getting the following error
image
Thank you so much !
In QuantityProduct, set also referencedColumnName in
#JoinColumn(
name = "lot")
I have two entities
#Entity
#Table(name = "categories")
public class Category {
#Getter
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id", unique = true, nullable = false)
private long categoryId;
#Getter #Setter
#ManyToMany(mappedBy = "categories", cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
List<Product> products;
#Getter #Setter
#Column(name = "category_name", nullable = false, unique = true)
private String categoryName;
And
#Entity
#Table(name = "products")
public class Product {
#Getter
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id", unique = true, nullable = false)
private long productId;
#Getter #Setter
#Column(name = "price")
private float price;
#Getter #Setter
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "categories_product")
private List<Category> categories;
#Getter #Setter
#Column(name = "product_code", unique = true, nullable = false)
private String productCode;
#Getter #Setter
#Column(name = "product_name", nullable = false)
private String productName;
#Getter #Setter
#Column(name = "description", nullable = false)
private String description;
#Getter #Setter
#Column(name = "short_description", nullable = false)
private String shortDescription;
}
I`m using MapStruct for DTO. When I want to add new product through controller I get the following error:
org.hibernate.PropertyValueException: not-null property references a null or transient value : com.project.shop.models.Category.categoryName
As I understand, hibernate tries to create a new Category, when I want it to use an already existing one in database.
CategoryDTO:
#Getter
#Setter
public class CategoryDto {
private long categoryId;
private String categoryName;
private boolean categoryActive;
}
ProductDTO:
#Getter
#Setter
public class ProductDto {
private String productName;
private String productCode;
private float price;
private String shortDescription;
private String description;
private List<CategoryDto> categories;
}
CategoryMapper:
#Mapper(componentModel = "spring")
public interface CategoryMapper {
CategoryDto toDto(Category category);
List<CategoryDto> toDtos(List<Category> categories);
List<Category> toModels(List<CategoryDto> categoryDtos);
Category toModel(CategoryDto categoryDto);
}
ProductMapper:
#Mapper(uses = {CategoryMapper.class},
componentModel = "spring")
public interface ProductMapper {
ProductDto toDto(Product product);
List<ProductDto> toDtos(List<Product> products);
List<Product> toModels(List<ProductDto> productDtos);
Product toModel(ProductDto productDto);
}
Controller:
#PostMapping("/product")
public ResponseEntity<ProductDto> create(#RequestBody ProductDto productDto) {
productService.save(productMapper.toModel(productDto));
return ResponseEntity.status(HttpStatus.CREATED).body(productDto);
}
productService.save:
public void save(Product product) {
productRepository.save(product);
}
It is not that easy basically. My suggestion (and my implementation) is that you pass only the categoryId with your ProductDTO. And at the service, take this ID, find the respective Category via a Repository and then set the Product's Category to this entity.
Simple example:
public ProductDTO addProduct(ProductDTO newDto) {
Category category = categoryRepo.findById(newDto.getCategory().getId())
.orElseThrow(// something);
Product entity = modelMapper.map(newDto, Product.class); // This does the same thing as your mapper, You can also implement this in your project
entity.setCategory(category );
return modelMapper.map(productRepo.save(entity), ProductDTO.class); // This saves the entity and converts it to a DTO and returns it
}
If you look at the identity, nullable = false option appears to be defined option is defined.
#Entity
#Table(name = "categories")
public class Category {
....
#Getter #Setter
#Column(name = "category_name", nullable = false, unique = true)
private String categoryName;
I think it would be good to look for the categoryName column value of CategoryDto first.
I have Product and Category entities. There is ManyToMany relationship between them and Product is relationship owner, so when I call action to create/save Product(with category1 and category2 for example) two queries are called:
insert into Products...
insert into ProductCategory...
And that is expected behavior.
But, when I add/remove come categories and try to call update(fetch product from db, set new values, and call save again) then these two queries are called:
update Products...
update Categories...
MY GOAL is to update just "products" table, and update JOIN TABLE("ProductCategory").
Here are related domain classes:
#Entity
#Data
#Table(name = "products")
#EqualsAndHashCode(callSuper = false)
public class Product extends BaseEntityAudit {
#NotEmpty
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#NotEmpty
#Column(name = "code")
private String code;
#NotNull
#Column(name = "price")
private Double price;
#Column(name = "published", nullable = true)
private boolean published;
#OneToMany(mappedBy = "product")
private List<Order> orders;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ecommerce_id", nullable = false)
private ECommerce eCommerce;
#ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.REFRESH })
#JoinTable(
name = "CategoryProduct",
joinColumns = { #JoinColumn(name = "product_id") },
inverseJoinColumns = { #JoinColumn(name = "category_id") }
)
private List<Category> categories;
}
#Entity
#Data
#Table(name = "categories")
#EqualsAndHashCode(callSuper = false)
public class Category extends BaseEntityAudit {
#Column(name = "name")
private String name;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ecommerce_id", nullable = false)
private ECommerce eCommerce;
#ManyToMany(mappedBy = "categories",
cascade = { CascadeType.MERGE, CascadeType.REFRESH } )
private List<Product> products;
}
And this is service method which handles update request:
#Override
public ProductResponse update(CreateUpdateProductRequest request,
String productId) throws ProductNotFoundException {
Product product = productRepository.getOne(Long.valueOf(productId));
productTransformer.productRequestToProduct(request, product);
return productTransformer.productToProductResponse(
productRepository.save(product));
}
And CreateUpdateProductRequest to Product transformer:
public void productRequestToProduct(CreateUpdateProductRequest request,
Product product) {
ECommerce eCommerce = new ECommerce();
eCommerce.setId(Long.valueOf(request.getECommerceId()));
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setCode(request.getCode());
product.setPrice(request.getPrice());
product.setCategories(
categoriesRequestToDomain(request.getCategoryIds()
));
product.setECommerce(eCommerce);
}
What should I do to keep records in JOIN TABLE(ProductCategory) up to date using just power of spring-data-jpa?