I have a JSON like this.
{
"productName":"soap",
"pQty":10,
"price" : 100,
"customerList":[
{
"name":"dasun",
"email":"lakmal#gmail.com",
"gender":"male"
},
{
"name":"BM",
"email":"BM#gmail.com",
"gender":"male"
}
]
}
I want to save this data into two separet tables called Product and Customer which having 1 to many relationship same as JSON`s appears.
I tried to save data using #OnetoMany but I couldn't. So I created entities like this.
#Data
#Entity
public class Product {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "product_id",updatable = false, nullable = false)
private Long pId;
private String productName;
private int pQty;
private int price;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
}
Customer Entity
#Data
#Entity
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "customer_id",updatable = false, nullable = false)
private Long custId;
private String name;
private String email;
private String gender;
}
My Rest Controller
#PostMapping(path = "/save/product",produces = MediaType.APPLICATION_JSON_VALUE)
public Iterable<Product> saveProduct(#RequestBody Product product){
Iterable<Product> response = automantionApiService.saveProduct(product);
return response;
}
My Repositories
#Repository
public interface ProductRepository extends JpaRepository<Product,Long> {
}
#Repository
public interface CustomerRepository extends JpaRepository<Customer,Long> {
}
This is where I want your attention. here what I`m doing is iterating request and trying to create new data rows as same as the request and save to DB.
#Override
public Iterable<Product> saveProduct(Product product) {
Product product1 = new com.adl.dte.core.model.Product();
product1.setProductName(product.getProductName());
product1.setPQty(product.getPQty());
product1.setPrice(product.getPrice());
Customer customer = new Customer();
List<Customer> customerList = new ArrayList<>();
if(!product.getCustomerList().isEmpty()){
product.getCustomerList().forEach( listOfCust ->{
customer.setName(listOfCust.getName());
customer.setEmail(listOfCust.getEmail());
customer.setGender(listOfCust.getGender());
customerRepository.save(customer);
product1.setCustomer(customer);
productRepository.save(product1);
});
}
return productRepository.findAll();
}
But my problem is only the last customer will be saved to the Db who named "BM". same as the response like this.
[
{
"productName": "soap",
"price": 100,
"customer": {
"custId": 5,
"name": "BM",
"email": "BM#gmail.com",
"gender": "male"
},
"pqty": 0,
"pid": 6
}
]
My target is to save each and every customer to the customer table and mapped with Person.
What I got wrong here?
Thanks.
First off, fix the relationship as follow
#Data
#Entity
public class Product {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "product_id",updatable = false, nullable = false)
private Long pId;
private String productName;
private int pQty;
private int price;
#OneToMany
private List<Customer> customerList;
}
Product entity will change as below
#Data
#Entity
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "customer_id",updatable = false, nullable = false)
private Long custId;
private String name;
private String email;
private String gender;
#ManyToOne
private Product product;
}
then change your saveProduct as below
#Override
public Iterable<Product> saveProduct(Product product) {
Product product1 = new com.adl.dte.core.model.Product();
product1.setProductName(product.getProductName());
product1.setPQty(product.getPQty());
product1.setPrice(product.getPrice());
product1.setCustomerList(product.getCustomerList());
product.getCustomerList().forEach(cust -> cust.setProduct(product1));
productRepository.save(product);
return productRepository.findAll();
}
You have some problems:
First, define a Customer list (List<Customer> customers) in your Product entity bean. Then add customers to it with the addCustomer method.
Also use for each customer a own new Customer object and don't use one Customer and update the values.
Saving each Customer is not necessary. Saving the Product object/entity should be enough.
The values are saved to the database when the transaction is committed and closed. Spring handles this for you.
Related
I'm building a RESTful API GET method with Spring Boot to get return of a Bill entity as JSON from database. The return is not expected as it has many duplicated values and a StackOverFlowError.
[{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":
//continues for eternity
Hibernate log:
Hibernate:
select
bill0_.bill_id as bill_id1_0_,
bill0_.date as date2_0_,
bill0_.time as time3_0_,
bill0_.total as total4_0_
from
bill bill0_
Hibernate:
select
billdetail0_.bill_id as bill_id1_1_0_,
billdetail0_.menu_item_id as menu_ite2_1_0_,
billdetail0_.bill_id as bill_id1_1_1_,
billdetail0_.menu_item_id as menu_ite2_1_1_,
billdetail0_.quantity as quantity3_1_1_,
billdetail0_.subtotal as subtotal4_1_1_,
menuitem1_.menu_item_id as menu_ite1_2_2_,
menuitem1_.description as descript2_2_2_,
menuitem1_.img_url as img_url3_2_2_,
menuitem1_.name as name4_2_2_,
menuitem1_.price as price5_2_2_,
menuitem1_.status as status6_2_2_,
menuitem1_.type as type7_2_2_
from
bill_detail billdetail0_
inner join
menu_item menuitem1_
on billdetail0_.menu_item_id=menuitem1_.menu_item_id
where
billdetail0_.bill_id=?
How can I get a return of a Bill like this:
{
"billId": 1,
"date": 2022-05-20,
"time": 16:48:06,
"total": 330000,
"billDetails": [
{
"menuItem": {
"id": 1,
"name": Rice,
// other attributes of MenuItem
},
"quantity": 2
"subtotal": 90000
},
{
"menuItem": {
"id": 2
"name": Wine
// other attributes of MenuItem
},
"quantity": 4
"subtotal": 240000
}
]
}
This is my classes and related functions
Class Bill
#Entity(name = "bill")
#Table(name = "bill")
public class Bill {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "bill_id")
private Long id;
private LocalDate date;
private LocalTime time;
private Double total;
#OneToMany(mappedBy = "bill", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();
Class MenuItem
#Entity
#Table(name = "menuItem",
uniqueConstraints = {
#UniqueConstraint(name = "menu_item_name_unique", columnNames = "name")
}
)
public class MenuItem {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "menu_item_id")
private Long id;
private ItemType type;
private String name;
private String description;
private String imgUrl;
private Double price;
private MenuItemStatus status = MenuItemStatus.ENABLED;
#OneToMany(mappedBy = "menuItem", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();
Class BillDetail
#Entity
#Table(name = "bill_detail")
public class BillDetail {
#EmbeddedId
private BillMenuItemID billMenuItemID = new BillMenuItemID();
#ManyToOne
#MapsId("billId")
#JoinColumn(name = "bill_id")
private Bill bill;
#ManyToOne
#MapsId("menuItemId")
#JoinColumn(name = "menu_item_id")
private MenuItem menuItem;
#Column
private Long quantity;
#Column
private Double subtotal;
GET method
#GetMapping
public List<Bill> getMenuItems() {
return billService.getBills();
}
public List<Bill> getBills() {
return billRepository.findAll();
}
public interface BillRepository extends JpaRepository<Bill, Long> {
}
Database
database
In the class MenuItem you should add the annotation #JsonIgnore to prevent an infinite loop in the JSON format returned; a bill has a BillDetails , a BillDetails has a MenuItem , a MenuItem Has a BillDetails , every BillDetail has a List of MenuItem ...
#Entity
#Table(name = "menuItem",
uniqueConstraints = {
#UniqueConstraint(name = "menu_item_name_unique", columnNames = "name")
}
)
public class MenuItem {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "menu_item_id")
private Long id;
private ItemType type;
private String name;
private String description;
private String imgUrl;
private Double price;
private MenuItemStatus status = MenuItemStatus.ENABLED;
// ADD JSON IGNORE ANNOTATION HERE :
#JsonIgnore
#OneToMany(mappedBy = "menuItem", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();
I set up the entity relationship with Product - Cart this way:
Product.java
#Entity(name="Product")
#Getter
#Setter
public class Product implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String description;
private int amount;
private String unit;
private double price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn()
private Cart cart;
}
Cart.java
#Entity(name="Cart")
#Getter
#Setter
public class Cart implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#OneToMany(
mappedBy="cart",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Product> shoppingList = new ArrayList<>();
private boolean payed = true;
}
What my task is to make a post request to localhost:8080/cart without a request body and the response should be this:
{
"id": 0,
"shoppingList": [
{
"productId": 0,
"amount": 0
}
],
"payed": true
}
So as you can see, I somehow need to put to the shoppingList only the productId and the amount (both are coming from product where I set up the entity relationship)
Thats how I make the POST request:
#PostMapping()
#ResponseStatus(HttpStatus.CREATED)
public Cart addProduct() {
return new Cart(this.service.create());
}
And this is my service where the logic should go:
#Override
public Cart create() {
Cart c = new Cart();
c.setPayed(false);
return this.repository.save(c);
}
That's where I need help to somehow make a c.setShoppingList( ??? ) to get id and the amount from product but only these two
I have these entities:
#Entity
#Data
#NoArgsConstructor
public class Hero {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String name;
#OneToMany(mappedBy = "character", cascade = CascadeType.ALL, orphanRemoval = true)
#NotEmpty
private List<HeroSkill> heroSkills;
}
#Entity
#Data
#NoArgsConstructor
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
}
#Entity
#Data
#NoArgsConstructor
public class HeroSkill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "hero_id", referencedColumnName = "id")
#JsonIgnore
private Hero hero;
#ManyToOne
#JoinColumn(name = "skill_id", nullable = false)
private Skill skill;
private int ranks;
}
My HeroService is like this:
#Transactional
public void create(Hero hero) {
heroRepository.save(hero);
}
I am using postman to create a new Hero and this is a sample POST request:
{
"name": "Test",
"heroSkills": [
{
"skill": {
"id": 1
},
"ranks": 4
},
{
"skill": {
"id": 2
},
"ranks": 4
}
]
}
Although a hero is created and two entities on HeroSkill table are also created, the HeroSkill hero_id column is null, so my new heroes are not associated with their skills as they should be.
It works if I modify my service like this:
#Transactional
public void save(Hero hero) {
Hero result = heroRepository.save(hero);
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(result);
heroSkillRepository.save(it);
});
}
But I think that I shouldn't have to pass the Hero manually to each HeroSkill. Isn't that the point of adding CascateType.ALL (which includes CascadeType.PERSIST)?
What is the correct approach to that?
Thanks in advance.
There is no reference of Hero(Parent) in HeroSkill(child) that's why JPA can't resolve hero_id and set as null. Use this heroSkill.setHero(hero) to set parent in child.
To save child with parent in bidirectional relation you have to make sync both side.
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(hero);
});
Hero result = heroRepository.save(hero);
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 am trying to save a "User" object that is related by a OneToMany relationship to a "Volunteer" object.
When I try to save it, it only works when I provide the primary IDs for both these objects. However, what I need is to save the entity and let the database dictate the ID's via autoIncrement. I am not sure how am I suppose to do this or even if it's possible.
Json Mapping that works:
{
"id":8,
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"id":6,
"committeesId": 2,
"outreachDate": "2019-12-07",
"usersId": 8
}]
}
Json Mapping that I need (but will not work):
{
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"committeesId": 2,
"outreachDate": "2019-12-07",
}]
}
So I am thinking maybe there's a way to connect the foreign keys so that I wont have to explicitly add the autoIncrement IDs (usersId, volunteersId).
User controller:
#Controller
public class UserController {
#RequestMapping(value = "/v1/users", method = RequestMethod.POST)
public ResponseEntity<Object> saveUsers( #RequestBody UserEntity request){
try {
return ResponseEntity.ok(userService.saveUser(request));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
User service:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public Page<UserEntity> saveUser(UserEntity user){
userRepository.save(user);
Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
return userRepository.findAll(pageable);
}
}
User Repository:
public interface UserRepository extends JpaRepository<UserEntity, Long> {
public List<UserEntity> findAllByOrderByIdAsc();
public List<UserEntity> findAllByOrderByIdDesc();
public Page<UserEntity> findByUserNameContaining(String userName, Pageable pageable);
}
User Entity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
private long id;
#Column(name = "username")
private String userName;
private String password;
#Column(name = "volunteers_id", nullable = true)
private Long volunteersId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "users_id")
private List<VolunteerEntity> volunteers = new ArrayList<>();
// omitted getters and setters
}
Volunteer Entity:
#Entity
#Table(name = "volunteers")
public class VolunteerEntity {
#Id
private long id;
#Column(name = "committees_id")
private long committeesId;
#Column(name = "outreach_date")
private Date outreachDate;
#Column(name = "users_id")
private Long usersId;
// omitted getters and setters
}
Any ideas or suggestions how to save this whole entity? I am wondering if this is really possible to save as in one whole process. Though if not, I am thinking of just saving them independently (User info first, then Volunteer next) but just in case it would be possible, it would really be a great help
You need to add #GeneratedValue anotation next to the #id
#Id
#GeneratedValue(strategy = GenerationType.selectOne)
private long id;
In case of sequence you need to add an extra anotation
#Id
#SequenceGenerator(name = "customName", sequenceName = "sequenceNameInDatabase")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="customName")
private long id;
This will make the primary id generation auto
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;