使用 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 dataSourceTx
用 TransactionAwareDataSourceProxy
包装同一个数据源, 被 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()
)。
如何设置 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 dataSourceTx
用 TransactionAwareDataSourceProxy
包装同一个数据源, 被 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()
)。