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);
Related
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.
I'm implementing a Spring boot application and using Spring Data JPA in it. As you know you don't have to implement the repository interface for just CRUD methods, because Spring Data JPA creates an implementation on the fly. So I have just this:
public interface PersonRepository extends JpaRepository<Person, Long> {}
I'm working with one-to-many relationship, this is in my Person domain:
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY,
mappedBy = "person")
private Set<Contact> contacts = new HashSet<>();
I decided to write an integration test for child removal from the parent:
#Test
public void removeFromContacts() {
// given
Person person = new Person ("test person");
Contact contact = new Contact("test#gmail.com", "+123456789");
contact.setPerson(person);
person.getContacts().add(contact);
personRepository.save(person);
Person savedPerson = personRepository.findOne(person.getId());
Contact persistedContact = savedPerson.getContacts().stream().findFirst().orElse(null);
// when
savedPerson.getContacts().remove(persistedContact);
persistedContact.setPerson(null);
Person edited = personRepository.save(savedPerson);
// then
Assert.assertTrue(edited.getContacts().isEmpty());
}
This test fails. The reason is savedPerson.getContacts().remove(persistedContact) line doesn't change anything, remove method returns false. It's pretty strange, because I'm trying to remove an object from a hash set which has only one object with exact same hash code (equals() method returns true as well). According to this answer the contact object could've been altered somehow after adding it to the hash set. The only thing I can think of is it happened after this line: personRepository.save(person).
If I'm right then I'm really confused: how should I remove the contact from a person, and even if I find a way, is it okay for personRepository.save method to cause a set to malfunction? And if I'm wrong I would love to know the right answer.
Thanks in advance.
Class Compte and Class User joind to one-to-one relationship
public void delete(Integer integer){
User user = userRepository.findOne(integer);
Compte compte = user.getCompte();
compte.setUser(null);
compteRepository.save(compte);
user.setCompte(null);
userRepository.save(user);
compteRepository.delete(compte);
userRepository.delete(user);
}
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.
I am working on a Spring-MVC appplication in which I have 3 classes, GroupCanvas, GroupSection, GroupNotes. GroupCanvas has one-to-many mapping with GroupSection and GroupSection has one-to-many mapping with GroupNotes. I am trying to retrieve notes based upon GroupCanvas's primary key, but I am getting a Hibernate Lazy Initialization Exception. I tried out the recommendations on net, mostly SO, but none of them seem to help. Here is code.
DAO Method throwing error :
#Override
public List<GroupNotes> searchNotesByDays(int days, int mcanvasid) {
Session session = this.sessionFactory.getCurrentSession();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
long daysAgo = cal.getTimeInMillis();
Timestamp nowMinusDaysAsTimestamp = new Timestamp(daysAgo);
Query query = session.createQuery("from GroupSection as n where n.currentcanvas.mcanvasid=:mcanvasid");
query.setParameter("mcanvasid", mcanvasid);
List<GroupSection> sectionList = query.list();
List<GroupNotes> notesList = new ArrayList<GroupNotes>();
for (GroupSection e : sectionList) {
Query query1 = session.createQuery("from GroupNotes as n where n.ownednotes.msectionid=:msectionid and n.noteCreationTime >:limit");
query1.setParameter("limit", nowMinusDaysAsTimestamp);
query1.setParameter("msectionid",e.getMsectionid());
notesList.addAll(query1.list());
}
return notesList;
}
GroupCanvas model :
#Entity
#Table(name = "membercanvas")
public class GroupCanvas{
variables, getters, setters ignored
#OneToMany(mappedBy = "currentcanvas",fetch=FetchType.LAZY, cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupSection> ownedsection = new HashSet<>();
public Set<GroupSection> getOwnedsection() {
return this.ownedsection;
}
public void setOwnedsection(Set<GroupSection> ownedsection) {
this.ownedsection = ownedsection;
}
}
GroupSection model class :
#Entity
#Table(name = "membersection")
public class GroupSection {
#ManyToOne
#JoinColumn(name = "groupcanvasid",nullable = false)
#JsonIgnore
private GroupCanvas currentcanvas;
public GroupCanvas getCurrentcanvas() {
return this.currentcanvas;
}
public void setCurrentcanvas(GroupCanvas currentcanvas) {
this.currentcanvas = currentcanvas;
}
public int getCurrentCanvasId(){
return this.currentcanvas.getMcanvasid();
}
#OneToMany(mappedBy = "ownednotes", fetch = FetchType.EAGER,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNotes> sectionsnotes = new HashSet<>();
public Set<GroupNotes> getSectionsnotes(){
return this.sectionsnotes;
}
public void setSectionsnotes(Set<GroupNotes> sectionsnotes){
this.sectionsnotes=sectionsnotes;
}
}
GroupNotes :
#Entity
#Table(name="groupnotes")
public class GroupNotes{
#ManyToOne
#JoinColumn(name = "msectionid")
#JsonIgnore
private GroupSection ownednotes;
public GroupSection getOwnednotes(){return this.ownednotes;}
public void setOwnednotes(GroupSection ownednotes){this.ownednotes=ownednotes;}
}
Error log :
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.journaldev.spring.model.GroupCanvas.ownedsection, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownednotes"]->com.journaldev.spring.model.GroupSection["currentcanvas"]->com.journaldev.spring.model.GroupCanvas["ownedsection"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.journaldev.spring.model.GroupCanvas.ownedsection, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownednotes"]->com.journaldev.spring.model.GroupSection["currentcanvas"]->com.journaldev.spring.model.GroupCanvas["ownedsection"])
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.writeInternal(MappingJackson2HttpMessageConverter.java:256)
What am I doing wrong, kindly let me know. If there is any more information required, kindly put a comment.
Your JSON converter is executed after the Hibernate session is completed. The JSON converter is blindly accessing all the getters and setters, even the lazy ones. So when Hibernate tries to initialize GroupCanvas#ownedSection, there is no session available and hence this exception is thrown.
Possible solutions:
Do not directly execute JSON converter on the Hibernate managed objects. Create DTO objects to do this job. DTO objects have no logic and are pure java beans and fit this role well. But the drawback is you have to maintain another class hierarchy. The benefits do outweigh the drawbacks. The following post can help with this approach:
DTO pattern : Best way to copy properties between two Objects
Use annotations to mark certain fields as not serializable. For example, JsonIgnore. The drawback with this is that if this field is ever needed in a different API, then you cannot use this.
If one of the back-ref can be eliminated from your model (notes->section/section->canvas), then that makes it "friendlier" to serialization. In other works, JSON does not work well with cyclic references, so the lesser the amount of bi-directional/loop constructs the better it is. If it were not for the possibility of a cyclic reference, then you could initialize all the data necessary for serialization including GroupCanvas.
I have my domain object, Client, I've got a form on my JSP that is pre-populated with its data, I can take in amended values, and persist the object.
Client has an abstract entity called MarketResearch, which is then extended by one of three more concrete sub-classes.
I have a form to pre-populate some MarketResearch data, but when I make changes and try to persist the Client, it doesn't get saved, can someone give me some pointers on where I've gone wrong?
My 3 domain classes are as follows (removed accessors etc)
public class Client extends NamedEntity
{
#OneToOne
#JoinColumn(name = "MARKET_RESEARCH_ID")
private MarketResearch marketResearch;
...
}
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class MarketResearch extends AbstractEntity
{
...
}
#Entity(name="MARKETRESEARCHLG")
public class MarketResearchLocalGovernment extends MarketResearch
{
#Column(name = "CURRENT_HR_SYSTEM")
private String currentHRSystem;
...
}
This is how I'm persisting
public void persistClient(Client client)
{
if (client.getId() != null)
{
getJpaTemplate().merge(client);
getJpaTemplate().flush();
} else
{
getJpaTemplate().persist(client);
}
}
To summarize, if I change something on the parent object, it persists, but if I change something on the child object it doesn't. Have I missed something blatantly obvious?
I've put a breakpoint right before the persist/merge calls, I can see the updated value on the object, but it doesn't seem to save. I've checked at database level as well, no luck
Thanks
You need to set a proper cascade option on #OneToOne in order to get your operations cascaded:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "MARKET_RESEARCH_ID")
private MarketResearch marketResearch;