Embedding objects rather than linking in Spring Data JPA - java

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.

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.

JPA ManyToMany: Adding existing object to another object

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.

Spring and Jackson: set json ignore dynamically

I have some JPA models: "Category" and "Article":
#Entity
#Table(name = "categories")
public class Category {
private int id;
private String caption;
private Category parent;
private List<Category> childrenList;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
#ManyToOne
#JoinColumn(name = "parent_id")
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
#OneToMany
#JoinColumn(name = "parent_id")
public List<Category> getChildrenList() {
return childrenList;
}
public void setChildrenList(List<Category> childrenList) {
this.childrenList = childrenList;
}
}
#Entity
#Table(name = "articles")
public class Article {
private int id;
private String caption;
private boolean isAvailable;
private String description;
private int price;
private Category category;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
#Column(name = "is_available")
#Type(type = "org.hibernate.type.NumericBooleanType")
public boolean getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(boolean available) {
isAvailable = available;
}
#Column
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Column
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
#ManyToOne
#JoinColumn(name = "category_id")
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
Also i have some REST controller with two methods:
1)In the first method i need to get and serialize last 10 Articles, but i don't need "childrenList" and "parent" field in Categegory.
2)In the second method i need to get the same but serialize "parent" field.
How can i solve this?
If i will use #JsonIgnore annotation to these fields then they will be never serialized.
Or should i use DTO classes?
How can i dynamically set field for ignoring?
I never use my Entitys for generating JSON, I think another set DTO classes will make you happier in the long run. My DTO typically has a constructor which takes the Entity as argument (it still needs a default constructor if you plan to use it for parsing incoming JSON).
If you really want to use your Entities, I would recommend that you use MixIns, which allows you to register a MixIn class, that augments the serialization of a specific class.
Here is a link to a MixIn example I made for another answer.
Use a custom serializer, the psedo code is below.
public class CategorySerializer extends StdSerializer<Category> {
public CategorySerializer() {
this(null);
}
public CategorySerializer(Class<Category> t) {
super(t);
}
#Override
public void serialize(Category value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// put the logic here to write the parent and child value or not
// here is the example to how the data is serialized
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("caption", value.caption);
jgen.writeEndObject();
}
}
Now, to utilize the custom serializer put this annotation above your Catagory entity class.
#JsonSerialize(using = CategorySerializer.class)

Hibernate #ManyToOne #JoinColumn is always null

I'm trying to implement One-to-Many relation between two tables using hibernate. Here is my code:
#Entity
public class Board
{
#Id
#Column(name = "board_id")
#GeneratedValue
private long id;
#Column
private String owner;
#Column
private String title;
#Column
private String refresh;
#Column
private Timestamp createDate;
#Column
private Timestamp modifyDate;
#OneToMany(mappedBy="board", cascade=CascadeType.ALL)
private List<Item> items;
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public String getOwner()
{
return owner;
}
public void setOwner(String owner)
{
this.owner = owner;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getRefresh()
{
return refresh;
}
public void setRefresh(String refresh)
{
this.refresh = refresh;
}
public Timestamp getCreateDate()
{
return createDate;
}
public void setCreateDate(Timestamp createDate)
{
this.createDate = createDate;
}
public Timestamp getModifyDate()
{
return modifyDate;
}
public void setModifyDate(Timestamp modifyDate)
{
this.modifyDate = modifyDate;
}
public List<Item> getItems()
{
return items;
}
public void setItems(List<Item> items)
{
this.items = items;
}
}
and second table:
#Entity
public class Item
{
public enum Type
{
link,
image,
presentation;
}
public enum JavaScript
{
enable,
disable;
}
#Id
#Column
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "board_id")
private Board board;
#Column
private Type type;
#Column(length = 10000)
private String link;
#Column
private String image;
#Column
private String presentation;
#Column
private String time;
#Column
private JavaScript javaScript;
#Column
private String first;
#Column
private String last;
#Transient
private MultipartFile imageFile;
#Transient
private MultipartFile presentationFile;
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public Board getBoard()
{
return board;
}
public void setBoard(Board board)
{
this.board = board;
}
public Type getType()
{
return type;
}
public void setType(Type type)
{
this.type = type;
}
public String getLink()
{
return link;
}
public void setLink(String link)
{
this.link = link;
}
public String getImage()
{
return image;
}
public void setImage(String image)
{
this.image = image;
}
public String getPresentation()
{
return presentation;
}
public void setPresentation(String presentation)
{
this.presentation = presentation;
}
public String getTime()
{
return time;
}
public void setTime(String time)
{
this.time = time;
}
public JavaScript getJavaScript()
{
return javaScript;
}
public void setJavaScript(JavaScript javaScript)
{
this.javaScript = javaScript;
}
public String getFirst()
{
return first;
}
public void setFirst(String first)
{
this.first = first;
}
public String getLast()
{
return last;
}
public void setLast(String last)
{
this.last = last;
}
public MultipartFile getImageFile()
{
return imageFile;
}
public void setImageFile(MultipartFile imageFile)
{
this.imageFile = imageFile;
}
public MultipartFile getPresentationFile()
{
return presentationFile;
}
public void setPresentationFile(MultipartFile presentationFile)
{
this.presentationFile = presentationFile;
}
}
but I can't get it working. board_id is always null in item table. Hibernate output looks strange:
Hibernate: insert into Board (board_id, createDate, modifyDate, owner, refresh, title) values (null, ?, ?, ?, ?, ?)
Hibernate: insert into Item (id, board_id, first, image, javaScript, last, link, presentation, time, type) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?)
any ideas?
To expand on the comment by #Antoniossss, you need to set the relation on both sides before persisting.
// Create owning entity first
Board board = new Board();
// Create child entities
Item item1 = new Item();
item1.setBoard(board); // set relation on Item side
board.getItems().add(item1); // set relation on Board side
Also note that it is considered good practice to initialize collection fields immediately, so that Board#getItems() never returns null.
Quick tip:
When using #GeneratedValue for an #Id field, it's best to avoid explicitly setting a value on that field.
#GeneratedValue means that either Hibernate or your database of choice will set the entity's id(either of which depends on your GenerationType), so setting an explicit value or allowing it to be publicly set possibly will result in throwing a Hibernate-specific StaleStateException.
So you should not include id in your constructor and also remove:
public void setId(long id)
{
this.id = id;
}

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