Biorąc pod uwagę pokaz slajdów, który zawiera slajdy i inną tabelę (SeenSlideshow), w której są przechowywane informacje o pracownikach, którzy widzieli pokaz slajdów.

@Data
@Entity
@Wither
@Table(name = "slideshow")
public class Slideshow {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private SlideshowStatus status;

    @Enumerated(EnumType.STRING)
    @Column(name = "persona", nullable = false)
    private SlideshowPersona persona;

    @Embedded
    private Post post;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "slideshow_id")
    @OrderBy("slide_order asc")
    private List<Slide> slides;

    @Embedded
    private Conclusion conclusion;

    @CreationTimestamp
    private LocalDateTime createdAt;

    private LocalDateTime publishedAt;

    @Transient
    private boolean seen;

    public Slideshow() {
    }

    public Slideshow(Integer id, SlideshowStatus status, SlideshowPersona persona, Post post, Conclusion conclusion, LocalDateTime createdAt, LocalDateTime publishedAt, boolean seen) {
        this.id = id;
        this.status = status;
        this.persona = persona;
        this.post = post;
        this.conclusion = conclusion;
        this.createdAt = createdAt;
        this.publishedAt = publishedAt;
        this.seen = seen;
    }
}

SeenSlideshow:

@Data
@Builder
@AllArgsConstructor
@Entity
@Table(name = "seen_slideshow")
public class SeenSlideshow {
    @EmbeddedId
    private SeenSlideshowIdentity pk;


    @MapsId("employeeId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "employee_id")
    private Employee employee;

    @MapsId("slideshowId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "slideshow_id")
    private Slideshow slideshow;

    public SeenSlideshow() { }

    public SeenSlideshow(Employee employee, Slideshow slideshow) {
        this.pk = SeenSlideshowIdentity.builder()
                                       .employeeId(employee != null ? employee.getId() : null)
                                       .slideshowId(slideshow != null ? slideshow.getId() : null)
                                       .build();
        this.slideshow = slideshow;
        this.employee = employee;
    }
}

Jak mogę utworzyć zapytanie, które:

  1. jest podzielony na strony
  2. wypełnij przejściowe pole seen klasy Slideshow
  3. pozwala na sortowanie według tego pola seen, na podstawie danego pracownika (parametr zapytania)
  4. pobrać listę slajdów w tym samym czasie
  5. ma filtry dynamiczne (zwykle używam do tego przykładu lub specyfikacji)

Udało mi się spełnić pierwsze 3 wymagania, ale nie 4 i 5.

Mogę zastosować obejście 4, ale kiedy próbowałem użyć zapytania według przykładu lub specyfikacji, nie zadziałało, ponieważ żądanie, którego używam, wygląda następująco:

    @Query(
        value =  "select new Slideshow(s.id, s.status, s.persona, s.post, s.conclusion, s.createdAt, s.publishedAt, (SELECT COUNT(ss.pk.slideshowId) > 0 FROM SeenSlideshow ss WHERE s.id = ss.pk.slideshowId and ss.pk.employeeId = :employeeId) as seen ) from Slideshow s",
        countQuery = "select count(s) from Slideshow s"
    )
    Page<Slideshow> findForEmployee(String employeeId, Pageable pageable);

Ponieważ potrzebuję parametru do obliczenia pola seen, nie mogę dodać żadnego Example<Slideshow> ani Specification<Slideshow>.

Kiedy dodam specyfikację, Spring JPA traktuje ją jako prosty nieużywany parametr, a filtry dynamiczne nie są stosowane.

Jak możemy wykonać dynamiczne zapytanie z podzapytaniem w klauzuli select?

0
Clément Poissonnier 18 listopad 2019, 17:53
Myślę, że nie możesz uwzględnić w zapytaniu pola przejściowego, ponieważ JPA przetłumaczy twoje zapytanie kryterialne na zwykłe zapytanie sql, a baza danych nie jest świadoma tego pola. O ile wiem, nie ma sposobu na zastosowanie tego obejścia do zapytania lub specyfikacji kryteriów. Ale jeśli chcesz korzystać ze zwykłych funkcji Hibernate, możesz podać adnotacja do formuły spróbuj. W ten sposób możesz uwzględnić pole w zapytaniu kryterialnym. Ale musisz spróbować, jeśli nie możesz użyć liczenia w adnotacji
 – 
flxplzk
18 listopad 2019, 18:14
Niestety nie mogę użyć @Formula, ta wartość jest oparta na podłączonym użytkowniku, a Formuła nie może przyjąć żadnego parametru
 – 
Clément Poissonnier
18 listopad 2019, 18:20
1
Możesz wypróbować nieprzenośną wersję Hibernate @Formula lub utworzyć widok bazy danych, który można zmapować do istniejącej jednostki za pomocą JPA @SecondaryTable. stackoverflow.com/questions/53176186/…
 – 
Alan Hay
18 listopad 2019, 18:20

1 odpowiedź

Udało mi się zaimplementować obejście za pomocą pomysłu Alana (które nie pasowało dokładnie do tego, czego chciałem, ponieważ moja wartość zależy od podłączonego użytkownika).

To nie jest idealne rozwiązanie, ale stworzyłem widok z kolumnami pokazu slajdów + employee_id + obliczone pole seen.

create view employee_slideshow as
(select s.*, e.id as employee_id, (select COUNT(ss.slideshow_id) > 0 from seen_slideshow ss where ss.slideshow_id = s.id and ss.employee_id = e.id) as seen
from slideshow s, employee e
group by s.id, e.id)

Widok ten może być stronicowany, sortowany i filtrowany przez pracownika.

Następnie stworzyłem encję pasującą do tego widoku, wraz z jej repozytorium:

@Entity
@Table(name = "employee_slideshow")
public class EmployeeSlideshow {
    @Id
    private Integer id;

    // and the other slideshow fields

    private boolean seen;

    private String employeeId;
}

Nie tego oczekiwałem po Spring JPA, ale przy małej wolumetrii to będzie w porządku.

0
Clément Poissonnier 19 listopad 2019, 18:41