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

使用Play-Slick时如何获取javax.sql.Datasource实例?

问题

使用play-slick搭配slick evolutions创建数据库,配置如下:

slick.dbs.default.profile="slick.jdbc.H2Profile$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"

通过slick操作数据库一切正常,但需要获取底层的javax.sql.Datasource实例传入pac4j的DbProfileService中。

尝试添加配置dbName="play",并编写如下Guice Provider代码:

@Provides
def dbProfileService(dbApi: DBApi): DbProfileService = {
  new DbProfileService(dbApi.database(configuration.get[String]("dbName")).dataSource)
}

运行时抛出异常,提示找不到play数据库,堆栈跟踪如下:

aused by: java.lang.IllegalArgumentException: Could not find database for play
    at play.api.db.slick.evolutions.internal.DBApiAdapter.$anonfun$database$1(DBApiAdapter.scala:32)
    at scala.collection.immutable.Map$Map1.getOrElse(Map.scala:248)
    at play.api.db.slick.evolutions.internal.DBApiAdapter.database(DBApiAdapter.scala:32)
    at modules.SecurityModule.dbProfileService(SecurityModule.scala:53)
    at modules.SecurityModule$$FastClassByGuice$$153731.GUICE$TRAMPOLINE(<generated>)
    at modules.SecurityModule$$FastClassByGuice$$153731.apply(<generated>)
    at com.google.inject.internal.ProviderMethod$FastClassProviderMethod.doProvision(ProviderMethod.java:260)
    at com.google.inject.internal.ProviderMethod.doProvision(ProviderMethod.java:171)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.provision(InternalProviderInstanceBindingImpl.java:185)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.get(InternalProviderInstanceBindingImpl.java:162)
    at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:40)
    at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:60)
    at com.google.inject.internal.ProviderMethod.doProvision(ProviderMethod.java:171)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.provision(InternalProviderInstanceBindingImpl.java:185)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.get(InternalProviderInstanceBindingImpl.java:162)
    at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:40)
    at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:60)
    at com.google.inject.internal.ProviderMethod.doProvision(ProviderMethod.java:171)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.provision(InternalProviderInstanceBindingImpl.java:185)
    at com.google.inject.internal.InternalProviderInstanceBindingImpl$CyclicFactory.get(InternalProviderInstanceBindingImpl.java:162)
    at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:50)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:146)
    at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:101)
    at com.google.inject.internal.Initializer$InjectableReference.get(Initializer.java:245)
    at com.google.inject.internal.Initializer.injectAll(Initializer.java:140)
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)

断点调试发现,当前仅存在default数据库,不存在预期的play数据库。

解决方法

1. 修正数据库名称错误

你的配置中,play-slick的数据库标识是default(对应slick.dbs.default节点),而不是JDBC URL中mem:play的play。所以获取数据库时应该传入"default"而非"play":

@Provides
def dbProfileService(dbApi: DBApi): DbProfileService = {
  new DbProfileService(dbApi.database("default").dataSource)
}

2. 处理Evolutions执行顺序问题

如果DbProfileService在初始化阶段会访问数据库(比如查询或初始化表结构),Guice的注入时机可能早于slick evolutions脚本的执行,导致数据库结构未就绪。可以通过以下方式解决:

方式一:使用延迟初始化Provider

将DbProfileService的提供改为延迟加载,确保在实际使用时才获取数据源,此时Evolutions已执行完毕:

@Provides
def dbProfileService(dbApi: DBApi): Provider[DbProfileService] = {
  () => new DbProfileService(dbApi.database("default").dataSource)
}

注入时使用Provider[DbProfileService],在需要使用的地方调用get()方法获取实例。

方式二:使用@PostConstruct延迟初始化

自定义DbProfileService的子类,在@PostConstruct注解的方法中初始化数据源,此时应用已完成启动,Evolutions已执行:

import javax.annotation.PostConstruct
import javax.inject.Inject
import org.pac4j.core.profile.service.DbProfileService
import play.api.db.DBApi

class DelayedDbProfileService @Inject()(dbApi: DBApi) extends DbProfileService {

  @PostConstruct
  def init(): Unit = {
    val dataSource = dbApi.database("default").dataSource
    // 根据DbProfileService的API设置数据源,例如:
    setDataSource(dataSource)
    // 其他初始化逻辑(如设置SQL语句等)
  }
}

然后在Guice模块中绑定该类:

bind(classOf[DbProfileService]).to(classOf[DelayedDbProfileService])

方式三:确保Evolutions自动执行

确认application.conf中已开启Evolutions自动执行:

play.evolutions.enabled=true
play.evolutions.autoApply=true

这样应用启动时会自动执行所有未应用的evolution脚本,确保数据库结构就绪。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 08:36:11