如何让Play框架应用执行CRUD API前运行SQL脚本?Evolutions失效排查
嘿,我来帮你搞定这两个问题——先是Play Evolutions没自动执行SQL脚本的坑,再是如何在CRUD API跑起来前执行自定义SQL脚本。
从你的配置和描述来看,主要有几个可能的原因,咱们一个个排查:
1. 确认你加对了依赖
要让Evolutions和Slick配合工作,你必须在build.sbt里引入play-slick-evolutions依赖,不然Evolutions根本不会和Slick的数据源联动。把这段加到你的依赖列表里:
libraryDependencies ++= Seq( guice, "mysql" % "mysql-connector-java" % "8.0.33", // 对应你的MySQL版本 "com.typesafe.play" %% "play-slick" % "5.0.0", // 对应你的Play版本 "com.typesafe.play" %% "play-slick-evolutions" % "5.0.0" )
2. 修正Slick数据源配置,和Play的数据源对齐
你当前的Slick配置是直接创建独立数据源,和Evolutions监听的Play默认数据源不是同一个,导致脚本执行了也没作用在Slick连接的库上。修改application.conf里的Slick配置:
slick.dbs.default.driver = "slick.jdbc.MySQLProfile$" # 让Slick使用Play的HikariCP数据源,这样Evolutions能作用于它 slick.dbs.default.db.dataSourceClass = "play.api.db.HikariCPDataSource" slick.dbs.default.db.properties.driver = "com.mysql.cj.jdbc.Driver" slick.dbs.default.db.properties.url = "jdbc:mysql://localhost:3306/playscalaslickexample?useSSL=false&serverTimezone=UTC" slick.dbs.default.db.properties.user = "你的MySQL用户名" slick.dbs.default.db.properties.password = "你的MySQL密码"
注意:先手动在MySQL里创建playscalaslickexample数据库——Evolutions只会帮你建表,不会帮你建库!
3. 检查Evolutions脚本的格式和路径
- 路径必须是
conf/evolutions/default/1.sql(因为你的数据源名称是default,不是数据库名playscalaslickexample) - 脚本必须符合Evolutions的格式,开头要有
# --- !Ups(执行的升级脚本)和# --- !Downs(回滚脚本),比如:
# --- !Ups CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL ); # --- !Ups INSERT INTO users(name) VALUES('测试用户'); # --- !Downs DROP TABLE users;
如果没有这个格式,Evolutions会忽略这个脚本。
4. 确认Evolutions配置生效
你的现有Evolutions配置是对的,保持不变就行:
play.evolutions.enabled=true play.evolutions.autoApply=true play.evolutions.db.default.enabled = true
做完这些,重启应用,Evolutions应该会自动执行1.sql里的脚本了。
如果需要在每次CRUD接口调用前执行特定SQL(比如记录审计日志、设置会话变量),有几种灵活的方式:
1. 自定义Action组合器(推荐,针对单个/部分接口)
创建一个可复用的Action包装器,在目标接口逻辑前执行SQL:
import play.api.mvc._ import slick.jdbc.MySQLProfile.api._ import scala.concurrent.ExecutionContext // 注入Database来执行SQL class PreRunSqlActions(db: Database)(implicit ec: ExecutionContext) { def withPreRunSql(action: Action[AnyContent]): Action[AnyContent] = Action.async { request => // 这里写你要执行的SQL,比如记录请求日志 val sqlTask = sql"INSERT INTO audit_log (path, request_time) VALUES (${request.path}, NOW())".asUpdate db.run(sqlTask).flatMap { _ => // 执行完SQL再调用原接口逻辑 action(request) } } }
然后在控制器里用这个组合器包装你的CRUD接口:
class UserController @Inject()( val controllerComponents: ControllerComponents, db: Database, preRunSqlActions: PreRunSqlActions )(implicit ec: ExecutionContext) extends BaseController { // 用自定义Action包装,执行CRUD前先跑SQL def getUsers() = preRunSqlActions.withPreRunSql { Action.async { db.run(Users.result).map(users => Ok(Json.toJson(users))) } } }
2. 使用全局Filter(针对所有符合条件的接口)
如果要给所有CRUD接口统一加前置SQL,用Play的Filter拦截请求:
import play.api.mvc._ import slick.jdbc.MySQLProfile.api._ import scala.concurrent.{ExecutionContext, Future} class PreRunSqlFilter(db: Database)(implicit ec: ExecutionContext) extends Filter { override def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader): Future[Result] = { // 只对/api开头的CRUD接口生效,可根据自己的路由调整 if (requestHeader.path.startsWith("/api")) { val sqlTask = sql"SET @current_request_id = '${requestHeader.id.toString}'".asUpdate db.run(sqlTask).flatMap { _ => nextFilter(requestHeader) } } else { // 非CRUD接口直接放行 nextFilter(requestHeader) } } }
然后在Module.scala里绑定这个Filter:
import play.api.mvc.EssentialFilter import com.google.inject.AbstractModule class Module extends AbstractModule { override def configure(): Unit = { bind(classOf[EssentialFilter]).to(classOf[PreRunSqlFilter]) } }
3. 数据库会话级钩子(针对所有数据库操作)
如果要在每次获取数据库连接时执行SQL(比如设置SQL模式),可以扩展Slick的Database:
import slick.jdbc.MySQLProfile.api._ import scala.concurrent.ExecutionContext object CustomDb { def withPreRunScript(db: Database)(implicit ec: ExecutionContext): Database = { Database.forDataSource(db.dataSource, None) { connection => // 每次获取连接都执行这个SQL val stmt = connection.createStatement() stmt.execute("SET sql_mode = 'STRICT_TRANS_TABLES'") stmt.close() connection } } }
之后在应用中用这个自定义的Database来执行操作即可。
内容的提问来源于stack exchange,提问作者Shashi Shekhar

