使用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

