我使用Spring boot 3和Jpa规范来构建动态查询,分页和获取连接在一起以避免N 1查询问题。但是分页应用于内存中的应用程序级别,而不是数据库级别的查询。
我的用户实体是
@Entity
@Table(name = "users")
public class User implements UserDetails, Serializable {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "names")
private String names;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
...
}
我的用户存储库是
@Repository
public interface UserRepository extends
JpaRepository<User, UUID>, JpaSpecificationExecutor<User> { ... }
我的UserGateway使用UserReposity. findAll(规范
@Repository
@RequiredArgsConstructor
public class UserGatewayImpl implements UserGateway {
private final UserRepository userRepository;
@Override
public Page<User> findByParams(UserSearchCommand searchCriteria, Pageable pageable) {
var userSpecification = buildCriteria(searchCriteria);
return userRepository.findAll(userSpecification, pageable);
}
}
我的构建条件。为了避免“org. hibernate.query.SemanticException:query指定join fetting,但获取的关联的所有者不存在于选择列表中”异常,因为JPA执行了2个查询,我检查了结果类型以知道它是否是计数查询。就像在这个答案中使用分页解决方案Fetch join
private Specification<User> buildCriteria(UserSearchCommand queryCriteria) {
return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (Long.class != criteriaQuery.getResultType() &&
long.class != criteriaQuery.getResultType()) {
root.fetch("roles", JoinType.LEFT);
} else {
root.join("roles", JoinType.LEFT);
}
if (nonNull(queryCriteria.getNames())) {
predicates
.add(criteriaBuilder.and(
criteriaBuilder.equal(root.get("names"), queryCriteria.getNames())));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
这似乎有效。分页“有效”。但是当我们查看控制台并检查hibernate生成的查询时,我看到分页没有在查询中的数据库级别应用。分页应用于内存中的应用程序级别。
生成的查询是
...
from
users u1_0
left join
(users_roles r1_0
join
roles r1_1
on r1_1.id=r1_0.role_id)
on u1_0.id=r1_0.user_id
where
1=1
order by
u1_0.id desc
我想一起使用JPA规范、分页和FETCH JOIN,但分页是在内存中的应用程序级别应用的。分页应该在查询中的数据库级别应用。
Hibernate在这种情况下不能做得更好,因为当一个集合被fetch join时,它不能应用SQL限制/偏移子句,因为SQL基数与实体结果基数不匹配。
您可以尝试在顶部使用Blaze-持久性,它还带有Spring Data集成,可以自动将查询转换为允许高效分页的表单。试一试。初始设置后,您只需将@EnableJpaRepositories
替换为@EnableBlazeRepositories
。