写在前面
升级了springboot版本,发现jpa默认的分页实现变成了弃用,仔细了解推荐使用blaze-persistence,于是我研究下jpa默认的分页实现代码和blaze-persistence的分页实现代码,确实我们应该尽快使用类似blaze-persistence实现。
特意去看了github blaze-persistence,项目活跃度不高,问题挺多的,而且使用遇到的问题集中在github、stackoverflow这两个地方,国内几乎是空白的。所以不建议在生产环境使用blaze-persistence。
但这并不妨碍它是一个非常有意义的开源项目。Blazebit是两个就读于维也纳科技大学的学生创立的,不得不羡慕老外的it行业土壤肥沃。
一、blaze-persistence能做什么
这是官方文档 blaze-persistence docs,它实现了spring data jpa的所有定义,并且增强非常多的功能(具体内容参见官方文档)。
当然,在这里我就是为了正常的分页查询,其他的spring data jpa功能我还是使用hibernate的默认实现。
二、springboot集成blaze-persistence
官方文档坑很多,写的不完整,这点需要注意
在当前已经使用jpa的项目中增加以下内容:
- 包引入
implementation("com.blazebit:blaze-persistence-integration-spring-data-2.6:1.6.7")
implementation("com.blazebit:blaze-persistence-integration-querydsl-expressions:1.6.7")
//这个根据你当前的jpa带的hibernate版本来我这里是5.6
implementation("com.blazebit:blaze-persistence-integration-hibernate-5.6:1.6.7")
- 全局配置
import com.blazebit.persistence.Criteria
import com.blazebit.persistence.CriteriaBuilderFactory
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Lazy
import org.springframework.context.annotation.Scope
import javax.persistence.EntityManagerFactory
import javax.persistence.PersistenceUnit
@Configuration
class BiazeJpaConfig {
@PersistenceUnit
private val entityManagerFactory: EntityManagerFactory? = null
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Lazy(false)
fun createCriteriaBuilderFactory(): CriteriaBuilderFactory {
val config = Criteria.getDefault()
return config.createCriteriaBuilderFactory(entityManagerFactory)
}
}
到这里就配置完成了,你可以按 blaze-persistence docs开始你的使用了
三、springboot共存JPAQueryFactory、BlazeJPAQuery
前面说了BlazeJPAQuery不活跃,所以对它不太放心,于是想两种方式共存,用其优点。
这是我多年前写的一个工具,当时针对的是JPAQueryFactory,现在把BlazeJPAQuery也加上
import com.blazebit.persistence.CriteriaBuilderFactory
import com.blazebit.persistence.querydsl.BlazeJPAQuery
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.ObjectMapper
import com.kdj.server.support.request.PageResult
import com.kdj.server.utils.PageUtils
import com.querydsl.core.Tuple
import com.querydsl.core.types.Expression
import com.querydsl.jpa.impl.JPAQueryFactory
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Component
import java.util.*
import javax.annotation.PostConstruct
import javax.persistence.EntityManager
/**
* 对querydsl的查询结果做了一个简化解析的实现,具体输出参考 PageUtlis
* @see com.ayouran.flow.utlis.PageUtlis
*/
@Component
class QuerydslSupport @Autowired
constructor(
private val objectMapper: ObjectMapper,
private val entityManager: EntityManager,
private val criteriaBuilderFactory: CriteriaBuilderFactory
) {
private val logger = LoggerFactory.getLogger(javaClass)
//查询工厂实体
private var blazeQueryFactory: BlazeJPAQuery<Tuple>? = null
//查询工厂实体
private var queryFactory: JPAQueryFactory? = null
@PostConstruct
private fun initFactory() {
logger.info("开始实例化JPAQueryFactory")
queryFactory = JPAQueryFactory(entityManager)
logger.info("开始实例化BlazeJPAQueryFactory")
blazeQueryFactory = BlazeJPAQuery<Tuple>(entityManager, criteriaBuilderFactory)
}
/**
* 获得查询工厂实例
*
* @return JPAQueryFactory
*/
fun getQueryFactory(): JPAQueryFactory {
if (queryFactory == null) initFactory()
return queryFactory!!
}
/**
* 获得查询工厂实例
*
* @return BlazeJPAQuery<Tuple>
*/
fun getBlazeQueryFactory(): BlazeJPAQuery<Tuple> {
if (blazeQueryFactory == null) initFactory()
return blazeQueryFactory!!
}
/**
* 将com.querydsl.core.Tuple转换为需要的bean
*
* @param tuple 查询得到的数据
* @param exprs 查询的列
* @param clazz 要转换的目标 class类型
* @return 返回根据 clazz转换的 class -> Object对象
* ==== 注意 =======
* @see objectMapperConfig
* 需要在有 objectMapperConfig 的配置前提
* @see com.fasterxml.jackson.annotation.JsonProperty
* 默认取最后的字段名字与clazz的类字段名称匹配,如有不匹配的字段 请自行使用@JsonProperty注解来匹配
* 匹配规则为 exprs 查询参数的全称
*
* 示例:
* querydslUtlis.getBean(it, exprs, QueryDeviceFlowRulesVO::class.java) as QueryDeviceFlowRulesVO
*/
fun <T> getBean(tuple: Tuple, exprs: Array<Expression<*>>, clazz: Class<T>): Any {
val map = getBeanMap(tuple, exprs)
return if (map.isEmpty()) Any() else mapToBean(map, clazz)
}
/**
* 将内容转换为mapbean
*
* @param tuple 查询得到的数据
* @param exprs 查询的列
* @return 返回一个基于bean属性的 map
*/
private fun getBeanMap(tuple: Tuple, exprs: Array<Expression<*>>): MutableMap<String, Any> {
val map = mutableMapOf<String, Any>()
exprs.forEach {
val value = getValue(tuple, it)
val key = it.toString()
val index = key.lastIndexOf(".")
var subKey = ""
if (index != -1) subKey = key.substring(key.lastIndexOf(".") + 1)
if (subKey.isNotEmpty() && !map.containsKey(subKey)) {
map[subKey] = value
} else {
map[key] = value
}
}
return map
}
/**
* 获取值
* @param tuple 查询的结果对象
* @param expression 查询的字段
* @return 值
*/
private fun getValue(tuple: Tuple, expression: Expression<*>): Any {
var value = tuple[expression]
value = when {
value != null -> {
value
}
(expression.type.name == "java.lang.Long"
|| expression.type.name == "java.lang.Int") -> {
0
}
(expression.type.name == "java.lang.Number"
|| expression.type.name == "java.lang.Double"
|| expression.type.name == "java.lang.Float") -> {
0.00
}
(expression.type.name == "java.util.Date") -> {
""
}
else -> {
expression.type.getDeclaredConstructor().newInstance()
}
}
return value
}
/**
* 将List<com.querydsl.core.Tuple>转换为需要的List<bean>
*
* @param tuples 查询得到的数据结果集
* @param exprs 查询的列
* @param clazz 要转换的目标 class类型
* @return 返回根据 clazz转换的 class -> List<Object>对象
* ==== 注意 =======
* @see objectMapperConfig
* 需要在有 objectMapperConfig 的配置前提
* @see com.fasterxml.jackson.annotation.JsonProperty
* 默认取最后的字段名字与clazz的类字段名称匹配,如有不匹配的字段 请自行使用@JsonProperty注解来匹配
* 匹配规则为 exprs 查询参数的全称
* 示例:
* querydslUtlis.getList(results.results, exprs, QueryDeviceVO::class.java) as List<QueryDeviceVO>
*/
fun <T> getList(tuples: MutableList<Tuple>, exprs: Array<Expression<*>>, clazz: Class<T>): MutableList<T> {
val list = mutableListOf<Map<String, Any>>()
tuples.forEach { tuple ->
val map = getBeanMap(tuple, exprs)
if (map.isNotEmpty()) list.add(map)
}
return listToListBean(list, getCollectionType(List::class.java, clazz))
}
// 将对象转成字符串
@Throws(Exception::class)
private fun objectToString(obj: Any): String {
return objectMapper.writeValueAsString(obj)
}
// 将Map转成指定的Bean
@Throws(Exception::class)
private fun <T> mapToBean(map: Map<*, *>, clazz: Class<T>): Any {
return objectMapper.readValue(objectToString(map), clazz)!!
}
// 将Bean转成Map
@Throws(Exception::class)
private fun beanToMap(obj: Any): Map<*, *> {
return objectMapper.readValue(objectToString(obj), MutableMap::class.java)
}
// 将list<map>转成指定的list<Bean>
@Throws(Exception::class)
private fun <T, R> listToListBean(list: MutableList<T>, clazz: JavaType): MutableList<R> {
return objectMapper.readValue(objectToString(list), clazz)
}
/**
* 获取泛型的Collection Type
*
* @param collectionClass 泛型的Collection
* @param elementClasses 元素类
* @return JavaType Java类型
* @since 1.0
*/
private fun getCollectionType(collectionClass: Class<*>, vararg elementClasses: Class<*>): JavaType {
return objectMapper.typeFactory.constructParametricType(collectionClass, *elementClasses)
}
/**
* BlazeJPAQuery的分页查询
* @param exprs 查询要显示的列名
* @param query 查询表达式
* @param clazz 分页对象的vo类
* @return 一个标准的分页返回
*/
fun <T> page(
exprs: Array<Expression<*>>,
query: BlazeJPAQuery<Tuple>,
clazz: Class<T>
): PageResult<T> {
query.select(*exprs)
val count = query.fetchCount()
var results = mutableListOf<Tuple>()
if (count > 0) {
results = query.fetch()
}
val list = this.getList(results, exprs, clazz)
val queryModifiers = query.metadata.modifiers
val page =
PageImpl(
list,
PageRequest.of(queryModifiers.offset!!.toInt(), queryModifiers.limit!!.toInt()),
count
)
return PageUtils.retPage(page)
}
}
使用
先使用spring注入 querydslSupport对象,下面是一个完整使用的例子,与jpa querydsl使用保持一致,不需要更改代码。
val qPostsModel = QPostsModel.postsModel
val qUserModel = QUserModel.userModel
val qPostsVisibleUserModel = QPostsVisibleUserModel.postsVisibleUserModel
val rredicate = mutableListOf<Predicate>()
if (pageRequest.query != null) rredicate.add(qPostsModel.content.like("%${pageRequest.query}%"))
if (channel != null) rredicate.add(qPostsModel.channel.eq(channel))
if (isPublish) {
rredicate.add(qPostsModel.userNo.eq(userNo))
} else {
// 发帖-部分人可见 部分人不可见 全部人可见
rredicate.add(
(qPostsModel.roles.eq(PostsRoles.PARTIALLY_VISIBLE)
.and(
(qPostsVisibleUserModel.postsVisibleUser.eq(PostsVisibleUser.VISIBLE_USER)
.and(qPostsVisibleUserModel.userNo.eq(userNo)))
.or(
qPostsVisibleUserModel.postsVisibleUser.eq(PostsVisibleUser.NOT_VISIBLE_USER)
.and(qPostsVisibleUserModel.userNo.ne(userNo))
)
)).or(qPostsModel.roles.eq(PostsRoles.VISIBLE_TO_ALL))
)
}
val sort = if (pageRequest.sortHeat() == pageRequest.sort) {
//最热
QPostsModel.postsModel.heat.desc()
} else {
//最新
qPostsModel.createAt.desc()
}
val exprs = arrayOf<Expression<*>>(
qPostsModel.postNo,
qPostsModel.userNo,
qUserModel.name,
qUserModel.avatar,
qPostsModel.content,
qPostsModel.images
)
val query = querydslSupport.getBlazeQueryFactory()
.select(*exprs)
.from(qPostsModel)
.leftJoin(qUserModel)
.on(qPostsModel.userNo.eq(qUserModel.userNo))
.leftJoin(qPostsVisibleUserModel)
.on(qPostsVisibleUserModel.userNo.eq(qUserModel.userNo))
.where(ExpressionUtils.allOf(rredicate))
.orderBy(sort)
.offset((pageRequest.pageIndex()) * pageRequest.pageSize())
.limit(pageRequest.pageSize())
val page = querydslSupport.page(exprs, query, PostsVO::class.java)
page.result?.filter { it.postNo != null }?.forEach { it.replys = getReplys(it.postNo!!, userNo) }
return page
有以下几个方法供选择:
-
querydslSupport.getBlazeQueryFactory()获取Blaze查询实例 -
querydslSupport.getQueryFactory()获取默认查询实例 -
querydslSupport.page()执行Blaze分页查询并解析成想要的数据结构 -
querydslSupport.getList()将querydsl list<Tuple>解析成想要的数据结构 -
querydslSupport.getBean()将querydsl Tuple解析成想要的数据bean
评论区