I am trying to create an api for a user to have the ability to add products to a specific category such that when, getCategory by id is called it gives a result as follows:
{
"product": [
{
"productId": 1,
"productName": "TestProdut1"
},{
"productId": 2,
"productName": "TestProdut2"
}
],
"categoryId": 1,
"categoryName": "Test1",
"parentCategoryId": 123
}
Please note i am using a postgres database
This is what i have done so far but when i try to get a category there
is no response back. When using swagger the response comes back as no
content
Category Entity :
#Entity
#Table(name = "category")
public class Category {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "category_id", unique = true, nullable = false)
private Long id;
#Column(nullable = false)
private String categoryName;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy = "categories", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<Product> products = new ArrayList<Product>();
#ManyToOne
#JoinColumn(name = "parent_id")
#JsonIgnore
private Category parentId;
#OneToMany(mappedBy = "parentId")
private List<Category> subCategories = new ArrayList<>();
}
Product Entity :
#Entity
#Table(name = "products")
public class Product {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "product_id")
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "category_id")
private Category categories;
}
Category Repository :
public interface CategoryRepository extends CrudRepository<Category, Long>{
public List<Category> findByCategoryName(String categoryName);
}
Category Service :
#Service
public class CategoryService {
#Autowired
private CategoryRepository categoryRepository;
public void addCategory(Category category) {
categoryRepository.save(category);
}
public List<Category> getAllCategories() {
List<Category> categoryList = new ArrayList<>();
categoryRepository.findAll().forEach(categoryList::add);
return categoryList;
}
public List<Category> getAllCategoriesByName(String categoryName) {
List<Category> categoryList = new ArrayList<>();
categoryList = categoryRepository.findByCategoryName(categoryName);
return categoryList;
}
public Optional<Category> getCategoryById(Long categoryId) {
Optional<Category> category = categoryRepository.findById(categoryId);
return category;
}
public void deleteCategory(Long id) {
categoryRepository.deleteById(id);
}
}
Category Controller :
#RestController
#Api(value = "CategoryAPI", produces = MediaType.APPLICATION_JSON_VALUE)
#RequestMapping("/ss")
public class CategoryController {
#Autowired
private CategoryService categoryService;
#RequestMapping(method=RequestMethod.POST , value="/category/add", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Boolean> addCategory(#RequestBody Category category) {
categoryService.addCategory(category);
return new ResponseEntity<Boolean>(true, HttpStatus.CREATED);
}
#RequestMapping(method= RequestMethod.GET, value="/category/get/by/id/{categoryId}")
public void getCategoryById(#PathVariable Long categoryId) {
categoryService.getCategoryById(categoryId);
}
#RequestMapping(method= RequestMethod.GET, value="/category/get/by/name/{categoryName}")
public void getCategoryByName(#PathVariable String categoryName) {
categoryService.getAllCategoriesByName(categoryName);
}
#RequestMapping(method= RequestMethod.GET, value="/all/category")
public void getCategories() {
categoryService.getAllCategories();
}
}
It is because you are having void return type when retrieving the list of categories and that is why you are not getting any response.
#RequestMapping(method= RequestMethod.GET, value="/category/get/by/name/{categoryName}")
public ResponseEntity<List<Category>> getCategoryByName(#PathVariable String categoryName) {
return new ResponseEntity<>(categoryService.getAllCategoriesByName(categoryName),HttpStatus.OK);
}
Related
When inserting a record using post request foreign key related reference record is not linking.
#RestController
#RequestMapping("auth")
public class PatientController {
#Autowired
private PatientService patientService;
#PostMapping(value = "patient/register", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String registerPatient(#RequestBody Patient patient) {
String response = patientService.registerPatient(patient);
return "{'result':" + response + "}";
}
}
#Service
public class PatientService {
#Autowired
private PatientRepository patientRepo;
public String registerPatient(Patient patient) {
patient = patientRepo.save(patient);
}
}
#Repository
public interface PatientRepository extends CrudRepository<Patient, Integer> {
}
Entity Classes:
#Entity
#Table(name = "patient")
public class Patient implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "patient_id")
private int patientId;
#Column(name = "patient_name", length = 200)
private String patientName;
#Column(name = "problem", length = 200)
private String problem;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctor_id", nullable = false, insertable = false, updatable = false)
private Doctor doctor;
}
#Entity
#Table(name = "doctor")
public class Doctor implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "doctor_id")
private int doctorId;
#Column(name = "doctor_name", length = 200)
private String doctorName;
#Column(name = "department", length = 200)
private String department;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "doctor")
private Set<Patient> patients = new HashSet<Patient>(0);
}
Database - Doctor Table:
doctor_id doctor_name department
12345678 Dfirstname Dlastname ENT
POST Request - JSON Body
{
"patientName":"Pfirstname Plastname",
"problem:"visibility problem - difficulty in low light",
"doctor":{"doctorId":"12345678"}
}
When I am sending this request the patient table doctor_id column is not being populated with the docortId.
at first glance (as service layer is not provided) You have to remove insertable=false and updatable=false from #JoinColumn
#JoinColumn(name = "doctor_id", nullable = false, insertable = false, updatable = false)
change this to:
#JoinColumn(name = "doctor_id", nullable = false)
As this directives doesn't let jpa to insert/update the DOCTOR_ID column
Also I prefer using werappers over primitive type as #Id change int to Integer as suggested here Using wrapper Integer class or int primitive in hibernate mapping
Also it seems that you have already persisted doctor (as it has already assigned id) you should firstly select doctor to db and add patient to it with both ends:
public void assignToDoctor(Doctor doctor) {
doctor.patients.add(this);
this.doctor = doctor;
}
here is full example:
public static void main(String[] args) {
SpringApplication.run(DemostackApplication.class, args);
}
#Component
public static class AppRunner implements ApplicationRunner {
#Autowired
MainService mainService;
#Override
public void run(ApplicationArguments args) throws Exception {
Doctor doctor = new Doctor();
doctor.department = "a";
doctor.doctorName = "Covid19 Ninja";
doctor = mainService.saveDoctor(doctor);
Patient patient = new Patient();
patient.patientName = "test";
patient.problem = "test";
patient.assignToDoctor(doctor);
Patient newPatient = mainService.savePatient(patient);
}
}
#Service
public static class MainService {
#Autowired
DoctorRepo doctorRepo;
#Autowired
PatientRepo patientRepo;
#Transactional
public Doctor saveDoctor(Doctor doctor) {
return doctorRepo.save(doctor);
}
#Transactional
public Patient savePatient(Patient patient) {
return patientRepo.save(patient);
}
}
#Entity
#Table(name = "patient")
public static class Patient implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "patient_id")
private Integer patientId;
#Column(name = "patient_name", length = 200)
private String patientName;
#Column(name = "problem", length = 200)
private String problem;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctor_id", nullable = false)
private Doctor doctor;
public void assignToDoctor(Doctor doctor) {
doctor.patients.add(this);
this.doctor = doctor;
}
}
#Entity
#Table(name = "doctor")
public static class Doctor implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "doctor_id")
private Integer doctorId;
#Column(name = "doctor_name", length = 200)
private String doctorName;
#Column(name = "department", length = 200)
private String department;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "doctor")
private Set<Patient> patients = new HashSet<Patient>(0);
}
I have not used getter/setters but you should :)
EDIT
your registerPatient() logic should be something like this:
#Transactional
public String registerPatient(Patient patient) {
Integer doctorId= patinet.getDoctor().getId();
//fetch the doctor from database
Doctor doctor = doctorRepository.findById(doctorId).orElseThrow(() -> new RuntimeException("doctor not found"));
//create bidirectional reference between patient and doctor
patient.setDoctor(doctor);
doctor.getPatients().add(patient);
//save patient
patient = patientRepo.save(patient);
return "OK";
}
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
I make method (acceptUseroffermapping) in a REST-controller (UserOfferController) in which I want to delete record in the DB (UserOfferMapping table). But the problem is that record not deleted and relation also saved after I run this method.
I have also UserOfferMapping class which maps to User class. In UserOfferController I manipulate with UserOfferMapping: creating, selecting records from DB and also trying to delete records but have fail.
UserOfferController.java:
/*...*/
#POST
#RequestMapping("/acceptUserOfferMapping")
public void acceptUseroffermapping(#RequestBody Map<String,
String> body) throws ParseException {
String userId = body.get("userId");
String offerId = body.get("offerId");
Optional<User> user = userRepository.findById(Integer.parseInt(userId));
UserOfferMapping mapping = userOfferMappingRepository.getById(Integer.parseInt(userId));
user.get().getUserOfferMapping().remove(mapping);
userRepository.save(user.get());
userOfferMappingRepository.deleteById(Integer.parseInt(offerId));
}
/*...*/
User.java:
/*some imports*/
#Entity
#Table(name = "User")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
/* ...
* a lot of fields
* ...
*/
// Important section which describes all Role Project and Skill mapping
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserUserrolemapping> userrolemapings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Userprojectmapping> userprojectmappings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserOfferMapping> userOfferMapping = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#OrderBy
private Set<Userskillmapping> userskillmappings = new HashSet<>();
/* ...
* a lot of fields too
* ...
*/
/* getter and setters */
}
UserOfferMappingRepository.java:
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
public void deleteById(int id);
}
UserOfferMapping.java:
#Entity
public class UserOfferMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name = "userid")
#JsonBackReference
private User user;
#ManyToOne
#JoinColumn(name = "roleid")
private Userrole userRole;
#ManyToOne
#JoinColumn(name = "projectid")
private Userproject project;
#Column(name = "fromdate", nullable = true)
private Date fromdate;
#Column(name = "todate", nullable = true)
private Date todate;
#Column(name = "chance")
private int chance;
#Column(name = "percent")
private int percent;
public int getId() {
return id;
}
public User getUser() {
return user;
}
public Userrole getUserRole() {
return userRole;
}
public Userproject getProject() {
return project;
}
public Date getFromdate() {
return fromdate;
}
public int getChance() {
return chance;
}
public int getPercent() {
return percent;
}
public void setId(int id) {
this.id = id;
}
public void setUser(User user) {
this.user = user;
}
public void setUserRole(Userrole userRole) {
this.userRole = userRole;
}
public void setProject(Userproject project) {
this.project = project;
}
public void setFromdate(Date fromdate) {
this.fromdate = fromdate;
}
public void setChance(int chance) {
this.chance = chance;
}
public void setPercent(int percent) {
this.percent = percent;
}
public void setTodate(Date todate) {
this.todate = todate;
}
public Date getTodate() {
return todate;
}
}
Can you try to use this
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
// public void deleteById(int id);
#Modifying(clearAutomatically = true)
#Query(value = "Delete from UserOfferMapping c WHERE c.id=:id")
public void deleteById(#Param("id") int id);
}
So, you have bidirectional entity association.
Try to add mapping.setUser(null); before userRepository.save.
Persisting and deleting objects requires a transaction in JPA so that is why you have to define #Transactional annotation before the method in Repository for example `
#Transactional
public void deleteById(#Param("id") int id);
I have two entities lets call them Categories and Products. These two entities are mapped by a many to many relationship.
My problem is that i am trying to get category information from products. Trying this results in empty categories.
This is my code :
PersistenceEntity
#MappedSuperclass
public class PersistenceEntity implements Serializable {
private static final long serialVersionUID = 4056818895685613967L;
// Instance Variables
#Id
#Column(unique = true)
#GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
#JsonIgnore
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
protected Date creationDate = new Date();
...Getters and Setters omitted for brevity
}
Category
#Entity
#Table(name = "category")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Category extends PersistenceEntity{
private static final long serialVersionUID = 1L;
#Column(nullable = false)
private String categoryName;
#Column(nullable = false)
private Boolean active;
#Column(nullable = true)
private String picture;
#JsonIgnore
private MetaData metadata;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "categories")
private Set<Product> products;
...Getters and Setters omitted for brevity
}
Product
#Entity
#Table(name = "products",uniqueConstraints = { #UniqueConstraint(columnNames = "productCode")})
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product extends PersistenceEntity {
private static final long serialVersionUID = 8727166810127029053L;
#Column(name = "product_name")
private String name;
private String productImageUrl;
#JsonIgnore
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name="category_products",
joinColumns={#JoinColumn(name="product_id", unique = false)},
inverseJoinColumns={#JoinColumn(name="category_id", unique = false)})
private Set<Category> categories;
...Getters and Setters omitted for brevity
}
ProductServiceImplementation
#Service
public class ProductService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private ProductRepository productRepository;
public List<Product> getProductsByShopId( Long id) {
List<Product> productList = new ArrayList<>();
productList = productRepository.findByShopId(id);
return productList;
}
public Set<Long> getCategoryIds(List<Product> products){
Set<Long> categoriesIDs = new HashSet<Long>();
for (Product product : products) {
product.getCategories().forEach(category -> {
categoriesIDs.add(category.getId());
});
}
return categoriesIDs;
}
}
The problem is getting the categoryIds that are mapped to the list of products.
How can i get CategoryIds from Product. My getCategoryIds function returns empty always
public Set<Long> getCategoryIds(List<Product> products){
Set<Long> categoriesIDs = new HashSet<Long>();
for (Product product : products) {
product.getCategories().forEach(category -> {
categoriesIDs.add(category.getId());
});
}
return categoriesIDs;
}
When running the application I get a org.hibernate.PersistentObjectException error from MockData. What is causing this? Are my cascadetypes wrong?
org.hibernate.PersistentObjectException: detached entity passed to
persist
The products are created and added after the categories, so the category should exist?
Product.java
#Entity
#Data
#Table(name = "product")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "manufacturer")
private String manufacturer;
#Column(name = "price")
private double price;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "category_id")
private Category category;
public Product(String name, String manufacturer, double price, Category category) {
this.name = name;
this.manufacturer = manufacturer;
this.price = price;
this.category = category;
}
public Product() {
}
}
Category.java
#Entity
#Data
#Table(name = "category")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<Product> products;
public Category(String name) {
this.name = name;
}
public Category() {
}
}
MockData.java
#Component
class MockData {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
#Autowired
public MockData(ProductRepository productRepository, CategoryRepository categoryRepository) {
this.productRepository = productRepository;
this.categoryRepository = categoryRepository;
loadData();
}
private void loadData() {
Category IT = new Category("IT");
Category beauty = new Category("Beauty");
Category food = new Category("Food");
categoryRepository.save(IT);
categoryRepository.save(beauty);
categoryRepository.save(food);
Product computer = new Product("Computer", "Dell", 5000, IT);
Product computer2 = new Product("Computer2", "HP", 5000, IT);
Product eyeliner = new Product("Eyeliner", "Chanel", 100, beauty);
Product hamburger = new Product("Angus", "Burger King", 100, food);
productRepository.save(computer);
productRepository.save(computer2);
productRepository.save(eyeliner);
productRepository.save(hamburger);
}
}
Changing mockdata to the following solved the problem:
#Component
class MockData {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
#Autowired
public MockData(ProductRepository productRepository, CategoryRepository categoryRepository) {
this.productRepository = productRepository;
this.categoryRepository = categoryRepository;
loadData();
}
private void loadData() {
Category IT = new Category("IT");
Category beauty = new Category("Beauty");
Category food = new Category("Food");
Product computer = new Product("Computer", "Dell", 5000, IT);
Product computer2 = new Product("Computer2", "HP", 5000, IT);
Product eyeliner = new Product("Eyeliner", "Chanel", 100, beauty);
Product hamburger = new Product("Angus", "Burger King", 100, food);
IT.setProducts(Arrays.asList(computer, computer2));
beauty.setProducts(Collections.singletonList(eyeliner));
food.setProducts(Collections.singletonList(hamburger));
categoryRepository.save(IT);
categoryRepository.save(beauty);
categoryRepository.save(food);
productRepository.save(computer);
productRepository.save(computer2);
productRepository.save(eyeliner);
productRepository.save(hamburger);
}
Products are created and added to categories before being saved.