17

I have a following domain model:

Playlist -> List<PlaylistItem> -> Video

@Entity
class Playlist{
   // id, name, etc
   List<PlaylistItem> playlistItems;
   // getters and setters
}


@Entity
class PlaylistItem{
   // id, name, etc.
   Video video;
   // getters and setters
}


@Entity
class Video{
   // id, name, etc.
   boolean isDeleted;
   // getters and setters
}

And my repository:

public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
   List<Playlist> findAll();
}

Now, how do I return a playlist with only existing videos, ie, if there are three videos in the database assigned to that playlist item and one of those videos has isDeleted set to true, then I need to get only two items instead.

Maksim
  • 16,635
  • 27
  • 94
  • 135
  • 1
    I guess you are asking about something similar to Hibernate FilterDef and Filter functionality. Unfortunately I don't know the easy way to use anything similar in spring data jpa. FYI: http://stackoverflow.com/questions/11619174/hibernate-filter-children – Vadim Kirilchuk Sep 02 '15 at 10:13
  • All of the answers provided so far, are just about querying to get the PlayLists that have at least one non-deleted item, not to filter out the *delete items* once you get the lists back. @VadimKirilchuk is the only one with a solution that will change what items you are getting back. – egeorge Jan 10 '23 at 21:51

4 Answers4

23

All you have to do is declare this method on your PlaylistRepository interface:

List<Playlist> findByPlaylistItemsVideoIsDeleted(boolean isDeleted);

And call it like this:

playListRepository.findByPlaylistItemsVideoIsDeleted(false);

That will return all playlist with videos that are not removed.

inafalcao
  • 1,415
  • 1
  • 11
  • 25
  • 3
    could you explain how this magically works? Any reference to jpa where this is described? – judos Jun 18 '18 at 21:28
  • @judos Have a look at https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation and https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation – masterxilo Jun 20 '19 at 13:10
7

You may have already resolved this issue, but I thought I would contribute this in hopes it might help you, or anyone else visiting this page.

Using Spring JPA Specifications, you would:

  1. Enable your PlaylistRepository to use JPA Specifications
  2. Write the Specification as a reusable method
  3. Make use of the Specification as the query

Here are the details.

1. Implement JpaSpecificationExecutor

Update PlaylistRepository to implement JpaSpecificationExecutor. This adds find* methods that accept Specification<T> parameters to your PlaylistRepository.

public interface PlaylistRepository extends JpaRepository<Playlist, Long>, 
            JpaSpecificationExecutor<Playlist> {

}

2. Create the Specification

Create a class with a static method for use in creating a reusable Specification.

public final class PlaylistSpecifications {

    private PlaylistSpecifications() {}

    public static Specification<Playlist> hasExistingVideos() {
        return (root, query, cb) -> {
            return cb.equal(root.join("playlistItems").join("video")
                    .get("isDeleted"), false);
        };
    }
}

Using root.join (and subsequent joins) is similar to using JOIN in SQL. Here, we are joining on the fields of classes, instead of on columns of tables.

3. Issue the Query

I don't know how you plan to issue your query, but below is an example of how it could be done in a "service" class:

@Service
public class PlaylistService {

    @Autowired
    private PlaylistRepository playlistRepository;

    public List<Playlist> findPlaylistsWithExistingVideos() {

        Specification<Playlist> spec = PlaylistSpecifications.hasExistingVideos();
        return playlistRepository.findAll(spec);
    }
}

Hope this helps!

Jack Straw
  • 490
  • 1
  • 7
  • 17
5

Maksim, you could use the @query annotation like this :

public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
   @Query("select playlist from Playlist playlist 
           fetch join playlist.playlistItems itens
           fetch join itens.video as video
           where video.isDeleted = false")
   List<Playlist> findAll();
}

Or even better way :

public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
   @Query("select playlist from Playlist playlist 
           fetch join playlist.playlistItems itens
           fetch join itens.video as video
           where video.isDeleted = :hasVideo ")
   List<Playlist> findPlayList(@Param("hasVideo") boolean hasVideo);
}
Diogo Calazans
  • 488
  • 9
  • 18
3

You can look into Spring Data Specifications. You use them by calling repository.findAll(s);

Specifications allow you add on arbitrary conditions to your query, including the filter you want to add. Another nice thing about Specifications is that they can be type-safe. See here:

http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications

Michael Tontchev
  • 909
  • 8
  • 23
  • How would one structure a JPA Specification to query for `Playlist`s using `Video.isDeleted()`? Would this involve using `Join`? – Jack Straw Apr 04 '19 at 21:09
  • Unfortunately I don't work on the same project any more so I've fallen out of touch with the tech - hope someone else can help! – Michael Tontchev Apr 05 '19 at 22:18