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.
Related
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.
I am developing an REST API to a pizzeria store. And here i'm trying to delete a Flavor and all data related to it. Further explained below:
Classes:
Flavor have at least one Filling, each one taking a position on it.
i.e: Souce (at pos. 1), mozzarela (at pos. 2) tomato (at pos. 3)
Flavors must have a price to each Size
With that in mind, we can conclude that exist two many-to-many relationships:
Flavor to many Filling
Flavor to many Size
Class diagram of actual implementation
The requirement is to: delete a Flavor, and automatically delete all the FillingPositionFlavor and FlavorPriceSize.
But,I'm confused on use of CascadeType.REMOVE and orphanRemoval = true:
When I use Cascade and OrphanRemoval on Flavor.sizePrices, get a HibernateException when trying to edit a Flavor, exclusion works fine:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.pkg.Flavor.sizePrices
When I use Cascade on Flavor.sizePrices, get a PSQLException when excluding a Flavor, editing works fine:
ERROR: update or delete on table "tb_flavor" violates foreign key constraint "fk9orw0yhtc0e06ka84dbcd2c82" on table "tb_flavor_size_price"
I'm doing unit testing of services in Spring Boot to test all the CRUD operations.
Below is the actual code, I hid properties like id and others to facilitate the read.
#Entity
#Table(name = "tb_flavor")
class Flavor {
#OneToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPositionFilling> flavors = new HashSet<FlavorPositionFilling>();
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPriceSize> priceSizes;
// other properties and methods
}
#Entity
#Table(name = "tb_flavor_price_size")
class FlavorPriceSize {
#EmbeddedId
private FlavorPriceSizeEmbeddeId id;
private float price;
// other properties and methods
}
#Embeddable
class FlavorPriceSizeEmbeddeId implements Serializable {
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_FLAVOR_FK", referencedColumnName = "id_flavor")
private Flavor flavor;
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_SIZE_FK", referencedColumnName = "id_size")
private Size size;
}
#Entity
#Table(name = "tb_flabor_position_filling")
class FlaborPositionFilling {
#EmbeddedId
private FlaborPositionFillingEmbeddedId id;
private Integer position;
}
#Embeddable
class FlaborPositionFillingEmbeddedId implements Serializable {
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name="ID_FLAVOR_FK", referencedColumnName="id_flavor")
private Flavor sabor;
#ManyToOne()
#JoinColumn(name="ID_FILLING_FK", referencedColumnName="id_filling")
private Filling filling;
}
I've read a lot about both, but still not understand the right use of each and their effect on operations. Can anyone explain it to me? Show videos, images, code...
Let's assume that you have a parent -> child relationship.
If you set CacadeType.REMOVE on the relationship every EntityManager.remove call on the parent will also remove the children.
orphanRemoval = true is used to delete orphan children.
So if remove a child from the parent reference or collection and save the parent the child will be deleted because its no longer attached to the parent.
Posting this here as I wasn't seeing much interest here: http://www.java-forums.org/jpa/96175-openjpa-one-many-within-one-many-merge-problems.html
Trying to figure out if this is a problem with OpenJPA or something I may be doing wrong...
I'm facing a problem when trying to use OpenJPA to update an Entity that contains a One to Many relationship to another Entity, that has a One to Many relationship to another. Here's a quick example of what I'm talking about:
#Entity
#Table(name = "school")
public class School {
#Column(name = "id")
protected Long id;
#Column(name = "name")
protected String name;
#OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
protected Collection<ClassRoom> classRooms;
}
#Entity
#Table(name = "classroom")
public class ClassRoom {
#Column(name = "id")
protected Long id;
#Column(name = "room_number")
protected String roomNumber;
#ManyToOne
#JoinColumn(name = "school_id")
protected School school;
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Desk> desks;
}
#Entity
#Table(name = "desk")
public class Desk {
#Column(name = "id")
protected Long id;
#ManyToOne
#JoinColumn(name = "classroom_id")
protected ClassRoom classRoom;
}
In the SchoolService class, I have the following update method:
#Transactional
public void update(School school) {
em.merge(school);
}
I'm trying to remove a Class Room from the School. I remove it from the classRooms collection and call update. I'm noticing if the Class Room has no desks, there are no issues. But if the Class Room has desks, it throws a constraint error as it seems to try to delete the Class Room first, then the Desks. (There is a foreign key constraint for the classroom_id column)
Am I going about this the wrong way? Is there some setting I'm missing to get it to delete the interior "Desk" instances first before deleting the Class Room instance that was removed?
Any help would be appreciated. If you need any more info, please just let me know.
Thanks,
There are various bug reports around FK violations in OpenJPA when cascading remove operations to child entities:
The OpenJPA FAQ notes that the following:
http://openjpa.apache.org/faq.html#reorder
Can OpenJPA reorder SQL statements to satisfy database foreign key
constraints?
Yes. OpenJPA can reorder and/or batch the SQL statements using
different configurable strategies. The default strategy is capable of
reordering the SQL statements to satisfy foreign key constraints.
However ,you must tell OpenJPA to read the existing foreign key
information from the database schema:
It would seem you can force the correct ordering of the statements by either setting the following property in your OpenJPA config
<property name="openjpa.jdbc.SchemaFactory"> value="native(ForeignKeys=true)"/>
or by adding the org.apache.openjpa.persistence.jdbc.ForeignKey annotation to the mapping:
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#org.apache.openjpa.persistence.jdbc.ForeignKey
protected Collection<Desk> desks;
See also:
https://issues.apache.org/jira/browse/OPENJPA-1936
I would like to know the difference between #JsonManagedReference and #JsonBackReference in Jackson?
#JsonManagedReference is the forward part of reference – the one that
gets serialized normally. #JsonBackReference is the back part of
reference – it will be omitted from serialization.
So they really depend on the direction of your relationship
public class User {
public int id;
public String name;
#JsonBackReference
public List<Item> userItems;
}
public class Item {
public int id;
public String itemName;
#JsonManagedReference
public User owner;
}
#JsonManagedReference -> Manages the forward part of the reference and the fields marked by this annotation are the ones that get Serialised
#JsonBackReference -> Manages the reverse part of the reference and the fields/collections marked with this annotation are not serialised.
Use case:
You have a one-many or many-many relationships in your entities/tables and not using the above would lead to errors like
Infinite Recursion and hence stackoverflow - > Could not write content: Infinite recursion (StackOverflowError)
The above errors occurs because Jackson (or someother similiar) tries to serialise both ends of the relationship and ends up in a recursion.
#JsonIgnore performs similiar functions but the above mentioned annotations are preferable.
As write Rajat Verma, his solution works perfectly. Thanks man you saved me lot of time and anger :-)
The important Part:
You need define fields as List, I had that as Set before and this solution NOT WORKING (appears as infinite loop)!
I add my solution:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Agent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "subscribers")
#ApiModelProperty(dataType = "List", example = "[1,2,3]") // for Swagger
#JsonIdentityReference(alwaysAsId = true) // show only id of Topic
private final List<Topic> subscribeTopics = new ArrayList<>()
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Topic {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "topic_agent",
joinColumns = #JoinColumn(name = "fk_topic_id"),
inverseJoinColumns = #JoinColumn(name = "fk_agent_id"))
#ApiModelProperty(dataType = "List", example = "[1,2,3]")
#JsonIdentityReference(alwaysAsId = true)
private final List<Agent> subscribers = new ArrayList<>();
}
#JsonManagedReference and #JsonBackReference are designed to handle this two-way linkage between fields, one for Parent role, the other for Child role.
For avoiding the problem, linkage is handled such that the property
annotated with #JsonManagedReference annotation is handled normally
(serialized normally, no special handling for deserialization) and the
property annotated with #JsonBackReference annotation is not
serialized; and during deserialization, its value is set to instance
that has the "managed" (forward) link.
Effective Java, Item 32, states Use EnumSet instead of bit fields. I also found this nice tutorial on the topic. This book has been around for a while, so why don't I find any posts on how to persist an EnumSet with Hibernate? Well, I actually found this one, and another one, but they are both quite old, point to the same and much older solution, which unfortunately did not help me, perhaps because of my lack of deeper hibernate knowledge? Here is an abstract of my code:
public class MyThing {
public enum MyOptions {
FLAG1, FLAG2
}
#Id
private Long id;
#Column(name = "options")
private EnumSet<MyOptions> options;
// [other fields, getters, setters etc]
}
I've tried other annotations like
#ElementCollection
with and without (targetClass = MyOptions.class)
and
#JoinTable(name = "my_options",
joinColumns = #JoinColumn(name = "id"))
and also
#OneToMany
#JoinColumn(name = "options")
but with no luck.
Preferably, I'd store the information in a new column of the my_thing table, but I could also live with a separate table for the enums, if required.
Try this
#ElementCollection
#CollectionTable(name = "my_options",
joinColumns = #JoinColumn( name = "mything_id"))
#Column(name = "option")
#Enumerated(EnumType.STRING)
private Set<MyOptions> options;
With this configuration, you need a database table named my_options with columns option and mything_id which targets MyThing table.