Как сформировать в jpa specification join запрос, если существует промежуточная таблица?
Есть 2 энтити:
@Entity
@Table
public class Complex {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(fetch = FetchType.EAGER)
private List<UserTable> users;
}
@Entity
@Table
public class UserTable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "users", fetch = FetchType.EAGER)
private List<Complex> complexes;
}
Для этой ситуации jpa создает промежуточную таблицу complex_users.
И вот вопрос, как правильно составить спецификацию, чтобы получить пользователей по энтити Complex?
Я пробую такой вариант:
public class UsersSpec {
public static Specification<User> find(UsersFilter usersFilter) {
return (root, query, cb) -> {
final Collection<Predicate> predicates = new ArrayList<>();
if(usersFilter.getComplex() != null) {
predicates.add(root.join("complexes", JoinType.LEFT).in(Collections.singletonList(usersFilter.getComplex())));
}
else {
predicates.add(root.join("complexes", JoinType.LEFT).isNull());
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
}
Но jpa генерирует совсем не тот запрос, как будто он забывает о третьей таблице:
select ... from
user_table user0_
left outer join
complex complexes1_
on user0_.id=complexes1_.users_id
where
(
complexes1_.id in (55)
)
Хотя должно быть так:
select ... from
user_table user0_
left join
complex_users complexes1_
on user0_.id=complexes1_.users_id
where
(
complexes1_.complex_id in (55)
)
Как правильно составить join?
Ответы (2 шт):
При использовании спецификаций подразумевается, что запрос динамический и набор спецификаций может быть различным. В таком случае, лучше делать не одну спецификацию на все условия сразу, а по спецификации на каждое условие, которые не зависят друг от друга. Join в данном случае не очень подходит, тут лучше использовать Subquery.
public class UserSpecifications {
public static Specification<User> containsComplex(Complex complex) {
return (root, query, builder) -> {
Subquery<Complex> subquery = query.subquery(Complex.class);
Root<Complex> subqueryRoot = subquery.from(Complex.class);
Predicate predicateToJoin = builder.isMember(root,
subqueryRoot.get("users"));
if(complex != null) {
Predicate predicateByComplex = builder.equal(subqueryRoot, complex);
subquery.select(operation).where(predicateToJoin, predicateByComplex);
return builder.exists(subquery);
}
subquery.select(operation).where(predicateToJoin);
return builder.exists(subquery).not();
};
}
}
Использование в проекте:
Specification spec = UserSpecifications.containsComplex(usersFilter.getComplex())
.and(UserSpecifications.secondSpecification(usersFilter.secondParameter()))
.and(UserSpecifications.thirdSpecification(usersFilter.thirdParameter()));
repository.findAll(spec);
Все спецификации должны возвращать предикат, а не null. Если спецификацию нужно проигнорировать исходя из значения параметра, то для этого есть builder.conjunction(), который вернет условия вида 1=1. Есть также builder.disjunction(), который вернет условие 1=0. Используют их следующим образом:
public static Specification<User> containsComplex(Complex complex) {
return (root, query, builder) -> {
if(complex != null) {
return builder.conjunction(); //всегда истина, т.е. игнорируется
} else {
return builder.disjunction(); //всегда ложь
}
};
}
Судя по вашим сущноснять вам нужна, @ManyToMany (неважно двунаправленая или одно решайте сами)
@Entity
@Table
public class Complex {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany(fetch = FetchType.Lazy)
private List<UserTable> users;
}
@Entity
@Table
public class UserTable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany(mappedBy = "users", fetch = FetchType.Lazy)
private List<Complex> complexes;
}
и запрос с джоином, плюс к кому же поменять fetch = FetchType.EAGER на fetch = FetchType.Lazy что бы избежать N+1
@Query(value = "select u from User u left join fetch u.complexes c where c = :complex")
User findByComplex (@Param(value = "complex") Complex complex);
или же вы можете сделать так как вы хотели через промежуточную таблицу если у вас будет не List сылка, а просто сущность, иначе отношение @ManyToMany