写在前面
升级了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
评论区