JPA ManyToMany: Adding existing object to another object - java

I am using JPA and wanted to figure out how the many-to-many relationship. Let's say I have a "Store" and a "Customer". These have a many to many relationship.
So my understanding is, a Store can have many Customers, and Customer can be associated with many Stores. So what I wanted to do is create two Stores and several customers. Then I wanted to have the same customer be a part of Store 1 and Store 2. However, when I saved Store 1 with the customer, and then took that same customer and associated it with Store 2 (let's say the customers shops at both stores), I get this error message: detached entity passed to persist.
Not sure how to resolve this. Any help and comments are appreciated. Thanks in advance!
#Entity
public class Store {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
private List<Item> items = new ArrayList<>();
#ManyToMany(cascade=CascadeType.ALL)
private List<Customer> customers = new ArrayList<>();
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
public Store() {
}
public Store(String name) {
this.name = name;
}
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 List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
public Store addItem(Item item) {
items.add(item);
return this;
}
}
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
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;
}
}
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private BigDecimal price;
public Item() { }
public Item(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public Item() {
}
public Item(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
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 BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
This is the driver code using Spring Boot:
Store safeway = new Store("Safeway4");
safeway.addItem(new Item("Fuji Apple", new BigDecimal(1)));
safeway.addItem(new Item("Black Grapes", new BigDecimal(2)));
safeway.addItem(new Item("Cheese Pizza", new BigDecimal(10)));
Store bestBuy = new Store("Best Buy4");
bestBuy.addItem(new Item("55 inch TV", new BigDecimal(550)));
bestBuy.addItem(new Item("Bluray Player", new BigDecimal(85)));
bestBuy.addItem(new Item("Nikon SLR", new BigDecimal(1500)));
Customer elf = new Customer();
elf.setName("Elf");
Customer neo = new Customer();
neo.setName("Neo");
safeway.getCustomers().add(elf);
safeway.getCustomers().add(neo);
Customer yoda = new Customer();
yoda.setName("Yoda");
Customer crazy = new Customer();
crazy.setName("Crazy");
bestBuy.getCustomers().add(yoda);
bestBuy.getCustomers().add(crazy);
log.debug("adding neo to best buy");
bestBuy.getCustomers().add(neo); // Adding Neo to both stores!
log.debug("saving safeway 1");
storeRepository.save(safeway);
log.debug("saving safeway 1 done");
log.debug("saving bestBuy 1");
storeRepository.save(bestBuy); // error happens here <-----------
log.debug("saving bestBuy 1 done");

If you remove the CascadeType.ALL, you'll avoid this problem.
Logically, a Customer can exist without ever being associated to any Store. That means the lifecycle of a Customer should be independent of that of a Store entity, thus cascading any operation for a Customer from Store is wrong.
You save your Customer instances separately, associate the saved instances with the appropriate Store and then save it separately.

Related

I want to show the nested json on browser but something is wrong where I'm getting infinite nested and repeated output

this is Normal Pojo classes of Order and OrderItem
#Entity
#Table(name = "Orders")
public class Order {
//#EmbeddedId
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int Oid;
String name;
double price;
Order(){
}
#OneToMany(mappedBy = "placeOrder",cascade = CascadeType.ALL )
List<OrderItem> items;
public void setItems(List<OrderItem> items) {
this.items = items;
}
public List<OrderItem> getItems() {
return items;
}
Order(String name,Double price){
this.name=name;
this.price=price;
}
public int getOid() {
return Oid;
}
public void setOid(int oid) {
Oid = oid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
#Entity
#Table(name = "OrderItems")#Data
#NoArgsConstructorpublic class OrderItem {
#Id#GeneratedValue(strategy = GenerationType.IDENTITY)
int itemId;
String itemName;
String amt;
#ManyToOne(fetch = FetchType.EAGER)
//Since many records from here are attacted to some other table's one record
#JoinColumn(name="Oid")
//used to create the Foreign key relation with column name Oid
Order placeOrder;
//Oid
OrderItem(String itemName,String amt,Order order){
this.itemName=itemName;
this.amt=amt;
this.placeOrder=order;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
}
Here this is my Controller and currently I'm not using the Services just the normal Repository(jpaRepository)
#GetMapping("/shoData/{Oid}")
Optional<Order> showOrderDetails(#PathVariable int Oid) {
//Oid=2;
System.out.println(Oid);
Optional<Order> opOrder;
opOrder=orepo.findById(Oid);
System.out.print("demo");
return opOrder;
}
I'm getting output but the Output is kinda messy actually its too much but I cut it down previously it was working totally fine but now dont know what happened suddenly
{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":{"name":"patel","price":7899.0,"items":[{"itemId":4,"itemName":"cat","amt":"73","placeOrder":}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}}]}
here I removed many lines but its too much and with this output my console is even cant load it
my Order table one row is Linked to the OrderItem table multiple rows
Probably OrderItem.placeOrder points again to the same order the OrderItem belongs to. So you get a refence loop.
You may add annotation
#JsonIgnore
Order placeOrder;
Anyway, it is not a good idea to return entities in the controller.

Embedding objects rather than linking in Spring Data JPA

I'm creating a simple web store as part of a larger application, using Spring Boot and Spring Data JPA. I have a number of items (my stock), and an order will consist of a collection of those items (along with shipping information, etc).
It's possible that items in the stock will update - for example, changing the price. I want to be able to do this without the overhead of versioning, etc. However, when I look up past orders I want to get the information that was used at the time.
Is there a way in Spring Data JPA of embedding a copy of the item in my order, rather than linking to the item object?
My classes are below:
ShopItem.java
#Entity
public class ShopItem {
#Id
#GeneratedValue
private Long id;
private String name;
private String description;
private int price;
private int stock;
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 String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
public void reduceStock(int by){
this.stock = this.stock - by;
}
}
ShopOrder.java
#Entity
public class ShopOrder {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Member member;
private boolean postage;
#OneToMany
private List<ShopOrderItem> items;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Member getMember(){
return member;
}
public void setMember(Member member) {
this.member = member;
}
public boolean getPostage() {
return postage;
}
public void setPostage(boolean postage) {
this.postage = postage;
}
public List<ShopOrderItem> getItems() {
return items;
}
public void setItems(List<ShopOrderItem> items) {
this.items = items;
}
}
ShopOrderItem.java
#Entity
public class ShopOrderItem {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private ShopItem item;
private int quantity;
public ShopOrderItem(ShopItem item, int quantity){
this.item = item;
this.quantity = quantity;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ShopItem getItem() {
return item;
}
public void setItem(ShopItem item) {
this.item = item;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public int getTotalPrice(){
return item.getPrice() * quantity;
}
}
Spring Data JPA is only a way to make your life easier when you use an ORM. and a ORM is only a way to make your life easier when you use a Database.
The thing is, if you are using a relational Database, there is no way to do what you want except by cloning datas.
so, one way is to clone your ShopItem entity and store it as a new entry in your database in a dedicated table.
another way may be to use a NoSQL DB which could handle this better.
There is a number of different ways to store a history of orders, but I can't think of anything from Spring data JPA which could do it out of the box.

Cannot delete resource in Spring Hateoas

I have a spring boot application which exposes resources using Spring HATEOAS. All the method GET, POST, PATCH works fine except DELETE. When I send a delete request to a resource, it returns 204 No content response but when I request for all resource, the item which I deleted appears again. No exception is logged on the console. No error in postman request.
The resource I am trying to delete is having many-to-one association with another POJO. But those resources which doesn't have many-to-one(some have one-to-many) is getting deleted.
The Mode Entity
#Entity
#Table(name="Modes")
public class Mode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "mode", fetch=FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Expense> expense;
public Mode() {}
#Autowired
public Mode(String name,Set<Expense> expense) {
this.name = name;
this.expense = expense;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The Category Entity
#Entity
#Table(name="Categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "category", fetch=FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Expense> expense;
public Category() { }
#Autowired
public Category(String name, Set<Expense> expense) {
this.setName(name);
this.expense = expense;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The Expense Entity
#Entity
#Table(name="Expenses")
public class Expense {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private BigDecimal amount;
#ManyToOne
#JoinColumn(name="categoryId")
private Category category;
#ManyToOne
#JoinColumn(name="modeId")
private Mode mode;
private Date date;
public Expense() {}
public Expense(String name, BigDecimal amount, Category category, Mode mode, Date date) {
this.name = name;
this.amount = amount;
this.category = category;
this.mode = mode;
this.date = date;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
The repositories I used
public interface CategoryRepository extends CrudRepository<Category, Integer> {
}
public interface ExpenseRepository extends CrudRepository<Expense, Integer> {
}
public interface ModeRepository extends CrudRepository<Mode, Integer> {
}
The delete request for Expense is not working
I use MySQL as database and use Postman to test the URL
Try Changing from the cascade cascade = CascadeType.ALL)
and set cascade = CascadeType.REMOVE, orphanRemoval = true it should work
Read the docs for more information:
https://docs.oracle.com/cd/E19798-01/821-1841/giqxy/

hibernate.hbm2ddl.auto=update don't work

I have model. there is this part:
model was mapped by jpa annotations.Everywhere I use fetchType = EAGER. If I load vacancy from database, I have 2 duplicates status_for_vacancy objects.
I use property hbm2ddl.auto = update.
If I make new schema of database and fill data, I haven't duplicates status_for_vacancy objects.
It really?
code:
vacancy:
#Entity
#Table(name = "vacancy")
#XmlRootElement(name="vacancy")
public class Vacancy {
private List<VacancyStatus> statusList = new LinkedList<VacancyStatus>();
#OneToMany(mappedBy = "vacancy", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<VacancyStatus> getStatusList() {
return statusList;
}
public void setStatusList(List<VacancyStatus> statusList) {
this.statusList = statusList;
}
}
status_for_vacancy:
#Entity
#Table(name = "status_for_vacancy")
public class StatusForVacancy extends AbstractStatus {
public StatusForVacancy() {
super();
}
public StatusForVacancy(Integer id, String name) {
super(id, name);
}
}
#MappedSuperclass
#XmlRootElement
public abstract class AbstractStatus {
private Integer id;
private String name;
public AbstractStatus() {
super();
}
public AbstractStatus(String name) {
super();
this.name = name;
}
public AbstractStatus(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column (name ="id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "name")
#NotEmpty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
vacancy_status:
#Entity
#Table(name = "vacancy_status")
public class VacancyStatus extends AbstractHistoryStatus {
private Vacancy vacancy;
private StatusForVacancy status;
public VacancyStatus() {
super();
}
public VacancyStatus(Integer id, User author, Date date,
Vacancy vacancy, StatusForVacancy status) {
super(id, author, date);
this.vacancy = vacancy;
this.status = status;
}
#ManyToOne
#JoinColumn(name = "vacancy_id")
public Vacancy getVacancy() {
return vacancy;
}
public void setVacancy(Vacancy vacancy) {
this.vacancy = vacancy;
}
#ManyToOne
#JoinColumn(name = "status_id")
public StatusForVacancy getStatus() {
return status;
}
public void setStatus(StatusForVacancy status) {
this.status = status;
}
}
#MappedSuperclass
public abstract class AbstractHistoryStatus {
private Integer id;
private User author;
private Date date;
public AbstractHistoryStatus() {
}
public AbstractHistoryStatus(Integer id, User author, Date date) {
super();
this.id = id;
this.author = author;
this.date = date;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
#Column(name="creation_date")
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
It is all mapping code for these entities.
in debugger:
both id==500 ==> hibernate understand, that it is same objects.
I try add all data from old database to new database - I get old error(
I fix cause of appearance of this problem. It appearances if I add record to note table:
I highly recommend you write equals() and hashCode() methods. The standard equals()/hashCode() implement referential equality (do 2 objects reference the same memory location). So if hibernate has 2 of the 'same' object in memory, but they don't reference the same memory location then you will see the object show up twice. But if you implement equals() based on primary key being equal, then even if there are two copies of the same object in memory, Hibernate won't give you duplicates.
See the JPA spec:
2.4 Primary Keys and Entity Identity
Every entity must have a primary key. ... The value of its primary key
uniquely identifies an entity instance within a persistence context
and to EntityManager operations
Also see this SO post.

hibernate many-to-many table mapping with extra fields as a list - Java classes?

I am quite interested in a Hibernate mapping such as the Order/Product/LineItem described here:
http://docs.jboss.org/hibernate/stable/core/reference/en/html/example-mappings.html#example-mappings-customerorderproduct
The documentation seems quite thorough, but I am a bit unclear on the semantics of the Java classes that one would create...
Any hints much appreciated.
Thank you!
Misha
Using that example, you would have classes that looked like the following:
import java.util.HashSet;
import java.util.Set;
public class Customer {
private String name = null;
private Set<Order> orders = new HashSet<Order>();
private long id = 0;
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 Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Order {
private long id = 0;
private Date date = null;
private Customer customer = null;
private List<LineItem> lineItems = new ArrayList<LineItem>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public List<LineItem> getLineItems() {
return lineItems;
}
public void setLineItems(List<LineItem> lineItems) {
this.lineItems = lineItems;
}
}
public class LineItem {
private int quantity = 0;
private Product product = null;
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
public class Product {
private long id = 0;
private String serialNumber = null;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
}
If you create the tables as per the structure in the example, that should set you up.
They show the UML and the class members in the diagram.
A Customer can have zero to many Order objects (Set).
An Order has at least one to many LineItem objects. {List).
A LineItem entry corresponds to a Product. So LineItem has exactly one Product object.
Not sure what your question is?

Categories

Resources