You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无CDI请求上下文时检索实体问题(OptaPlanner、Hibernate Panache、Kotlin)

解决OptaPlanner ConstraintProvider中调用Hibernate Panache查询的上下文异常

问题背景

你尝试通过数据库配置动态控制OptaPlanner的约束开关,在ConstraintProvider的defineConstraints方法中直接调用Hibernate Panache的ConfigEntity.findByKey查询配置,但应用启动时触发以下异常:

(Quarkus Main Thread) Failed to start application (with profile dev): javax.enterprise.context.ContextNotActiveException: Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active. Consider adding @Transactional to your method to automatically activate a transaction, or @ActivateRequestContext if you have valid reasons not to use transactions.

给defineConstraints方法添加@Transactional注解后问题依然存在。

问题原因

OptaPlanner的ConstraintProvider在应用启动阶段初始化,此时CDI请求上下文和事务上下文都未激活。@Transactional注解仅在CDI容器管理的事务上下文触发方法调用时生效,而defineConstraints是由OptaPlanner初始化逻辑直接调用的,不受CDI事务上下文管理,所以添加注解无效。

解决方案

将配置加载逻辑移到CDI bean中,利用Quarkus启动事件提前把配置加载到内存,再在ConstraintProvider中注入该bean获取配置值,避免直接在defineConstraints中访问数据库。

步骤1:创建配置加载CDI Bean

import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.event.Observes
import jakarta.transaction.Transactional
import io.quarkus.runtime.StartupEvent

@ApplicationScoped
class ConfigLoader {
    private val configCache = mutableMapOf<String, String>()

    // 应用启动时加载配置到内存缓存
    @Transactional
    fun loadConfigOnStartup(@Observes event: StartupEvent) {
        // 按需加载需要的配置键,也可调用ConfigEntity.listAll()加载全部配置
        listOf("room", "teacher").forEach { key ->
            ConfigEntity.findByKey(key)?.let { config ->
                configCache[key] = config.stringValue
            }
        }
    }

    // 从缓存获取配置值
    fun getConfig(key: String): String? {
        return configCache[key]
    }
}

步骤2:修改ConstraintProvider注入配置Bean

import jakarta.inject.Inject
import org.optaplanner.core.api.score.stream.Constraint
import org.optaplanner.core.api.score.stream.ConstraintFactory
import org.optaplanner.core.api.score.stream.ConstraintProvider

class TimeTableConstraintProvider : ConstraintProvider {

    @Inject
    lateinit var configLoader: ConfigLoader

    override fun defineConstraints(constraintFactory: ConstraintFactory): Array<Constraint> {
        val constraints = mutableListOf<Constraint>()

        if (configLoader.getConfig("room") == "ENABLED") {
            constraints.add(roomConflict(constraintFactory))
        }
        if (configLoader.getConfig("teacher") == "ENABLED") {
            constraints.add(teacherConflict(constraintFactory))
        }

        return constraints.toTypedArray()
    }

    // 约束逻辑示例(根据实际业务调整)
    private fun roomConflict(constraintFactory: ConstraintFactory): Constraint {
        return constraintFactory.from(Lesson::class.java)
            .join(Lesson::class.java,
                equal(Lesson::room),
                overlapping(Lesson::startTime, Lesson::duration)
            )
            .penalize("Room conflict", HardSoftScore.ONE_HARD)
    }

    private fun teacherConflict(constraintFactory: ConstraintFactory): Constraint {
        return constraintFactory.from(Lesson::class.java)
            .join(Lesson::class.java,
                equal(Lesson::teacher),
                overlapping(Lesson::startTime, Lesson::duration)
            )
            .penalize("Teacher conflict", HardSoftScore.ONE_HARD)
    }
}

原理说明

  • ConfigLoader作为应用级CDI bean,通过@Observes StartupEvent监听启动事件,触发带有@Transactional注解的加载方法,此时事务上下文已激活,可正常调用Panache查询并将配置存入内存缓存。
  • TimeTableConstraintProvider初始化时通过CDI注入ConfigLoader,直接从内存缓存读取配置值,无需再访问数据库,彻底规避了上下文缺失的问题。

内容的提问来源于stack exchange,提问作者twango

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:00:51