Hibernate #OneToMany Mapping - issues with deleting records - java

In my application I would like to put all images in the application in one table. I posted a question last time and someone recommended that I use unidirectional #OneToMany.
I have the following entities which are associated with Image entity
#Entity
#Table(name="promotion")
public class Promotion {
#Id
#Column(name="id")
protected String id;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="itemId")
protected List<Image> images = new ArrayList<>();
}
#Entity
#Table(name="product")
public class Product {
#Id
#Column(name="id")
protected String id;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="itemId")
protected List<Image> images = new ArrayList<>();
}
#Entity
#Table(name="image")
public class Image{
#Id
#Column(name="id")
private String id = IdGenerator.createId();
#Column(name="itemId")
private String itemId;
#Column(name="title", nullable = false)
protected String title;
#Column(name="filename", nullable = false)
protected String filename;
#Column(name="path", unique = true)
private String path;
#Column(nullable = true)
protected int width;
#Column(nullable = true)
protected int height;
}
Issues am facing now are:
A)
When I use cascade={CascadeType.PERSIST, CascadeType.MERGE} on the images ArrayList attributes I get this exception:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
So I replaced it with cascade=CascadeType.ALL and when I save Product or Promotion the associated Image(s) are saved as well which is cool and that is what I want
B)
The main problem I have now is when I delete a Product or Promotion, its associated images never get deleted. Their associated images stays in the image table.
By using cascade=CascadeType.ALL I expect that when I delete a Product or a Promotion, its images should also be deleted automatically. I tried to delete an image from the database if it will trigger its associated Product or Promotion to be deleted but it didn't since I think it is unidirectional which makes sense. But how come when I delete a Product or a Promotion its associated images don't get deleted

add orphanRemoval = true in both relationships:
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
This will
apply the remove operation to entities that have been removed from the
relationship and to cascade the remove operation to those entities.

Related

Spring JPA most efficient way to load ManyToOne relationship?

I have an entity called StoreQuantity, which stores the current in stock quantity of all products/items in a store:
#Data
#Entity
#Table(name = "STORE_QUANTITY")
public class StoreQuantity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_QUANTITY_ID", nullable = false)
private int storeQuantityId;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
#Column(name = "INSTORE_QUANTITY")
private int instoreQuantity;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "STORE_ID", nullable = false)
private Store store;
}
Corresponding Store entity:
#Entity
#Table(name = "store")
public class Store implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_ID", nullable = false)
private int storeId;
#Column(name = "NAME", nullable = false)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "store")
private List<StoreQuantity> storeQuantityList;
}
Im trying to retrieve all the quantities of products in all stores, and export as csv. I currently have thought of two ways of doing so:
Either Retrieve the entire storequantity table in one call, and for each storequantity I print as csv.
public String currentStoreQuantitiesCSV() {
List<StoreQuantity> storeQuantityList = storeQuantityRepository.findAllByOrderByStoreDesc();
for (StoreQuantity storeQuantity : storeQuantityList) {
//StoreId
csvString.append(storeQuantity.getStore().getStoreId()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
Or I call them by store:
public String currentStoreQuantitiesCSV() {
List<Store> storeList = storeRepository.findAll();
for (Store store:storeList){
List<StoreQuantity> storeQuantityList = store.getStoreQuantityList();
for (StoreQuantity storeQuantity : storeQuantityList) {
//Store Name
csvString.append(storeQuantity.getStore().getName()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
}
They both work, now it's just a matter of efficiency and ram utilization. I read by default JPA will eagerly load any ManyToOne relationships: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate
So does this mean if I choose option 1, there will be as many copies of store objects for every storequantity object? This will be extremely bad as I only have 20-or so stores, but thousands and thousands of storequantities, and id each of them are loaded with their own store object it will be very bad. Or will every storequantity point to the same store Objects? I'm only considering method two because that way there wouldnt be a lot of store objects in memory.
I did some testing looking at the stack memory, it seems that JPA will automatically map all ManyToOne relationships to one object. So in this case for example we have one store, and 10 storequantities that have a ManyToOne to that store. JPA will only instantiate one store object and point all 10 storequantity objects to that one store, instead of creating one store for every storequantity object. So option 1 will be the most efficient as we decrease the amount of database calls.

JPA Cascaded Update/Delete of OneToMany

I have two entities "Article" and "Comments". Article has OneToMany relationship with Comment.
#Entity
#Table(name = "article")
public class Article implements Serializable
{
public Article()
{
}
private static final long serialVersionUID = 1L;
#Id
#Column(name = "article_id")
private int articleId;
#Column(name = "title")
private String title;
#Column(name = "category")
private String category;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "articleId", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
}
#Entity
#Table(name = "comment")
public class Comment
{
private static final long serialVersionUID = 1L;
public Comment()
{
}
#Id
#Column(name = "comment_id")
private int commentId;
#Column(name = "author")
private String author;
#Column(name = "text")
private String text;
#JoinColumn(name = "article_id", nullable = false)
private int articleId;
}
I am using JPA EntityManager to perform CRUD operations on Article.
I have an article with the following data in the "article" table.
article_id title category
1 Java 8 in 30 days Java
I have two comments with the following data in the "comment" table.
comment_id author text article_id
1 ABC Java is awesome ! 1
2 XYZ Nice Article !!! 1
Here is my EntityManager code which gets invoked when there is an update to an article including comments.
public Article update(Article article)
{
return entityManager.merge(article);
}
The issue that I am facing here is that whenever I delete a Comment from an existing article using the above method call then the comment really does not get deleted from the table. I understand that "merge" is same as "upsert", but I did not find any other method in EntityManager interface to achieve the comment deletion along with other changes to article.
In your code, #OneToMany(... cascade = CascadeType.ALL) means that whenever a modification is done on the parent (persist, delete, etc.), it is cascaded to the children as well. So if an Article is saved, all its' corresponding Comments are saved. Or if an Article is deleted, all its' corresponding Comments are deleted.
In your case, what you want is just to delete a Comment, unrelated to operations that happen in its' Article.
An easy way to do that is use #OneToMany(... cascade = CascadeType.ALL, orphanRemoval = true) and then when you decide to trigger delete on the first Comment for example, just use Collection.remove() on it:
Article article = ...;
//...
Comment targetComment = article.getComments().iterator().next();
article.getComments().remove(targetComment);
// now Hibernate considers `targetComment` as orphan and it executes an SQL DELETE for it
Make sure your collection is the only one holding a reference to the object referred by targetComment, otherwise you will have inconsistencies in memory.

Best ways to make unique field / table column in many to many relations Jpa

I have a "Product" and "Tag' class ManyToMany relationship. I get Products and tags from an rss feed. Some tag names are duplicated with new product entries (while inserting them) as many products come with same tag names.
I want to make Tag names UNIQUE. If, while adding a product, a Tag already exists, the product should be added there in that tag (or I should say associated with that tag) rather a new duplicate named tag be created.
If Tag does not exists, new tag created and product added there.
If, for example, product feed has [product 1, tag [tag1,tag2,tag3]] , [product 2, tag [tag7,tag5, tag1]]
If product 1 is added, tag 1, tag2, tag3 are created. then for product 2, tag 1 should not be created again
Question: Is there a built in way in JPA or I have to do check / if else() / getTagByName() each time and see if it exist.
What are my options?
I prefer a JPA option (not hibernate or ORM specific)
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany (cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private List<Tag> tagList;
//getters and setters
}
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany (mappedBy = "tagList", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private List<ListItem> listItemList;
#ManyToMany (mappedBy = "tagList", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private List<Product> productList;
//getters and setters
}

Hibernate sometimes leaves empty foreign key fields in child entities

Lately I encountered a strange phenomenon with my Hibernate/Postgres data. On unknown conditions Hibernate creates orphans of child entities (null foreign key field to parent). But I have annotated the #OneToMany relation as orphanRemoval.
The "main" entity:
#Entity
#Audited
public class Product {
#NotNull(groups = { CheckId.class })
#NotBlank(groups = { CheckId.class })
#Id
#Column(length = 32)
private String id = IdGenerator.createId();
#Version
private Integer version;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name=Product.PRODUCT_ID)
#Index(name="idx_prod_ident")
private Set<Identifier> identifiers;
...
}
One child entity:
#Entity
#Audited
public class Identifier {
#NotNull(groups = { CheckId.class })
#NotBlank(groups = { CheckId.class })
#Id
#Column(length = 32)
private String id = IdGenerator.createId();
#Version
private Integer version;
#NotNull
#Column(nullable=false, length=3)
#Index(name = "idx_id_prod_type")
private String type;
#NotNull
#Column(nullable=false, length=65)
#Index(name = "idx_id_prod_value")
private String value;
...
}
There are lots of such #OneToMany relations with different (but structurally similar) entities in Product. Millions of records are written correctly, but in most of them I occasionally encounter some hundreds with product_id null. How is this possible?
Unfortunately I cannot easily determine when this happens (due to the missing product_id). Also product_id is not part of the Envers history table of the child entities. So I cannot examine if the product still exists, and what the services have done with it lately.
For information: when a child entity is removed from the parent, this is done via
product.getIdentifiers.remove(identifier);
or
product.getIdentifiers.removeAll(identifiers);
or
product.getIdentifiers.clear();
This is hopefully a valid way to remove them ;)
If I correctly understand you use FetchType.LAZY - it mean the data will be retrieved only during the session, you can use Hibernate.initialize for retrieving #OneToMany fields or can use FetchType.EAGER instead FetchType.LAZY

Hibernate OneToMany Mapping - Where Set of Entities is getting saved?

I have two entity class named Customer and Activity.
Customer has customerId,customerName,activities here activities holds set of Activity corresponds to each Customer mapped by #OneToMany(mappedBy = "customer") relationship.
The Customer class has been defined as follows (I have removed other fields and some getter and setter for clarity):
#javax.persistence.Entity
#Table(name = "CUSTOMER")
public class Customer extends Entity {
private String customerId;
private String customerName;
private Set<Activity> activities;
#NaturalId
#Column(name = "CUSTOMER_ID", nullable = false)
public String getCustomerId() {
return customerId;
}
#Column(name = "CUSTOMER_NAME", nullable = false)
public String getCustomerName() {
return customerName;
}
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
#Cascade(CascadeType.SAVE_UPDATE)
public Set<Activity> getActivities() {
return activities;
}
}
Activity has activityId,activityName,customer which is mapped by customerId with #ManyToOne relation.
Activity class is defined like:
#javax.persistence.Entity
#Table(name = "ACTIVITY")
public class Activity extends Entity {
private String activityId;
private String activityName;
private Customer customer;
#NaturalId
#Column(name = "ACTIVITY_ID", nullable = false)
public String getActivityId() {
return activityId;
}
#Column(name = "ACTIVITY_NAME", nullable = false)
public String getActivityName() {
return activityName;
}
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID", nullable = false)
public Customer getCustomer() {
return customer;
}
}
Before saving a new Activity, I am adding this Activity to the activities set of Customer.
I want to know:
Where the activities of Customer are getting saved ?
How the update and delete operation of both of the entities will effect each other?
Is it the right way to create #OneToMany and #ManyToOne relationship?
Can I add column name on activities?
I am beginner in hibernate, any pointer would be very helpful to me.
Thanks in advance.
- Where the activities of Customer are getting saved ?
They are not getting saved as "activities of customer" anywhere in the DB.When the activities of a customer are needed, they are loaded together with the customer using a join query on the customer_id column, or they are loaded later (lazy-loading) using a separate query that filters the customer_id column.
- How the update and delete operation of both of the entities will effect each other?
A delete of an activity will not affect the customer, but next time its activities are loaded, the deleted activity will not be part of the collection anymore.
How the delete of a customer activities can be defined using the cascade attribute on the #ManyToOne, for example :
#OneToMany(mappedBy = "customer", cascade = CascadeType.REMOVE)
Basically, the desired behavior depends on if an activity can exist on its own without a customer : if yes, then the customer can be deleted, and the customer_id field can be set to null on the activity table. Otherwise the activities belonging to a customer will be 'cascade-deleted'. Or a customer just can0t be deleted before one has removed all its activities first.
- Is it the right way to create #OneToMany and #ManyToOne relationship?
Yes, it is correct.
- Can I add column name on activities?
No, it wouldn't make any sense. A column could only contain one ID, and activities is a collection.
I hope this helps.

Categories

Resources