# 事务

## 基本机制 <a href="#ji-ben-ji-zhi" id="ji-ben-ji-zhi"></a>

创建数据库连接之后，我们使用该数据库连接创建事务管理器。

```go
db := rdb.Connect("local.properties")
tm := rdb.NewTransactionManager(db)
```

GoooQo中的事务管理通过[TransactionManager](https://github.com/doytowin/goooqo/blob/main/core/core.go#L60)和[TransactionContext](https://github.com/doytowin/goooqo/blob/main/core/core.go#L66)（以下简称TC）配合完成。

```go
package core

import (
	"context"
	"database/sql/driver"
)

type TransactionManager interface {
	GetClient() any
	StartTransaction(ctx context.Context) (TransactionContext, error)
	SubmitTransaction(ctx context.Context, callback func(tc TransactionContext) error) error
}

type TransactionContext interface {
	context.Context
	driver.Tx
	Parent() context.Context
	SavePoint(name string) error
	RollbackTo(name string) error
}
```

`TransactionManager`中的方法`StartTransaction`负责开启事务并返回TC；TC组合了`driver.Tx`，负责事务的提交和回滚。

[TxDataAccess](https://github.com/doytowin/goooqo/blob/main/core/core.go#L74)接口组合了[DataAccess](https://github.com/doytowin/goooqo/blob/main/core/core.go#L46)和`TransactionManager`，可以在实现数据库访问接口的同时，更方便的进行事务管理。

```go
type TxDataAccess[E Entity] interface {
	TransactionManager
	DataAccess[E]
}
```

## 事务使用示例 <a href="#shi-wu-shi-yong-shi-li" id="shi-wu-shi-yong-shi-li"></a>

使用`TransactionManager#StartTransaction`开启事务，手动提交或者回滚事务：

```go
tc, err := userDataAccess.StartTransaction(ctx)
userQuery := UserQuery{ScoreLt: P(80)}
cnt, err := userDataAccess.DeleteByQuery(tc, userQuery)
if err != nil {
	err = RollbackFor(tc, err)
	return 0
}
err = tc.Commit()
return cnt
```

或者使用`TransactionManager#SubmitTransaction`通过回调的方式提交事务：

```go
err := tm.SubmitTransaction(ctx, func(tc TransactionContext) (err error) {
    // transaction body
    return
})
```

## 事务的传播管理 <a href="#shi-wu-de-chuan-bo-guan-li" id="shi-wu-de-chuan-bo-guan-li"></a>

在Spring的事务传播机制中定义了以下7个级别，`GoooQo`中的对应处理方式如下：

* REQUIRED

  使用任意context调用`TxDataAccess`的`StartTransaction`：

  如果context为TC，则将context强制转化为TC后返回；

  如果context不是TC，则调用`db#BeginTx`开启事务获取`sql.Tx`，再通过context和`sql.Tx`创建TC后返回。
* SUPPORTS

  使用任意`Context`调用`TxDataAccess`的数据库访问方法。
* REQUIRES\_NEW

  当`ctx`为TC时，使用`ctx.(TC).Context`开启事务；\
  当`ctx`不为TC时，使用`ctx`开启事务；
* NOT\_SUPPORTED

  当`ctx`为TC时，使用`ctx.(TC).Context`调用`TxDataAccess`的数据库访问方法；\
  当`ctx`不为TC时，使用`ctx`调用`TxDataAccess`的数据库访问方法；
* MANDATORY:

  对传入的`ctx`不是TC的情况进行处理。
* NEVER

  对传入的`ctx`是TC的情况进行处理。
* NESTED

  使用TC的`SavePoint/RollbackTo`方法。

这里整理了一个表格对前4个传播级别进行了对比：

| context参数\数据库操作         | 开启事务          | 调用数据库          |
| ----------------------- | ------------- | -------------- |
| 任意Context               | REQUIRED      | SUPPORTS       |
| ctx.(TC).Context \| ctx | REQUIRES\_NEW | NOT\_SUPPORTED |
