2015-09-30 22 views
8

Mam jednostkę User, jednostkę UserToApplication i jednostkę Application.Sprężyna Dane Specyfikacja JPA przy użyciu CriteriaBuilder w/a jeden do wielu relacji

Pojedynczy może mieć dostęp do więcej niż jednego Application. Pojedynczy Application może być używany przez więcej niż jeden User. Jest to jednostka . To jest jednostka UserToApplication.

@Entity 
@Table(name = "USER_TO_APPLICATION", schema = "UDB") 
public class Application { 
    private Long userToApplicationId; 
    private User user; 
    private Application application; 

    @SequenceGenerator(name = "generator", sequenceName = "UDB.USER_TO_APP_SEQ", initialValue = 0, allocationSize = 1) 
    @Id 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator") 
    @Column(name = "USER_TO_APPLICATION_ID", unique = true, nullable = false) 
    public Long getUserToApplicationId() { 
     return userToApplicationId; 
    } 

    public void setUserToApplicationId(Long userToApplicationId) { 
     this.userToApplicationId = userToApplicationId; 
    } 

    @ManyToOne 
    @JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID", nullable = false) 
    public User getUser() { 
     return user; 
    } 

    public void setUser(User user) { 
     this.user = user; 
    } 

    @ManyToOne 
    @JoinColumn(name = "APPLICATION_ID", nullable = false) 
    public Application getApplication() { 
     return application; 
    } 
} 

A oto jednostka Application.

@Entity 
@Table(name = "APPLICATION", schema = "UDB") 
public class Application { 
    private Long applicationId; 
    private String name; 
    private String code; 

    /* Getters and setters omitted for brevity */ 
} 

Mam następujący Specification które mogę używać, aby wyszukać User przez firstNm, lastNm i email.

public class UserSpecification { 

    public static Specification<User> findByFirstNmLastNmEmail(String firstNm, String lastNm, String email) { 
     return new Specification<User>() { 
      @Override 
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 
       final Predicate firstNmPredicate = null; 
       final Predicate lastNmPredicate = null; 
       final Predicate emailPredicate = null; 

       if (!StringUtils.isEmpty(firstNm)) { 
        firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm)); 
       } 
       if (!StringUtils.isEmpty(lastNm)) { 
        lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm)); 
       } 
       if (!StringUtils.isEmpty(email)) { 
        emailPredicate = cb.like(cb.lower(root.get(User_.email), email)); 
       } 
       return cb.and(firstNmPredicate, lastNmPredicate, emailPredicate); 
      } 
     }; 
    } 

} 

A oto metamodel User_, który dotychczas miałem.

@StaticMetamodel(User.class) 
public class User_ { 
    public static volatile SingularAttribute<User, String> firstNm; 
    public static volatile SingularAttribute<User, String> lastNm; 
    public static volatile SingularAttribute<User, String> email; 
} 

Teraz chciałbym również zdać się na liście identyfikatorów aplikacji do Specification, tak, że jego podpis metody byłoby:

public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds) 

Więc moje pytanie brzmi, czy mogę dodać @OneToMany mapowanie do metamodelu User_ dla pola Collection<Application> applications jednostki User, a następnie w jaki sposób powinienem go odnieść w Specification?

mojego obecnego Specification byłby podobny do następującego zapytania SQL:

select * from user u 
where lower(first_nm) like '%firstNm%' 
and lower(last_nm) like '%lastNm%' 
and lower(email) like '%email%'; 

i co chciałbym osiągnąć w nowej Specification byłoby coś takiego:

select * from user u 
join user_to_application uta on uta.user_id = u.user_id 
where lower(u.first_nm) like '%firstNm%' 
and lower(u.last_nm) like '%lastNm%' 
and lower(u.email) like '%email%' 
and uta.application_id in (appIds); 

Czy to możliwe, zrobić tego rodzaju mapowanie w metamodelu i jak mogę osiągnąć ten wynik w moim Specification?

Odpowiedz

11

Znalazłem rozwiązanie. Mapowanie jeden do wielu atrybutów, w metamodelu dodałem następujące:

public static volatile CollectionAttribute<User, Application> applications; 

Musiałem także dodać metamodel dla podmiotu Application.

@StaticMetamodel(Application.class) 
public class Application_ { 
    public static volatile SingularAttribute<Application, Long> applicationId; 
} 

potem w moich Specification mogłem otworzyć applications dla użytkownika, przy użyciu metody .join() na przykład Root<User>. Oto utworzony Predicate.

final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds); 

Ponadto, warto zauważyć, że mój Specification jak jest napisane w pytaniu nie będzie działać, jeśli którykolwiek z wartościami wejściowymi są puste. Wartość null przekazana do metody .and() z spowoduje, że będzie to NullPointerException. Tak więc utworzyłem ArrayList typu Predicate, a następnie dodałem każde Predicate do listy, jeśli odpowiedni parametr nie był pusty. Na koniec przekształcam ArrayList w tablicę, aby przekazać ją do funkcji .and() z . Oto ostateczna Specification:

public class UserSpecification { 

    public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds) { 
     return new Specification<User>() { 
      @Override 
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 
       final Collection<Predicate> predicates = new ArrayList<>(); 
       if (!StringUtils.isEmpty(firstNm)) { 
        final Predicate firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm)); 
        predicates.add(firstNmPredicate); 
       } 
       if (!StringUtils.isEmpty(lastNm)) { 
        final Predicate lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm)); 
        predicates.add(lastNmPredicate); 
       } 
       if (!StringUtils.isEmpty(email)) { 
        final Predicate emailPredicate = cb.like(cb.lower(root.get(User_.email), email)); 
        predicates.add(emailPredicate); 
       } 
       if (!appIds.isEmpty()) { 
        final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds); 
        predicates.add(appPredicate); 
       } 

       return cb.and(predicates.toArray(new Predicate[predicates.size()])); 
      } 
     }; 
    } 

} 
+0

deklaracji zmiennej w klasie Application_ powinna być następująca, public static volatile SingularAttribute applicationId; zamiast publicznego statycznego zmiennego SingularAttribute applicationId; Ogólna odpowiedź jest idealna, ale – Avi

+0

@Avi dziękuje za wskazanie tego! Poprawiłem to w mojej odpowiedzi. –