I came across a part of code in my project where getPersistenceManager().refresh(entity); is used. When I checked the Hibernate queries that get fired. There are few other queries which get fired and are child elements of the entity to be refreshed.
Why are those queries fired? Why not queries for all child entities fired?
EDIT:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "entity", cascade = { CascadeType.PERSIST,
CascadeType.REMOVE, CascadeType.REFRESH })
#JoinColumn(name = "ENTITY_TABLE", nullable = false)
private List<Employee> employee; // This is an example. Actual code resembles this example.
As you have failed to post any code it is difficult to say, however the most likely explanation is that the relevant cascade options are not defined on the association.
http://docs.oracle.com/javaee/7/api/javax/persistence/CascadeType.html#REFRESH
#Entity
public class Parent{
// on of the following would have to be defined
//#OneToMany(cascade = CascadeType.ALL)
#OneToMany(cascade = CascadeType.REFRESH)
public Set<Child> children;
}
Related
I´m creating my first Spring Boot application with the Java Persistence API to write to and read from a postgres database. I´ve looked through many tutorials and posts to figure out my exact problem and it seems like I currently have a bidirectional one-to-many relationship with two entities (Parent and Child), but the foreign-key of the child column is always null when I write to the database.
ParentEntity:
#Entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "tb_parent")
public class Parent {
#Schema(description = "id of the parent")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Schema(description = "child-list of the application")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval = true)
private Set<Child> children;
}
ChildEntity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "tb_child")
public class Child{
#Schema(description = "id of the child")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToOne(targetEntity = Parent.class, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = true, insertable = true)
private Parent parent;
}
ParentService:
...
#Override
public Parent create(Parent parent) {
log.info("save new parent: {}", parent);
return parentRepo.save(parent); // the repo is an interface that extends the JpaRepository
}
...
After invoking the create method, I have a parent row in the tb_parent with a generated id and one or more child rows in the tb_child with a generated id and a null value for the parent_id column.
Even though I´m able to find lots of posts describing a similar issue, I wasn´t yet able to find a solution that works for me.
Update #1:
A common suggestion is to manually set the parent object in all child elements. However, this results in a Stackoverflow Exception due to the circular structure.
public void setChildren(Set<Child> children) {
children.forEach(child -> child.setParent(this));
this.children = children;
}
Additionally, it kinda feels off because almost everything is managed automatically by the JPA Annotations and then you have to manually sync the data.
Thanks to Georgy Lvov I was able to find the most effective solution. I had to do the following steps:
remove the #Data annotation as suggested by Georgy Lvov (Thank you so much!) This basically avoids the generated getter and setter methods for all attributes and the methods equals(), hashCode(), and toString() by Lombok which caused the Stackoverflow Exception.
annotate the Set<Child> children; variable in the Parent class with the #JsonManagedReference annotation. see below:
#JsonManagedReference
#Schema(description = "child-list of the application")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval = true)
private Set<Child> children;
annotate the private Parent parent; in the Child class with the #JsonBackReference annotation. see below:
#JsonBackReference
#ManyToOne(targetEntity = Parent.class, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = true, insertable = true)
private Parent parent;
The #JsonBackReference also seems to avoid the circular structure when you create an openapi.json file.
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
I am attempting to remove entries from a many to many relationship using Spring Data JPA. One of the models is the owner of the relationship and I need to remove entries of the non-owner entity. These are the models:
Workflow entity
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "dataUploads")
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
DataUpload repository
#Repository
public interface DataUploadsRepository extends JpaRepository<DataUpload, UUID> {
#Transactional
void delete(DataUpload dataUpload);
Optional<DataUpload> findByDataUploadId(UUID dataUploadId);
}
To delete data uploads, I am trying to execute a couple of query methods of the repository:
First version
dataUploadsRepository.deleteAll(workflow.getDataUploads());
Second version
workflow.getDataUploads().stream()
.map(DataUpload::getDataUploadId)
.map(dataUploadsRepository::findByDataUploadId)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(dataUploadsRepository::delete);
Problem is that Spring Data JPA is not removing DataUploads nor entries of the association table workflow_data.
How can I tell Spring Data to remove from both data_uploads and workflow_data (association table)?
I would appreciate any help.
I found the solution for this problem. Basically, both entities (in my case) need to be the owner of the relationship and the data from the association table must be deleted first.
Workflow entity (relationship owner)
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity (relationship owner)
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"),
inverseJoinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"))
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
Notice that Workflow has ALL as cascade type, since (based on the logic I need), I want Spring Data JPA to remove, merge, refresh, persist and detach DataUploads when modifying workflows. On the other hand, DataUpload does not have cascade type, as I do not want Workflow instances (and records) to be affected due to DataUploads deletions.
In order to successfully delete DataUploads, the associate data should be deleted first:
public void deleteDataUploads(Workflow workflow) {
for (Iterator<DataUpload> dataUploadIterator = workflow.getDataUploads().iterator(); dataUploadIterator.hasNext();) {
DataUpload dataUploadEntry = dataUploadIterator.next();
dataUploadIterator.remove();
dataUploadsRepository.delete(dataUploadEntry);
}
}
dataUploadIterator.remove() deletes records from the association table (workflow_data) and then the DataUpload is deleted with dataUploadRepository.delete(dataUploadEntry);.
It has been a while since I have to fix these kind of mappings so I'm not going to give you a code fix, instead maybe give you another perspective.
First some questions like, do you really need a many to many? does it make sense that any of those entities exist without the other one? Can a DataUpload exist by itself?
In these mappings you are supposed to unassign the relationships on both sides, and keep in mind that you could always execute a query to remove the actual values (a query against the entity and the intermediate as well)
A couple of links that I hope can be useful to you, they explain the mappings best practices and different ways to do the deletion.
Delete Many, Delete Many to Many, Best way to use many to many.
I have Many To Many with additional column. Here realization(getters/setters generated by lombok, removed for clarity):
public class User extends BaseEntity {
#OneToMany(mappedBy = "user",
orphanRemoval = true,
fetch = FetchType.LAZY
cascade = CascadeType.ALL,)
private List<UserEvent> attendeeEvents = new ArrayList<>();
}
#Table(
name = "UC_USER_EVENT",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "event_id"})}
)
public class UserEvent extends BaseEntity {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_response_id")
private UserResponse userResponse;
}
public class Event extends BaseEntity {
#OneToMany(mappedBy = "event",
orphanRemoval = true,
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private List<UserEvent> userEvents = new ArrayList<>();
}
I want this - when i delete Event, All "UserEvents" connected to it should be removed. And if I delete User, all "UserEvents" should be removed too.
I delete my event(eventRepository is Spring Jpa interface):
eventRepository.delete(event);
Then retrieving UserEvents from db:
List<UserEvent> userEvents = userEventsId.stream()
.map(id -> entityManager.find(UserEvent.class, id)).collect(Collectors.toList());
And there is collection with 2 items(this is count of UserEvents), but they all "null".
I can't understand what happening and how to do it right.
I need them deleted and when I check collection there should be 0, instead of 2.
The delete says marked for deletion, please try calling flush after deletion, and then find.
I guess find goes to the database, find the two rows, but when trying to instantiate them, find the entities marked for deletion and then you have this strange behaviour.
Recomendation: try to abstract more from the database and use less annotations. Learn the conventions of names for columns and tables (if you need to) and let JPA do its job.
Could you help to persist a Map when String is not the key of the Entity mapped?
For example:
class A {
#Id
long id;
String code;
}
class B {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#What magical combination of JPA annotations should I use here?!
Map<String,A> mapAByCode;
}
I've tried a lot of combinations of {#JoinTable,#MapKeyColumn,#JoinColumn,#JoinTable} annotations with no success and I'm going crazy...
Thanks!
Since you seem to want to map your entities using the A.code value, #MapKey is what you are after. #MapKey allows you to define the value within the reference to use as the key for the map. As the javadoc states, it is required to be unique though or you will run into problems.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#MapKey(name="code")
Map<String,A> mapAByCode;
It seems that the problem is related to other map I'm using in the same class:
#Entity
class A {
#Id
long id;
String code;
}
#Entity
class B {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinTable(name = "B_MAPABYID", joinColumns = #JoinColumn(name = "B_ID"))
#MapKey(name="id")
Map<Long,A> mapAById;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinTable(name = "B_MAPABYCODE", joinColumns = #JoinColumn(name = "B_ID"))
#MapKey(name="code")
Map<String,A> mapAByCode;
}
This configuration is not working for me, but if I set mapAById as transient all works fine. Does it makes any sense for you?