Johan
Johan

Reputation: 231

JPA left join queries wont instantiate child collections in Spring Boot?

Just a quick question, I am using JPA with spring boot. I have a class Member with a @ManyToMany(FetchType.LAZY) relationship to class Artist. Members does not have to have Artists but Artists needs to have Members.

In my JpaRepository extension I have a method for fetching Members and any possible Artist children it may have. Looks like this:

@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 B children the B collection comes back as null. I was using this approach with a JavaEE project before and then this kind of query would always give me an empty collection instead of just NULL. Is spring boot handling this differently?

My question is, is there anything I can do so that it returns an empty collection instead of null?

Here is Entity 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 is 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;

@Id field is in the superclass and is of type Long

Upvotes: 3

Views: 960

Answers (1)

Ruben
Ruben

Reputation: 771

By default JPA should indeed return an empty set in the scenario you have described. However, looking at your entities I suspect that there is a scenario which could lead to the null result :

  1. It looks like that your entities do not initialize the artists list to an empty collection when an member is created.
  2. Create and save an instance of a Member
  3. If in the same JPA session you would try to load the member using getMemberWithArtists repository method then, indeed, the member.artists collection would have been null. This is caused by the fact that the member instance is returned from the JPA session cache (the one created in the step (2) where member.artists was equal to null due to missing default initialization of the collection)

I have tried to illustrate same in the code snippet bellow :



    @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 actually passes for me. (in case of test JPA session spans for the whole test. In the actual web application you should configure the JPA session to last only for a single request).

I believe that just initializing the member.artists to an empty collection in the constructor of the Member entity should solve the issue for the newly created and persisted entities (when loaded within the same JPA session).



    public Member() {
        this.artists = new ArrayList();
    }

Hope this helps.

Upvotes: 2

Related Questions