使用 Slick 进行 Spring 笔交易

Using Spring transactions with Slick

如何设置 Slick(使用 2.1)以便它使用与 Spring 配置关联的同一事务管理器?

更多上下文:我们有一个依赖 Slick 进行数据库工作的应用程序,并使用了一个库 (Activiti),其事务管理由 Spring 提供。我们围绕 Spring 事务调用该库,如下所示,我们希望每当 Activiti 端的事务失败时,我们的 Slick 调用发出的查询也会回滚。

def withSpringTransaction[T](f: TransactionStatus => T)(implicit   transactionTemplate: TransactionTemplate) =
transactionTemplate.execute(new TransactionCallback[T] {
  protected def doInTransaction(status: TransactionStatus) = f(status)
})


withSpringTransaction { transactionStatus =>
   db.withTransaction { session =>
       // Activiti API calls
       // Slick API calls
  }
}

我知道如果出现问题我们可以在前面的代码中同时调用 transactionStatus.setRollbackOnly()session.rollBack(),但我们的问题在于更复杂的场景,Activiti 在无法访问的其他地方调用一些监听器到在此范围内声明的会话。

我最终通过让 Spring 完全处理事务来解决了这个问题。如果事务被 Activiti 回滚,或者如果我们使用 transactionStatus.setRollbackOnly() 在我们的代码中手动回滚它,那么我们通过 Slick 完成的数据库查询将被回滚,只要我们将 Slick 配置为指向与 Spring 相同的数据源,包装在 TransactionAwareDataSourceProxy 中。所以 Spring 的应用程序上下文应该像

<bean id="dataSourceRaw" class="com.example.spring.DataSourceProvider" factory-method="getDataSource" />

<bean id="dataSourceTx" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
    <property name="targetDataSource" ref="dataSourceRaw" />
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">       
  <property name="dataSource" ref="dataSourceRaw" />
</bean>

所以需要引用数据源的 Spring bean 指向 dataSourceRaw,我们创建一个新的 bean dataSourceTxTransactionAwareDataSourceProxy 包装同一个数据源, 被 Slick 使用。

那么问题中的例子就变成了

def withSpringAndSlickTransaction[T](f: TransactionStatus => Session => T)(implicit transactionTemplate: TransactionTemplate, dataSourceTx: TransactionAwareDataSourceProxy) = {
    transactionTemplate.execute(new TransactionCallback[T] {
      protected def doInTransaction(status: TransactionStatus) = {
        val proxiedDb: scala.slick.driver.PostgresDriver.simple.Database = Database.forDataSource(dataSourceTx)
        val session = proxiedDb.createSession()
        f(status)(session)
      }
    })
  }

withSpringAndSlickTransaction { transactionStatus => session =>
   // Activiti API calls
   // Slick API calls
}

请注意,我们应该在 Slick 中手动创建会话,因为对 db.withSession 的调用会尝试将底层连接设置为自动提交模式,这会引发异常,因为 Spring 已经将其设置为不自动提交。当然,我们也禁止使用 session.rollback 手动回滚与 Slick 的事务,而是让 Spring 自动执行(或者我们自己手动使用 transactionStatus.setRollbackOnly())。