Sequelize에서 트랜잭션에는 크게 두 가지 종류가 있다.
// First, we start a transaction and save it into a variable
const t = await sequelize.transaction();
try {
// Then, we do some calls passing this transaction as an option:
const user = await User.create({
firstName: 'Bart',
lastName: 'Simpson'
}, { transaction: t });
await user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, { transaction: t });
// If the execution reaches this line, no errors were thrown.
// We commit the transaction.
await t.commit();
} catch (error) {
// If the execution reaches this line, an error was thrown.
// We rollback the transaction.
await t.rollback();
}
직접 sequelize.transaction() / t.commit() / t.rollback() 과 같은 함수를 호출하고 있는 것을 알 수 있다.
try {
const result = await sequelize.transaction(async (t) => {
const user = await User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, { transaction: t });
await user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, { transaction: t });
return user;
});
// If the execution reaches this line, the transaction has been committed successfully
// `result` is whatever was returned from the transaction callback (the `user`, in this case)
} catch (error) {
// If the execution reaches this line, an error occurred.
// The transaction has already been rolled back automatically by Sequelize!
}
seuqelize 객체의 transaction 메소드에 async 콜백을 넘겨주고 실행했다.
이 경우에는
1. Sequelize는 자동으로 트랜잭션을 시작하고 트랜잭션 객체 t를 얻는다.
2. 콜백을 트랜잭션 t를 넘겨서 실행한다.
3. 만약 콜백 실행 도중 에러가 발생하면 자동으로 트랜잭션이 롤백되고
4. 별 일 없이 콜백이 다 잘 실행되면 자동으로 트랜잭션이 커밋된다.
만약 SQL 작업 관련해서는 별다른 오류가 없었지만 개발자 판단 하에 트랜잭션을 롤백해야하는 상황이라면
await sequelize.transaction(async t => {
const user = await User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, { transaction: t });
// Woops, the query was successful but we still want to roll back!
// We throw an error manually, so that Sequelize handles everything automatically.
throw new Error();
});
이렇게 콜백 안에서 에러를 throw 하면 된다.
그런데 Managed Transaction 코드에서도 자세히 보면 마찬가지로 각 메소드에 { transaction: t} 라는 두 번째 인자를 넣어서 트랜잭션 객체를 넣어주고 있음을 알 수 있다.
모든 트랜잭션에서 자동으로 각각의 트랜잭션 객체를 자동으로 넘겨주려면 cls-hooked(CLS, Continuation Local Storage) 모듈을 설치하고 이런 식의 코드를 써주면 된다.
const cls = require('cls-hooked');
const namespace = cls.createNamespace('my-very-own-namespace');
const Sequelize = require('sequelize');
Sequelize.useCLS(namespace);
new Sequelize(...);
이렇게 하고나면
sequelize.transaction((t1) => {
// With CLS enabled, the user will be created inside the transaction
return User.create({ name: 'Alice' });
});
이런 식으로 이제 해당 트랜잭션 객체를 넘겨주지 않아도 된다.
배치 작업 등을 수행할 때는 Concurrent 트랜잭션들이 필요하다. 만약 CLS enabled 상태라고 가정했을 때 다음 코드는 주석에 그 의미들이 나와있다.
sequelize.transaction((t1) => {
return sequelize.transaction((t2) => {
// With CLS enabled, queries here will by default use t2.
// Pass in the `transaction` option to define/alter the transaction they belong to.
return Promise.all([
User.create({ name: 'Bob' }, { transaction: null }),
User.create({ name: 'Mallory' }, { transaction: t1 }),
User.create({ name: 'John' }) // this would default to t2
]);
});
});
각 작업이 트랜잭션에 속해있지 않거나, 어느 트랜잭션에 속하는지가 옵션으로 표시되어 있거나,
아니면 CLS에 의해 자동으로 기본 트랜잭션에 속하거나 하고 있다. 이런 식으로 Concurrent 트랜잭션을 구현할 수 있다.
Unmanaged transaction의 경우에는
sequelize.transaction(options);
Managed transaction의 경우에는
sequelize.transaction(options, callback);
이렇게 필요한 옵션을 넘거주면 된다.
예를 들어 Isolation Level에는 다음과 같은 값들이 있는데
const { Transaction } = require('sequelize');
// The following are valid isolation levels:
Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED"
Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED"
Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ"
Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE"
이런 식으로 특정 트랜잭션의 isolation level을 설정할 수 있다.
const { Transaction } = require('sequelize');
await sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE
}, async (t) => {
// Your code
});
만약 모든 트랜잭션에 대해서 같은 isolation level을 주고 싶다면 이렇게 해주면 된다.
const { Sequelize, Transaction } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:', {
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
** 참고로 Servcie, Repository layer의 함수 하나당 트랜잭션 하나만 할당되도록 하는 것이 깔끔하다!
정리 굉장히 잘되어있네요! 잘봤습니다 :)