0

挑战:

我正在尝试从我的数据库中批量获取嵌套实体的集合。生成的数据集包含数千个实体,因此我的方法是根据这篇文章以分页方式获取实体。数据是从基于 Web 的前端获取的,纯非分页查询最多需要 10 秒(不可接受)。

问题:

“父”实体已正确获取,但“子”实体似乎未获取。在来自 TestRepository.getRankedTests(...) 的结果实体列表中,“子”实体列表未初始化,访问它们将导致LazyInitializationException. 这指向我的问题的方向SqlResultMapping,但我看不到错误。我试图SqlResultMapping为孩子注入错误,这会导致休眠在运行时抱怨,所以它似乎试图将我的配置映射到子实体的属性,尽管未初始化的子实体集合让我哑口无言。

父实体(Test.kt):

@NamedNativeQuery(
    name = "Test.getRankedTests",
    query = """
        select *
        from (
            select
                *,
                DENSE_RANK() over (
                    order by "o.id"
                ) rank
            from (
                select
                    o.id as "o.id",
                    o.version as "o.version",
                    a.id as "a.id",
                    a.organisation_id as "a.organisation_id",
                    a.type as "a.type"
                from  organisation o
                left join address a on o.id = a.organisation_id
                order by o.organisation_number
            ) o_a_an_c
        ) o_a_an_c_r
        where o_a_an_c_r.rank > :min and o_a_an_c_r.rank <= :max
        """,
    resultSetMapping = "TestMapping"
)
@SqlResultSetMapping(
    name = "TestMapping",
    entities = [
        EntityResult(
            entityClass = Test::class,
            fields = [
                FieldResult(name = "id", column = "o.id"),
                FieldResult(name = "version", column = "o.version"),
            ]
        ),
        EntityResult(
            entityClass = TestChild::class,
            fields = [
                FieldResult(name = "id", column = "a.id"),
                FieldResult(name = "organisation", column = "a.organisation_id"),
            ]
        ),
    ]
)
@Entity
@Table(name = "organisation")
class Test(
    @Id
    val id: Long,
    val version: Long,
    @OneToMany(mappedBy = "organisation", cascade = [CascadeType.ALL], orphanRemoval = true)
    val addresses: List<TestChild>,
)

子实体(TestChild.kt):

@Entity
@Table(name = "address")
@Suppress("LongParameterList")
class TestChild(
    @Id
    val id: Long,
    @ManyToOne(fetch = FetchType.LAZY)
    val organisation: Test,
)

存储库(TestRepository.kt):

@Repository
interface TestRepository : JpaRepository<Test, Long> {
    fun getRankedTests(
        min: Long,
        max: Long
    ): List<Test>
}
4

2 回答 2

0

AFAIK,无法通过 JPA 结果集映射注释获取集合。如果您愿意,可以使用特定于 Hibernate 的 API 来执行此操作,看起来类似于:

SQLQuery q = session.createNativeQuery(...);
q.addRoot("o", Test.class)
 .addProperty("id", "o.id")
 .addProperty("version", "o.version");
q.addFetch("a", "o", "addresses")
 .addProperty("id", "a.id")
 .addProperty("organisation", "a.organisation_id");

但是,如果您只是想要有效的分页,我建议您查看Blaze-Persistence,它带有专门的实现和有效的 spring-data 集成

@Repository
interface TestRepository : JpaRepository<Test, Long> {
    @EntityGraph("addresses")
    fun getRankedTests(
        pageable: Pageable
    ): Page<Test>
}
于 2021-08-09T07:08:37.313 回答
0

感谢Christian Beikov的好提议。这里缺少的链接是 ResultTransformer。由于本机查询最终会在同一 JDBC 行上同时包含父项和子项,因此我们最终将得到一个包含两者的对象数组。ResultTransformer 将负责将该对象数组映射回实体层次结构。这是我修复它的方法:

添加了一个 DAO,用于使用 entityManager 获取结果:

@Repository
class Dao(
    @PersistenceContext
    private val entityManager: EntityManager
) {

    fun getRankedTests(): List<Test> =
        entityManager.createNamedQuery("Test.getRankedTests")
            .setParameter("max", 5)
            .setHint(QueryHints.HINT_READONLY, true)
            .unwrap(NativeQuery::class.java)
            .setResultTransformer(TestResultTransformer(entityManager))
            .resultList.filterIsInstance(Test::class.java)
}

创建了以下 ResultTransformer:

class TestResultTransformer(private val entityManager: EntityManager) : BasicTransformerAdapter() {
    override fun transformList(
        list: List<*>
    ): List<Test> {
        val identifiableMap: MutableMap<Long, Test> = mutableMapOf()
        for (entityArray in list) {
            if (entityArray is Array<*>) {
                var test: Test? = null
                var testChild: TestChild? = null
                for (tuple in entityArray) {
                    entityManager.detach(tuple);
                    when (tuple) {
                        is Test -> test = tuple
                        is TestChild -> testChild = tuple
                        else -> {
                            throw UnsupportedOperationException(
                                "Tuple " + tuple?.javaClass + " is not supported!"
                            )
                        }
                    }
                }
                if (test != null) {
                    val key = test.id
                    if (!identifiableMap.containsKey(key)) {
                        identifiableMap[key] = test
                        test.addresses = mutableListOf()
                    }
                    if (testChild != null) {
                        test.addresses.add(testChild)
                    }
                }
            }
        }
        return identifiableMap.values.toList()
    }
}
于 2021-08-09T20:22:33.807 回答