侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

spring data jpa 之 blaze-persistence

Administrator
2022-08-19 / 0 评论 / 0 点赞 / 204 阅读 / 14237 字

写在前面

升级了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

    个人公众号

0

评论区