I'm having some issues getting a bidirectional one-to-many association working with JoinTables. This is what I got:
Class A:
#OneToMany
#JoinTable(name="join_table",
JoinColumns={#JoinColumn(name="a_id")},
inverseJoinColumns={#JoinColumn(name="b_id")}
)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
public Set<B> getBs() {
return bs;
}
Class B:
#ManyToOne
#JoinTable(name="join_table",
joinColumns={#JoinColumn(name="b_id", insertable=false,updatable=false)},
inverseJoinColumns={#JoinColumn(name="a_id", insertable=false,updatable=false)})
public A getA() {
return a;
}
If I create a instance of A and B, add the instance of B to A and save. It works. But when I reload the instance of A and try and access the set of Bs it throws a LazyInitializationError with the message "illegal access to loading collection ".
Where am I going wrong here? :) Can anybody point me to a example of bidirectional association which uses a join table. And where the ownership is kept to Class A, I have searched though the documentation at hibernate.org but I cant seem to find it.
-Daniel
Your mapping are proper and that's why the entry is getting saved in the Database. The issue in fetching is because of the Lazy Initialization.
To solve it modify mapping of the class A as,
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="join_table",
joinColumns={#JoinColumn(name="a_id")},
inverseJoinColumns={#JoinColumn(name="b_id")}
)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
public Set<B> getBs() {
return bs;
}
This will fire an additional query to the table B and initialize the collection. It might affect the performance depending on the no of entries in your defendant table.
Read the API here for more information.
Related
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 have a Hibernate query with Criteria.
What I would like to do is (just for one query) tell hibernate to ignore an existing #ManyToOne annotation.
that is because hibernate creates and Left join on other tables.
I could figure it how to do it.
I have found this 2 links which didn't solve my problem:
Hibernate: How to remove an entity to which none refers to anymore in ManyToOne?
What is the difference between DELETE_ORPHAN and DELETE?
If you have such mapping:
//Parent
public class A {
...
}
//Child
public class B {
private A parent; //Many to one
...
}
Please try something like this:
Criteria q = ....;
q.setFetchMode("parent", FetchMode.SELECT);
....
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);
I have 2 objects joined together defined as such:
public class A {
...
#Id
#Column(name = "A_ID")
#SequenceGenerator(...)
#GeneratedValue(...)
public Long getA_ID();
#OneToOne(mappedBy = "a", fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = B.class)
public B getB();
...
}
#VirtualAccessMethods(get = "getMethod", set = "setMethod")
public class B {
...
#Id
public Long getA_ID();
#MapsId
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL ,targetEntity = A.class)
#JoinColumn(name="A_ID")
public A getA();
getMethod(String name);
setMethod(String name, Object value);
...
}
When I go to em.merge(A) with B joined onto A for an INSERT, everything works fine. However if I do the same thing for an update, it will update only A. The update logic is like so:
#Transactional
public void update(Object fieldOnANewValue, Object fieldOnBNewField) {
A objA = em.executeQuery(...) //loads objA by primary key
objA.setFieldOnA(fieldOnANewValue);
B objB = objA.getB(); //lazy loads objB
objB.setMethod("FieldOnB", fieldOnBNewValue);
}
If I look at the logs, there is a SQL UPDATE statement committing the changes I made to A, but nothing for B. If I manually call em.merge(objB) the same issue exists. Does anyone know exactly what EclipseLink does to determine whether or not to generate an UPDATE statement? Particularly with regard to #VirtualAccessMethods? However, I have had the #OneToOne mappings setup differently before and em.merge(objB) worked fine then, plus INSERT works, so I'm not sure if that's the issue. On the flip side, if I have another object that is also joined onto A, but just is a normal POJO like A is, the UPDATE statement is generated for that. Caching is turned off, and I've verified that the objects are updated correctly before merge is called.
Please show the complete code and mappings.
Given you are using virtual access (are you using this correctly?), it could be some sort of change tracking issue related to the virtual access. Does the issue occur without using virtual access?
Try setting,
#ChangeTracking(ChangeTrackingType.DEFERRED)
to see if this has an affect.
You could also try,
#InstantiationCopyPolicy
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.