I have entities "ZakladProdukcyjny" and "MiejsceProwadzeniaDzialnosci".
There is an unidirectional relation #OneToMany with a join table.
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "ZAKLAD_PRODUKCYJNY_MIEJSCE_PROWADZENIA_DZIALALNOSCI",
joinColumns = {
#JoinColumn(name = "zakladProdukcyjny_ID")},
inverseJoinColumns = {
#JoinColumn(name = "miejsceProwadzeniaDzialalnosci_ID")})
private List<MiejsceProwadzeniaDzialalnosci> miejscaProwadzeniaDzialalnosci = new ArrayList<>();
I am using Spring JPARepositories
public interface ZakladProdukcyjnyRepository extends JpaRepository<ZakladProdukcyjny, Long>,
Everytime i am saving the parent entity with zakladProdukcyjnyRepository.save(zakladProdukcyjny), children entities are being persised into DB so everytime save is executed on the JPARepository i am having duplicated entries.
The child entity uses a lombok for generating equals and hashcode.
#EqualsAndHashCode(callSuper=false)
public class MiejsceProwadzeniaDzialalnosci extends BaseEntity {
I have no idea what may be wrong here.
This should have beed fixed long time ago:
https://hibernate.atlassian.net/browse/HHH-5855
https://hibernate.atlassian.net/browse/HHH-6776
Try changing the List to a Set or remove CascadeType.ALL and leave just CascadeType.MERGE.
I have solved the problem. The issue was an equals functionality. Somewhere in the code i had:
for (MiejsceProwadzeniaDzialalnosci mpd : uaktualnioneMiejscaProwadzeniaDzialalnosciZBDO) {
if (!(zaklad.getMiejscaProwadzeniaDzialalnosci().contains(mpd))) {
zaklad.getMiejscaProwadzeniaDzialalnosci().add(mpd);
}
}
after ovveriding the equals method there is no duplicates.
Related
I have a ManyToMany relationship between Profile and ProfileExperience that is mapped as follows:
#ManyToMany
#JoinTable(name = "profile_experience_relations",
joinColumns = {
#JoinColumn(name = "profile_id")
},
inverseJoinColumns = {
#JoinColumn(name = "profile_experience_id")
})
private List<ProfileExperience> experiences;
I have added localization support inside of ProfileExperience, following this guide like so:
ProfileExperience Class
#OneToMany(mappedBy = "profileExperience", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
#MapKey(name = "localizedProfileExperiencePk.locale")
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
private Map<String, LocalizedProfileExperience> localizations = new HashMap<>();
LocalizedProfileExperience Class
#Entity
#Getter
#Setter
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class LocalizedProfileExperience {
#EmbeddedId
private LocalizedProfileExperiencePk localizedProfileExperiencePk;
#ManyToOne
#MapsId("id")
#JoinColumn(name = "profileExperienceId")
private ProfileExperience profileExperience;
private String value;
}
Composite PK Class
#Embeddable
#Getter
#Setter
public class LocalizedProfileExperiencePk implements Serializable {
private static final long serialVersionUID = 1L;
private String profileExperienceId;
private String locale;
public LocalizedProfileExperiencePk() {
}
Before adding the localization, there was no duplicate entries in the responses, however - everything retrieved is now duplicated.
I can solve the issue by using a Set, however I'm curious as to why this happened. What is the explanation? Can I solve it without using a set? Am I overlooking something incredibly simple?
The problem is that you are probably using join fetch or an entity graph to fetch nested collections. Now, when you look at the JDBC result set, you will see that there are many duplicate result set rows. If you have a profile with 2 profile experiences, and each has 3 localizations, you will see that you have 6 (2 * 3) duplicate rows. Theoretically, Hibernate could try to retain the expected object graph cardinality, but this is not so easy, especially when multiple collections are involved. Also, for certain collection mappings it would simply not be possible to do.
So the short answer to your problem is, never use a List unless duplicity matters to you. In this case, you will have an order column though, so even then it would be safe to use a list.
Implement the equal method of your data class. Hibernate need it.
In the context of a Spring Boot project using Spring Data JPA, I have defined the following entities:
Ent1 contains a list of Ent2 elements
Ent2 contains a list of Ent3 elements
When fetching a top-level Ent1 object through a repository, I'm seeing that every Ent2 which has more than one child appears multiple times in the Ent1.ent2 list. For example, an Ent2 with two childs will appear twice.
So instead of getting this:
I'm getting this:
Notes:
There are no duplicates in the database
If I delete ent3b in the database, the duplicated ent2 disappears
Here's a simplified version of the code:
```java
#Entity
public class Ent1 {
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent2> ent2 = new ArrayList<Ent2>();
}
#Entity
public class Ent2 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent1 parent;
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent3> ent3 = new ArrayList<Ent3>();
}
#Entity
public class Ent3 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent2 parent;
}
```
Solution was to convert Lists into Sets. Lists in JPA require additional data (i.e. an ordering column) to extract a total ordering of elements from the relationship. It can be done but typically the average user only needs Set and it's a reflection of the relationship that most people model.
OP also commented that the previous provider didn't have this requirement so if you were previously using EclipseLink and switching ORM providers this may be a problem for you too.
We are using Spring Data repositories with Hibernate 5.x
We have a entity graph with a deep hierarchy.
The mapping looks like this:
#Entity
public class FooBar {
#OneToMany(mappedBy = "fooBar", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
...
}
#Entity
public class Foo {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foobar_id")
private FooBar fooBar;
#OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bar> chassis = new HashSet<>(0);
...
}
#Entity
public class Bar {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foo_id")
private FooBar foo;
...
}
As you can see the FooBar entity has a set of Foo entities. Each Foo entity contains more Bar entities and so on.
We use the Fetchgraph feature to load the FooBar entity with the relations we need during runtime to avoid n+1 query issue when fetching lazy associations.
After the service call to load the entity graph the transaction has ended and the entity is detached.
When calling save on the FooBar entity at a later time, this causes multiple select statements. Each fetching one of the child entities.
I know that this comes from the entitymanager merge() call which fetches the object graph from the db before copying state changes from the detached objects.
I have two questions:
Why is hibernate not able to join these statements to one big select like what happens when using the fetchgraph?
When i remove all cascade options from the relations it still causes multiple selects but only attributes of the top, FooBar entity, will be updated. Why is hibernate still fetching all loaded child entites during merge even with no cascade merge?
Thanks
You can use session.update instead of merge to overcome this issue.
Session session = entityManager.unwrap(Session.class);
for (Post post: posts) {
session.update(post);
}
I have similar issue with your case, and the reason is the setting of cascading CascadeType.ALL on the #OneToMany association. Updating and merging the parent entity cause a lot of select on the child association.
#Entity
public class FooBar {
#OneToMany(mappedBy = "fooBar", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
...
}
I fix my case by reducing the scope of cascading, only PERSIST and REMOVE is sufficient
#OneToMany(mappedBy = "fooBar", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
I have a situation where an entity could use another entity, and it could be used by another, so i have defined a ManyToMany relation that reference the same entity, so i could have listUse and listUsedBy, and both are persisted in the same table entity_usage :
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = {
#JoinColumn(name = "id_use", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "id_used_by", referencedColumnName = "id")})
private List<Entity> listUse;
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = {
#JoinColumn(name = "id_use_by", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "id_use", referencedColumnName = "id")})
private List<Entity> listUsedBy;
Exemple : Entity A could use Entity B and C, so Entity B and C are used by A.
Now my problem is when i add B and C to listUse, they are persisted in entity_usage, but when try to display listUsedBy i have to redeploy my project, otherwise listUsedBy remains empty, is there a way to refresh listUsedBy when persist my entity without having to redeploy my project.
This is the general approach:
#Entity
public class SomeEntity
{
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = #JoinColumn(name = "using_id"),
inverseJoinColumns = #JoinColumn(name = "used_by_id"))
private Set<SomeEntity> using = new LinkedHashSet<>();
#ManyToMany(mappedBy = "using")
private Set<SomeEntity> usedBy = new LinkedHashSet<>();
public void addUsing(SomeEntity entity)
{
this.using.add(entity);
entity.usedBy.add(this);
}
public void addUsedBy(SomeEntity entity)
{
this.usedBy.add(entity);
entity.using.add(this);
}
}
and it's used:
public void someMethod(long parentEntityId, long childEntityId)
{
EntityManager em = getSomeEntityManager();
SomeEntity parentEntity = em.find(SomeEntity.class, parentEntityId);
SomeEntity childEntity = em.find(SomeEntity.class, childEntityId);
parentEntity.addUsing(childEntity);
}
typically this is a transactional EJB method.
Note that there's no need to em.merge anything, since entities are already managed by em.find.
Anyway, whichever method you'll use to manage your entities (query, find, persist, merge), remember that's important to call addUsing/addUsedBy only when both entities are managed.
This is one of the main incoherences that ORM logic cannot handle by its own: you have to inform both entities (parent and child) of their relation. It's not sufficient to set the relation only on one side - if you only say that A is parent of B, B still doesn't know who is its parent.
However, there exists alternative approaches, like setting only the owning side of the relation (parent.getChildren().add(child)), flush, and refresh the child.
Nevertheless (as I experienced very well on my skin) the alternatives are very hard to handle in real world complex applications.
As a side note, I'd use Set instead of List for the relation, unless you need some kind of insertion-order.
Im kinda new to JPA, My question is, if I have the following parent- child relationship between two entities. with this setting(as show below), is it okay to delete a child using just a named query ("delete from child where parent.id:id) and then not remove the from the parent children collection? I have tested this approach of just using named query and not deleting the children from the parent collection and it works just fine, but im trying to see if there are any major impacts when i delete them this way. The reason why im not removing them to the collection objects is because, Children is set to have NOT nullable field parent id. Thank you very much, and I look forward for your answers :)
public class Parent {
ID.....
parentName...
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL,
fetch = FetchType.EAGER, orphanRemoval = true)
private List<Child> children;
}
public class Child {
id;
#ManyToOne(optional = false)
#JoinColumns({
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID", nullable = false)
})
private Parent parent;
}
Looks like it's perfectly OK to delete objects like this. orphanRemoval manages the rest for you.