Duplication with CascadeType - java

I'm a little bit confused right now. I have three tables: specialPrice, partner, product.
In the specialPrice I can store discount for products which will available for the selected partner. So it has only three columns. Partner and product table and they row is referenced from the specialPrice table.
These three tables are represented as entities in my Java application as well. Here is my problem: if I want to store one specialPrice I got the following exception:
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: 0.
the error message is helpful, I have to use CascadeType, okay. BUT if I use the CascadeType.ALL the specialPrice will be created (so no more java.lang.IllegalStateException), but in the product and in the partner table the selected partner and the product will be duplicated... I don't understand how is this possible?
specialPrice Entity:
#Basic(optional = false)
#NotNull
#Column(name = "DISCOUNT_RATE")
private int discountRate;
#JsonBackReference
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Product productId;
#JsonBackReference
#JoinColumn(name = "PARTNER_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Partner partnerId;
reference from partner Entity:
#JsonIgnore
#OneToMany(mappedBy = "partnerId")
private Collection<SpecialPrice> specialPriceCollection;
(same for the product)
Using EclipseLink (JPA 2.1)
Can someone help me, what am I doing wrong? I don't want to duplicate the selected partner and product...
Thank you!

I understand Partner and Product are both pre-existing entities?
In such case, try the following code in your service method:
specialPrice.setPartner(partnerRepository.getOne(specialPrice.getPartnerId().getId());
specialPrice.setProduct(productRepository.getOne(specialPrice.getProductId().getId());
specialPriceRepository.save(specialPrice);
As a side note, using CascadeType.ALL with #ManyToOne is almost never a good idea.

Related

Spring boot Hibernate Infinity fetch loop

I've made some research but without any specific answer.
I know how #JsonView... #JsonIgnore works... but my point here is about back end, the point of view from there. I'm working on spring boot and by default OSIV is enabled, so as far as I know, if I'm not wrong, if I make a call in database on an #Entity that has #ManyToMany association it will eagerly fetch everything.
Till there I have no issues, the problem is that the associated Collection also has Collections... And some services need to fetch them and others don't... Then I keep getting LazyInitializationException.
#Entity
#EntityListeners(AuditingEntityListener.class)
#Inheritance(strategy = InheritanceType.JOINED)
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private int id;
private String categoryTitle;
private String categoryDescription;
#ManyToMany
#JoinTable(
name = "Category_Parent",
joinColumns = #JoinColumn(name = "id_category", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_category_parent")
)
private Set<Category> parentCategory;
#ManyToMany
#JoinTable(
name = "Category_Parent",
joinColumns = #JoinColumn(name = "id_category_parent", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_category")
)
private Set<Category> subCategory;
Then to prevent that error I used #Query like this
#Repository
public interface CategoryRepository extends JpaRepository<Category, Integer> {
#Query("from Category c left join fetch c.subCategory left join fetch c.parentCategory")
List<Category> getAllCategories();
}
Now I'm able to fetch it lazly... I used that #Query way because it is the only one I know to fetch the associated Collections... I heared about EntityGraph and Hibernate.initialize() but have no knowledge on how to proceed (would appreciate some link).
So, then I have Json exception because the json response is infinite. How can I avoid this new issue? Using DTO?
I appreciate.
------ EDIT ------
I've used #JsonView on the properties that I want to send as response, however if I use #JsonView over subCategory only, it works, but if I use on parentCategory I got the infinite loop once again... Can't solve it.
You can add fetch = FetchType.LAZY to your #ManyToMany or #OneToMany annotation like this: #ManyToMany(fetch = FetchType.LAZY). More instruction is at https://www.baeldung.com/hibernate-lazy-eager-loading

Java Spring Boot - JPA : StackOverflowError with a #ManyToMany relation

I am currently developing an application with the API in JavaSpringBoot and I need to add relationships between users (friends). For this I made a many-to-many relation which contains two fields:
CREATE TABLE friend_relation
(
fk_id_friend integer REFERENCES users (id) NOT NULL,
fk_id_user integer REFERENCES users (id) NOT NULL,
PRIMARY KEY (fk_id_friend, fk_id_user)
);
When I connect with a user and add relationships : everything is fine, but if I connect to one of the accounts added as a friend by the other user, there is a StackOverflowError.
I know it's because of the almost identical entries in the database, but I have no idea how to get my entity to be correct.
Currently each user must add the other individually, I imagine that I have to make a friend request system but again I am blocked.
Do I have to make an "effective" field in my friend_relation table. If so, how do I use it? Should I create a specific entity for this table or leave it in the User entity?
Currently, this is what my user entity looks like:
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When trying to modify my entity to avoid the error:
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_user"),
inverseJoinColumns=#JoinColumn(name="fk_id_friend")
)
private List<UserEntity> friends;
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_friend"),
inverseJoinColumns=#JoinColumn(name="fk_id_user")
)
private List<UserEntity> friendOf;
I looked for resources on the internet but I did not find it, I may have searched poorly and I apologize in advance if the answer has already been given.
If you can help me it is with great pleasure, thank you in advance!
(And sorry for the Google Translate I preferred not to use my rough English)✌
Ok sorry update, i don't post the stake trace : https://pastebin.com/Ls2qRpU4
It happens when I get my user on the front side. I am trying to connect but I cannot because this error occurs.
First of, I've noticed an #Data on your entity, which I suppose is from Project Lombok? Just be careful, Lombok generated methods can trigger lazy loading and there are problems with equals/hashCode for auto-generated IDs. (see here and here)
Now to your problem:
It's indeed a JSON serialization issue. You have circular references and Jackson runs in circles bc. it doesn't know where to stop.
You can either use a DTO projection where you resolve this circle yourself, like:
public class UserDto {
public Integer id;
public List<Integer> friends; // List of IDs
}
You can also use Jackson annotations to achieve almost the same thing, like #JsonIdentityInfo. Here's an article about it.
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When you have complex Entities with circular references or very large object trees, you often have to think hard about how to serialize them - especially when taking LazyLoading into account. I have spent a lot of time on this in complex professional projects. Simple automatically generated DTOs and serialization sometimes don't cut it.
I'm not an expert on this, so I'm sure others will give more accurate answers. However, the problem seems to occur when mapping the entity to JSON. You can't map to JSON a user with a collection of friend users that also have their friends in a fully mapped collection since that causes infinite recursion. I would try to use Jackson annotations to make the serialization of the friends list produce a list of ids instead of a list of complete users.

Why MERGE operation works when saving parent with no primary key which has children with primary key?

I have following classes in bidirectional many to many relationship.
#Table(name = "message")
#Entity
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "message_id", unique = true, nullable = false)
private int id;
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
#JoinTable(name = "tags_messages",
joinColumns = #JoinColumn(name = "message_id", referencedColumnName = "message_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "tag_id"))
private Set<Tag> tags=new HashSet<>();
and
#Table
#Entity(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id", unique = true, nullable = false)
private int id;
#Column(name = "name", unique = false, nullable = false)
private String name;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
private Set<Message> messages;
When trying to save new Message, I got exception saying: "detached entity to persist...Tag". I got it to work by setting CascadeType.MERGE, but I don't understand why it is working. It would be great if someone can explain me why :)
Steps I did which lead to exception:
In db I already had two Tags objects and no Messages. I also had connecting empty table messages_tags
On frontend (Android) I create new Message object (without id), add one Tag (entire object, pulled from db, with id) to Message.
Send new Message to backend (using Retrofit). Hit my controller function, then service function in which I tried to save new Message with accompanying child Tags. At first, my cascading type annotation on both side, was like this:
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
I thought, since I have one part of relationship covered, that I need to cover other one as well. So I did this:
newMessage.getTags().forEach(t -> t.getMessages().add(newMessage));
messageRepository.save(newMessage) //Bum! exception
I commented out that line for setting other part of relationship, set MERGE as cascading type and save simply worked. WHY? Are there any other consequences I may experience while doing other CRUD operations on any of these entities?
When you add a new Tag to the Message on the frontend, you have a different persistent context from the one used on backend. That's why the tag entity is seen as detached (it has a PK but it is not in the backend's persistent context). Since you did not specify a cascade type for JPA to know that to do with Tag instance, the persist of the Message instance fails.
Once you specify the cascade type MERGE, the Tag instance is merged into the backend's persistent context and the save succeeds.
You can avoid the using of MERGE cascade by saving first the Tag instance.

Delete orphans when deleting parent manyToOne annotaion

I have two entities, related as below
#Entity
#Table(name = "APPOINTMENT")
public class Appointment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long codeAp;
#ManyToOne(fetch = FetchType.EAGER)
, #OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "codeP")
private Patient patient;
//attributes
//getters and setters
//constructors
#Entity
#Table(name = "PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long codeP;
//attributes
//getters and setters
//constructors
I'm using JpaRepository delete method.
There is a constraint between the tables PATIENT and APPOINTMENT in database,
I want to remove orphans, when I remove Patient.
I added #OnDelete hibernate annotation but it doesn't work for me!
Can you please tell me why?
I want to keep that unidirectional relationship, can you please help me in this?
If you want to keep using the association as unidirectional only, you can define the lazy-loaded inverse side in a field without exposing getters and setters for it:
#Entity
public class Patient {
#OneToMany(mappedBy = "patient", orphanRemoval = true)
private Collection<Appointment> appointments;
}
This way orphanRemoval logic is applied from patients to their appointments and as a bonus you get the ability to navigate from patients to appointments in HQL queries.
Notice the mappedBy attribute which tells that appointments are responsible for the association management, so you continue to associate appointments with patients by setting patients in the many-to-one relation defined in the Appointment.
There is no way that you could achieve that automatic behavior on the #ManyToOne side. Its just semantically incorrect, period.
Taking under consideration though, the fact that you only want to have an uni-directional mapping and do not specify the Set<Appointment> dependency on the Patient, then a kind of workaround to your situation would be to replace the #ManyToOne with a #OneToOne relationship. Then you would be able to use orphan-removal functionality:
#OneToOne(fetch = FetchType.EAGER, orphanRemoval=true)
#JoinColumn(name = "codeP")
private Patient patient;
Keep in mind though that if you follow this path, adapt you code and at some point you will be in need to introduce #OneToMany dependency on the `Patient' side then you will stumble upon problems. So i would recommend working out pros and cons first in relation to future possible alteration to the entity graph.

Unwanted behavior – Hibernate deletes child elements annotated with #ElementCollection when parent element is updated

everyone.
I am have Customer and Service tables in one to many relation - one customer can have no or many services. The Service table has a customer_id column which is a foreign key referencing the primary key of the Customer table.
When retrieving customers from the database I need to get only the IDs of the related services and that is why I decided to use the #ElementCollection annotation.
My mapping for the Customer is as follows:
#Entity
#Table(name = "customer")
public class CustomerEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false)
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "service", joinColumns = #JoinColumn(name = "customer_id", updatable = false, insertable = true))
#Column(name = "id", updatable = false, insertable = true)
private Set<Integer> serviceIds;
}
Everything works perfect when I get data from the database. The problem is when I try to update a customer in the database. Upon update Hibernate deletes all the Service table rows which reference the updated customer if the serviceIds member of the customer entity was set to null or an empty Set. I would like to avoid this behavior. I would like this serviceIds member to be read only for Hibernate and to be ignored when the customer is updated in the database - i.e. I want to update only the customer table and nothing else. Is it possible to achieve this using ElementCollection?
By the way I tried the following mapping and the update of the Customer does not lead to any deletions in the Service table even if I set the serviceIds to null or an empty Set.
#OneToMany
#JoinColumn(name = "customer_id", referencedColumnName = "id", updatable = false, insertable = false)
private Set<ServiceEntity> serviceIds;
Thank You for Your help.
When modifying - even - a single element in an #ElementCollection, Hibernate will delete the old collection (the one persisted in DB) and then insert the new image of the collection. The question was already asked here:
Hibernate - #ElementCollection - Strange delete/insert behavior
In order to avoid this problem you can:
Use a List of Integer with the #OrderColumn annotation as described in the above link.
Create an Entity class wrapping your Integer (+ #Generated #Id).
Best regards

Categories