JPA leaves join requests not instantiating child collections in Spring Boot?
Just a quick question, I am using JPA with spring boot. I have a class member with @ManyToMany (FetchType.LAZY) relationship to Artist class. Members do not need to have artists, but artists must have members.
In my JpaRepository extension , I have a method to fetch members and any possible child executors it may have. Looks like that:
@Query("select m from Member m left join fetch m.artists where m.id=:id")
Member getMemberWithArtists(@Param("id") long memberId);
In the case where A has no children B, collection B is returned as null. I've used this approach with a JavaEE project before, and then this kind of query would always give me an empty collection, not just NULL. Is spring boot differently?
My question is, is there anything I can do to make it return an empty collection instead of null?
Here is object A
@Entity
@Table(name="MEMBER")
public class Member extends LocationHolder implements Messenger {
@ManyToMany(cascade={CascadeType.MERGE,CascadeType.REFRESH}, fetch=FetchType.LAZY)
@JoinTable(name="MEMBER_ARTIST_REL",
joinColumns=@JoinColumn(name="MEMBER_ID", referencedColumnName="ENTITY_ID"),
inverseJoinColumns=@JoinColumn(name="ARTIST_ID", referencedColumnName="ENTITY_ID"))
private List<Artist> artists;
Here B
@Entity
@Table(name="ARTIST")
public class Artist extends LocationHolder {
@ManyToMany(mappedBy="artists", targetEntity=Member.class, fetch=FetchType.LAZY, cascade={MERGE, REFRESH})
private List<Member> members;
The @Id field is in a superclass and is of type Long
source to share
By default, JPA should really return an empty set in the scenario you described. However, looking at your entities, I suspect there is a scenario that could produce the result null
:
- It looks like your objects are not initializing the list with
artists
an empty collection when you create an item. - Create and save an instance
Member
- If, in the same JPA session, you tried to download
Member
using the repository methodgetMemberWithArtists
, then indeed the collectionmember.artists
would benull
. This is caused by the fact that the instance is beingMember
returned from the JPA session cache (the one that was created in step (2) where itmember.artists
was equalnull
due to the lack of default initialization for the collection)
I tried to illustrate this in the following piece of code:
@Test
public void testNullCollection() {
Member member = new Member();
memberRepository.save(member);
member = memberRepository.getMemberWithArtists(member.getId());
// Here the collection is going to be null since the instance is returned from the session cache
assertNull(member.getArtists());
entityManager.flush();
entityManager.clear();
// Here the collection is going to be empty, since in the previous step the session was cleared hence forcing JPA to initialize the entity from scratch and inject lazy collection
member = memberRepository.getMemberWithArtists(member.getId());
assertNotNull(member.getArtists());
assertTrue(member.getArtists().isEmpty());
}
This integration test really works for me. (in case of a test JPA session for the whole test. In a real web application, you have to configure the JPA session to continue for only one request).
I believe that simply initializing member.artists
an empty collection in the object's constructor Member
should solve the problem for newly created and persisted objects (when loaded in the same JPA session ).
public Member() {
this.artists = new ArrayList();
}
Hope it helps.
source to share