adonisJS/transaction(+エラーの4つの解決策)を使用


プロジェクトで取引を行うロジックを補完する必要があるので、仕事のついでに整理しておきました.

🎢 トランザクションの概念


私が定義したトランザクション
1回のリクエストでデータベースの複数のテーブルにデータを入れる必要がある場合は、
すべてのデータ入力に成功または失敗した操作.
例えば私は友達に1万元送金します.
1.私の口座からお金が流出しました.
2.友人の口座にお金を預ける.
この過程は両方とも成功するか、両方とも失敗するかのどちらかだ.
私のお金だけが友達の口座に入っていなければ、お金は消えてしまいます.
->もしあなたが私のお金だけを持って行って、あなたの友達の口座にお金がないなら、
transactionのrollbackで私の口座からお金が流出しないようにします.

注意:トランザクションとは?

🎢 トランザクションをadonisJSに適用


adonisの基本例.
import Database from '@ioc:Adonis/Lucid/Database'
import User from 'App/Models/User'

await Database.transaction(async (trx) => {
  const user = new User()
  user.username = 'virk' // 1. user 테이블에 데이터를 넣는다

  user.useTransaction(trx) // 2. transaction 부분 완료 (임시 저장개념)
  await user.save()

  /**
   * The relationship will implicitly reference the 
   * transaction from the user instance
   */
  await user.related('profile').create({ // 3. user 와 연결된 profile 테이블에 데이터를 넣는다.
    fullName: 'Harminder Virk',
    avatar: 'some-url.jpg',
  })
})
補足コード
const trx = await Database.transaction() // transaction 시작
try {
    user.name = request.input('name') // 데이터를 넣는다.
    user.tel = request.input('tel')
    user.address = request.input('address')

    user.useTransaction(trx) // user 테이블 부분완료 (임시저장)
    await (await user.save()).refresh()
    
    post.title = request.input('title') // 데이터를 넣는다.
    post.body = request.input('body')
    post.isHidden = request.input('isHidden')

    post.useTransaction(trx) // post 테이블 부분완료 (임시저장)
    await (await post.save()).refresh()
    
    await trx.commit() // 완료 (여기까지 왔으면 문제없다는 것이다. 실제로 저장진행)

    return response.ok(user)
  
  } catch (error) {
     await trx.rollback() // 실패가 발생하면 이쪽으로 빠지는데, 철회 상태가 된다. (DB를 확인해보면 수행한 작업들이 아무것도 들어와 있지 않다.)

     throw new TransactionExceptionHandler()
  }
注意:docs.adonisjs.com(本書)

🎢 エラーTransaction queryが完了しました。


すべてのソースをチェックした後、最後のチェックを行うためにテストを行いました.
そしてエラーに遭遇しました.Transaction query already complete, run with DEBUG=knex:tx for more info同じエラーメッセージが3回も受信され、解決されたのはもう一つの原因です.
理由1:非同期を正しく掛けていないため
// 수정 전
posts.map(async (post: Post) => {
  post.isHidden = request.input('isHidden)
                                
  post.useTransaction(trx)
  await (await post.save()).refresh()
})

// 수정 후
await Promise.all(
  posts.map(async (post: Post) => {
    post.isHidden = request.input('isHidden)
                                
    post.useTransaction(trx)
    await (await post.save()).refresh()
  })
)
これらのデータは、まず一時的に格納され、次に別のデータロジックが格納されます.
常に上のデータを発見してから起動します.
これはmapの特性のためです.
mdn:mapメソッドは、各配列の要素が所与の関数を呼び出す新しい配列を返します.
mapの内部はpromiseオブジェクトで、同期処理するならpromise.allでいいです
順番通りに同期処理するためです.
△動機、非同期、約束の概念をさらに学ぶ必要があると思います.
理由2:commitの後にもう一つのトランザクションがあります.
const trx = await Database.transaction()
    try {
      // 1번 데이터 저장 로직
      
      // 2번 데이터 저장 로직
      
      // ...
      
      await trx.commit()

	  // 아래 코드를 await trx.commit()위로 올렸다.
      await DataHistory.changeDataHistory(user.id, ...)

      return response.created(channel)
    } catch (error) {
      await trx.rollback()
      throw new TransactionExceptionHandler()
    }
ChangeDataHistoryの論理
これは、データベースにどのデータを格納する論理であり、一時的に格納する必要があります.
位置を変えると、エラーは発生しません.
理由3:構文エラー
const trx = await Database.transaction()
    try {
      // 1번 데이터 저장 로직
      
      // 2번 데이터 저장 로직
      
      // ...

      user.load('post') // -> await user.load('post')

      await trx.commit()

      return user
    } catch (error) {
      console.log('error', error)
      await trx.rollback()

      return {
        error: error,
      }
    }
loadはuserに接続されたpostデータを取得する論理であり,非同期処理が必要である.
待機と負荷は同じテーブルです.

ポスト


この機会に取引についてよく知っています.
ちなみにcommitやrollbackが抜けているとDBが拡張する可能性があります.
トランザクションを適用すると、すべての論理が完了した後にコミットされます.
エラーが発生した場合は、rollbackが必須かどうかを確認してください.

🎢 統計データに反映されていないデータエラー


トランザクションを適用した後、数日後にデータ異常の通知を受けました.
実際のデータと統計表に記録されているデータの数が異なることが確認された.
新しく修正された取引のためであることが確認された.
transactionに新しいデータを格納->これらのデータを計算することによって統計データを格納します.
新しいデータは一時的に格納され、統計データは実際のデータのみを計算します.
->一時データを計算できるようにtrxキーを与えた.
public static async userPostCount (user: User, trx:TransactionClientContract | null) {
  let query = Post.query().count('* as total')
  if (trx) {
    query.useTransaction(trx) // 이렇게 임시저장된 데이터도 쿼리에 걸어준다.
  }
  query.where('user_id', user.id)
 
  const total = await query.first()

  return total
}