JPA Cascaded Update/Delete of OneToMany - java

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.

Related

Hibernate #OneToMany Mapping - issues with deleting records

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.

One-To-Many relation in JPA DELETE operation is not working

The entity model and repository is given below.
Channel.java
public class Channel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "channel_name")
private String channelName;
#Column(name = "channel_type")
private Integer channelType;
#Column(name = "seq_id")
private Integer seqId;
#Column(name = "channel_device_key")
private String channeldeviceKey;
}
UserRoomChannel.java
public class UserRoomChannel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne
#JoinColumn(name = "user_house_id")
private UserHouse userHouse;
#OneToOne
#JoinColumn(name = "room_id")
private Room room;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Channel> channels;
}
UserRoomChannelReposirtory.java
public interface UserRoomChannelRepository extends JpaRepository<UserRoomChannel, Long> {
#Query(value = "DELETE FROM user_room_channel_channels WHERE channels_id=?1", nativeQuery = true)
void deleteUserRoomChannelChannels(Long id);
}
I can save the data successfully. When data is saved through this a third table named user_room_channel_channels is created.
EX:
user_room_channel_id channels_id
1 1
1 2
But When I tried to delete with channels_id it give me the error
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:.....
The native query what I write it execute from the command line.
But using JPA I can't do that.
Any help or any suggestion for resolving this issue?
A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance
is because before you delete your channels(and its children), you somehow do the following:
you load your UserRoomChannel along with its Channels children in the collection from the Database.
somewhere in your code you change the reference of the children collection : myUserRoomChannel.setChannels(newChannelCollections) or myUserRoomChannel.channels =new ChannelCollections();
and you try to delete the user with your repositorisory.
Hibernate who remembered having set the children collection with reference A to the User can find the collection anymore, because User.channels is now User.channels == B (with B being a new reference to your collection).
How to fix it:
just find the place where you are replacing your children collections and instead of:
myUserRoomChannel.setChannels(newChannelCollections), or
myUserRoomChannel.channels =new ChannelCollections(),
just do
myUserRoomChannel.getChannels().add/delete/clearYourChannels()
I just have done the below step and it works perfectly.
Reomve:
myUserRoomChannel.setChannels(channels)
Add
myUserRoomChannel.getChannels().removeAll(channels) and then
userRoomChannelRepository.save(myUserRoomChannel)

efficiently loading collection of collections from database in hibernate

I have an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/

Hibernate #Where annotation on a ManyToOne relation

I have recently started to refactor my project because I had to add an extra column to some of my table. The extra column is an Enum (Pending, or Active).
Because of that change I would need now to refactor ALL my queries to only retrieves a row if the status is ACTIVE.
After some research I found that we can annotate an Entity with the #Where annotation. it works fine where I use it on a simple column but my table look like this:
#Where(clause = 'state='ACTIVE'")
#Entity
public class Place {
#Column(name="id_place")
private String placeId;
#Column(name="name")
private String palceName;
#OneToMany(mappedBy = "place")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'state='ACTIVE'")
#Entity
public class Tag {
#Column(name="id_tag")
private String tagId;
#Column(name="name")
private String tagName;
#OneToMany(mappedBy = "tag")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'poi.state='ACTIVE' AND tag.state='ACTIVE")
#Entity
public class PlaceTag {
#Column(name="id")
private String id;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "place_id")
private Place place;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "tag_id")
private Tag tag;
...
...
}
Now my question would be how can make this statement ONLY return the places and tags that are ACTIVE ?
SELECT pt FROM PlaceTag pt;
Is this possible? Or will I have to write the query Explicitly ?
Thank you
As you already discovered, or simply use cases the #Where clause is just fine, but in your case, you want to filter PlaceTag by the place and tag too, so a joined is required in this situation.
So, you can keep the #Where clause for Place and Tag, while for PlaceTags you need to use a JPQL query:
select pt
from PlaceTag pt
join pt.tag t
join pt.place p
where
t.state='ACTIVE' and p.state='ACTIVE'
At least until #WhereJoinTable annotation is made to work for many-to-one associations too.

Need an example of a primary-key #OneToOne mapping in Hibernate

Can somebody please give me an example of a unidirectional #OneToOne primary-key mapping in Hibernate ? I've tried numerous combinations, and so far the best thing I've gotten is this :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#Id
#JoinColumn(name = "paper_cheque_id")
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
private PaperCheque paperCheque;
}
Whenever Hibernate tries to automatically generate the schema for the above mapping, it tries to create the primary key as a blob, instead of as a long, which is the id type of PaperCheque. Can somebody please help me ? If I can't get an exact solution, something close would do, but I'd appreciate any response.
I saved this discussion when I implemented a couple of #OneToOne mappings, I hope it can be of use to you too, but we don't let Hibernate create the database for us.
Note the GenericGenerator annotation.
Anyway, I have this code working:
#Entity
#Table(name = "message")
public class Message implements java.io.Serializable
{
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "message_id")
public MessageContent getMessageContent()
{
return messageContent;
}
}
#Entity
#Table(name = "message_content")
#GenericGenerator(name = "MessageContent", strategy = "foreign",
parameters =
{
#org.hibernate.annotations.Parameter
(
name = "property", value = "message"
)
}
)
public class MessageContent implements java.io.Serializable
{
#Id
#Column(name = "message_id", unique = true, nullable = false)
// See http://forum.hibernate.org/viewtopic.php?p=2381079
#GeneratedValue(generator = "MessageContent")
public Integer getMessageId()
{
return this.messageId;
}
}
Your intention is to have a 1-1 relationship between PaperChequeStopMetaData and PaperCheque? If that's so, you can't define the PaperCheque instance as the #Id of PaperChequeStopMetaData, you have to define a separate #Id column in PaperChequeStopMetaData.
Thank you both for your answers. I kept experimenting, and here's what I got working :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#SuppressWarnings("unused")
#Id
#Column(name = "paper_cheque_id")
#AccessType("property")
private long id;
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
#PrimaryKeyJoinColumn(name = "paper_cheque_id")
#JoinColumn(name = "paper_cheque_id", insertable = true)
#NotNull
private PaperCheque paperCheque;
#XmlAttribute(namespace = XMLNS, name = "paper-cheque-id", required = true)
public final long getId() {
return this.paperCheque.getId();
}
public final void setId(long id) {
//this.id = id;
//NOOP, this is essentially a pseudo-property
}
}
This is, by all means, a disgusting hack, but it gets me everything I wanted. The paperCheque property accessors are as normal (not shown). I've run into this kind of unidirectional OneToOne mapping problem before and settled for much worse solutions, but this time I decided I was going to figure out out, so I kept hacking away at it. Once again, thank you both for your answers, it's much appreciated.
Just updating this question for future views.
When this question was made i think there wasn't a proper solution for this problem. But since JPA 2.0 you can use #MapsId to solve this problem.
Reference with proper explanation: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
You should stay away from hibernate's OneToOne mapping, it is very dangerous. see http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128
you are better off using ManyToOne mappings.

Categories

Resources