JPA Multiple Merges Causing Duplicate Records - java

Issue
I have a one to many parent child relationship but when I merge the parent object it is creating a duplicate set of child records.
Details
This is a follow on from JPA OneToMany Only Updates First Time
I am using Eclipse Link and following on from this I now have two classes that look like
#Entity
public class Foo {
#Id
#Column(name="FOO_ID)
private int id;
#Column(name="FOO_NAME")
private String name;
#OneToMany(mappedBy="foo", cascade = CascadeType.ALL)
private List<Bar> bars;
public void addBar(Bar b) {
b.setFoo(this);
bars.add(b);
}
}
#Entity
public class Bar {
#Id
#Column(name="BAR_ID")
private it id;
#Column(name="BAR_NAME")
private String name;
#ManyToOne
#JoinColumn(name="FOO_ID")
private Foo foo;
}
and then I have
public void processBars(Foo foo) {
for (MyListItem i : myList) {
Bar bar = new Bar();
bar.setName("Test");
foo.addBar(bar);
}
entityMgr.merge(foo);
}
and finally
processBars(foo);
if(someCodition) {
foo.setStatus("xxx");
entityMgr.merge(foo);
}
This second merge is then creating a second set of Bars
Sequence, Id and EqualsAndHashCode
I am guessing this is down to the use of database sequences for the ID fields. When Bar is created the ID is obviously null and then for some reason it is checking again after the record has been inserted and deciding it is a different object. I am using Lombok and have added #EqualsAndHashCode annotations to both classes excluding the list of children in 'Foo'
Update
Reworked question as I figured out it was the second merge that is causing the problems

JPA's merge api takes the object passed in and merges it into the context. This is different from persist, which takes the instance passed in and makes it managed - merge will return the managed instance.
Entity e = new Entity();
Entity e1 = em.merge(e);
assertTrue(e1!=e);// they are different instances
when the transaction is flushed/committed, e1 will have its ID set because it was managed by the persistence unit, but 'e' will not. So when you call em.merge(e), you are giving it a blank instance, forcing in duplicates.
Simple solution is to return the resulting Foo from your processBars method and use it for your subsequent changes and merge calls.

Related

Spring Data JPA - bidirectional relation with infinite recursion

First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.

How to know the ID (#GeneratedValue) of a POJO at runtime

I have a form to fill a POJO called Father. Inside it, I have a FotoFather field.
When I save a new Father, I save automatically the object FotoFather (with Hibernate ORM pattern).
FotoFather.fotoNaturalUrl must be filled with the value of Father.id and here is the problem!
When i'm saving Father on the db, of course I still haven't Father.id value to fill FotoFather.fotoNaturalUrl. How can I solve this problem?
Thank you
#Entity
#Table(name = "father")
public class Father implements Serializable{
...
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
#OneToOne(targetEntity = FotoFather.class, fetch = FetchType.EAGER)
#JoinColumn(name = "fotoFather", referencedColumnName = "id")
#Cascade(CascadeType.ALL)
private FotoFather fotoFather;
}
FotoFather.class
#Entity
#Table(name = "foto_father")
public class FotoFather.class{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
#Column(name = "foto_natural_url")
private String fotoNaturalUrl;
...
}
If you simply need the complete URL for some application-specific purpose, I would likely err on the side of not trying to store the URL with the ID at all and instead rely on a transient method.
public class FotoFather {
#Transient
public String getNaturalUrl() {
if(fotoNaturalUrl != null && fotoNaturalUrl.trim().length > 0) {
return String.format("%s?id=%d", fotoNaturalUrl, id);
}
return "";
}
}
In fact, decomposing your URLs even more into their minimalist variable components and only storing those in separate columns can go along way in technical debt, particularly if the URL changes. This way the base URL could be application-configurable and the variable aspects that control the final URL endpoint are all you store.
But if you must know the ID ahead of time (or as in a recent case of mine, keep identifiers sequential without loosing a single value), you need to approach this where FotoFather identifiers are generated prior to persisting the entity, thus they are not #GeneratedValues.
In order to avoid issues with collisions at insertion, we have a sequence service class that exposes support for fetching the next sequence value by name. The sequence table row is locked at read and updated at commit time. This prevents multiple sessions from concurrency issues with the same sequence, prevents gaps in the range and allows for knowing identifiers ahead of time.
#Transactional
public void save(Father father) {
Assert.isNotNull(father, "Father cannot be null.");
Assert.isNotNull(father.getFotoFather(), "FotoFather cannot be null.");
if(father.getFotoFather().getId() == null) {
// joins existing transaction or errors if one doesn't exist
// when sequenceService is invoked.
Long id = sequenceService.getNextSequence("FOTOFATHER");
// updates the fotofather's id
father.getFotoFather().setId(id);
}
// save.
fatherRepository.save(father);
}
I think you can do be registering an #PostPersist callback on your Father class. As the JPA spec notes:
The PostPersist and PostRemove callback methods are invoked for an
entity after the entity has been made persistent or removed. These
callbacks will also be invoked on all entities to which these
operations are cascaded. The PostPersist and PostRemove methods will
be invoked after the database insert and delete operations
respectively. These database operations may occur directly after the
persist, merge, or remove operations have been invoked or they may
occur directly after a flush operation has occurred (which may be at
the end of the transaction). Generated primary key values are
available in the PostPersist method.
So, the callback should be called immediately after the Father instance is written to the database and before the FotoFather instance is written.
public class Father(){
#PostPersist
public void updateFotoFather(){
fotofather.setNaturalUrl("/xyz/" + id);
}
}

Delete a record on an OneToMany association with Hibernate

I am working on an application using Hibernate and I want to delete some records in the database. The relevant Entities are:
#Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
#Id
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
#OneToMany
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
...
}
#Entity
public class Part implements Serializable {
#Id
#GeneratedValue
private Long part_id;
private String userCode = "";
//getters and setters
....
}
I have let Eclipse implement equals and hashCode in Entity Part based on part_id and userCode. There is also an Entity Factory from which 'begin' all the associations to the other Entities. Therefore, in order to save all the changes it only necessary to execute the comand:
session.update(factory);
All the changes are saved successfully except from the delete from parts. I do:
products.getParts.remove(part);
The issues comig out are:
1) In some cases is part from the Set not removed although the comparison to a part in the Set with equals true returns (the part is in Set according to equals but it is not removed)
2) Even if the remove in the Set succeeds, the record in the database is not deleted.
Based on the above ascertainments what is the best way to remove the records in this case using not loads of queries?
You need to explicitly remove the child:
session.delete(part);
From Hibernate Docs:
The following code:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();
will not remove c from the database. In this case, it will only remove
the link to p and cause a NOT NULL constraint violation. You need to
explicitly delete() the Child.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
When using hibernate to map relationships you must be aware of two main concerns:
Which is the owner of the relationship? The owner is the side of the relation whose changes will be persisted in database. In your case the owner is the Part object.
Is a true parent/child relationship or simply a composition relationship? In your case I think the answer is composition
If you want to manage the relation using the set, you have two options:
use #ElementCollection instead of #OnetoMany
change ownership. Something like this:
#OneToMany
#JoinColumn(name="part_id")
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
However, the second option is not recommended here. See section 2.2.5.3.1.2.

JPA OneToMany - Collection is null

I'm trying to set up a bidirectional relationship using JPA. I understand that it's the responsability of the application to maintain both sides of the relationship.
For example, a Library has multiple Books. In the Library-entity I have:
#Entity
public class Library {
..
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL)
private Collection<Book> books;
public void addBook(Book b) {
this.books.add(b);
if(b.getLibrary() != this)
b.setLibrary(this);
}
..
}
The Book-entity is:
#Entity
public class Book {
..
#ManyToOne
#JoinColumn(name = "LibraryId")
private Library library;
public void setLibrary(Library l) {
this.library = l;
if(!this.library.getBooks().contains(this))
this.library.getBooks().add(this);
}
..
}
Unfortunately, the collection at the OneToMany-side is null. So for example a call to setLibrary() fails because this.library.getBooks().contains(this) results in a NullPointerException.
Is this normal behavior? Should I instantiate the collection myself (which seems a bit strange), or are there other solutions?
Entities are Java objects. The basic rules of Java aren't changed just because there is an #Entity annotation on the class.
So, if you instantiate an object and its constructor doesn't initialize one of the fields, this field is initialized to null.
Yes, it's your responsibility to make sure that the constructor initializes the collection, or that all the methods deal with the nullability of the field.
If you get an instance of this entity from the database (using em.find(), a query, or by navigating through associations of attached entities), the collection will never be null, because JPA will always initialize the collection.
It seems that books type of Collection in Library class is not initilized. It is null;
So when class addBook method to add a book object to collection. It cause NullPointerException.
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL)
private Collection<Book> books;
public void addBook(Book b) {
this.books.add(b);
if(b.getLibrary() != this)
b.setLibrary(this);
}
Initilize it and have a try.
Change
private Collection<Book> books;
To
private Collection<Book> books = new ArrayList<Book>();
Try to set the fetch type association property to eager on the OneToMany side. Indeed, you may leave this part (this.library.getBooks().add(this)) to be written within a session:
Library l = new Library();
Book b = new Book();
b.setLibrary(l);
l.getBooks().add(b);

JPA: Setting #JoinColumn(updatable = false) to avoid OptimisticLockException

Given the following the following two entities
#Entity
public class A {
#Version
protected int version;
String basicPropertey;
// getter and setter for basicProperty
}
#Entity
public class B {
#Version
protected int version;
#ManyToOne
private A a;
public B(A a) {
this.a = a}
}
//getter for a
}
Two questions:
Is there version number of the entity A increased every time the
entity B is merged to the DB (note that there is no
CascadeType.MERGE defined on the relationship to A) and, thus, cause
a possible OptimisticcLockException when A is merged with a
different version number?
If yes, would it help to avoid the likelihood of an
OptimisticLockException if I add #JoinColumn(updatable=false) on the
relationship?
My situation is that entity B is updated very frequently and A sporadically, but only its basic attributes (not the relationship) and I'm getting an OptimisticLockException on the entity A.
If you only change B, then you cannot get a lock error on A.
You will only get a lock error on B.
Check you SQL log to what you are actually doing.

Categories

Resources