트랜잭션¶
Sequelize는 트랜잭션 사용의 두 가지 방법을 제공합니다.
Managed, Promise 체인의 결과에 따라 자동으로 트랜잭션을 커밋 또는 롤백하고 (CLS가 활성화 된 경우) 콜백 내의 모든 호출에 트랜잭션을 전달합니다.
Unmanaged, 커밋, 롤백 및 트랜잭션을 사용자에게 전달
주요 차이점은 Managed 트랜잭션은 Promise가 반환 될 것으로 예상되는 콜백을 사용하고 Unmanaged 트랜잭션은 Promise을 반환한다는 것입니다.
Managed transaction (auto-callback)¶
Managed 트랜잭션은 커밋 또는 롤백을 자동으로 처리합니다. Managed 트랜잭션을 시작할 때 sequelize.transaction
에 콜백을 전달하여 트랜잭션을 사용합니다.
트랜잭션으로 전달 된 콜백이 어떻게 Promise 체인을 반환하는지 확인하고 t.commit()
또는 t.rollback()
을 명시 적으로 호출하지 않습니다. 리턴 된 체인의 모든 Promise가 성공적으로 해결되면 트랜잭션이 commit 됩니다. 만약, 하나 또는 다수의 Promise가 reject 되면, 트랜잭션은 rollback 됩니다.
return sequelize.transaction(t => {
// chain all your queries here. make sure you return them.
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(user => {
return user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, {transaction: t});
});
}).then(result => {
// Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback
}).catch(err => {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback
});
롤백 오류 발생¶
Managed 트랜잭션을 사용할 때는 트랜잭션을 수동으로 커밋하거나 롤백해서는 안됩니다. 모든 쿼리가 성공적이지만 여전히 트랜잭션을 롤백하려는 경우 (예 : 유효성 검사 실패로 인해) 체인을 중단하고 거부하는 오류를 발생시켜야합니다.
return sequelize.transaction(t => {
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(user => {
// Woops, the query was successful but we still want to roll back!
throw new Error();
});
});
모든 쿼리들에게 트랜잭션을 자동으로 전달¶
앞의 예에서, 여전히 트랜잭션을 { transaction: t }
을 두 번째 인자로 수동으로 전달합니다. 모든 쿼리에 대해 자동으로 트랜잭션을 전달하기 위해 CLS (continuation local storage) 모듈을 설치하고 고유 한 코드로 네임 스페이스를 인스턴스화해야합니다.
const cls = require('continuation-local-storage');
const namespace = cls.createNamespace('my-very-own-namespace');
CLS를 사용하려면 sequelize 생성자의 정적 메서드를 사용하여 sequelize에 사용할 네임 스페이스를 알려야합니다.
const Sequelize = require('sequelize');
Sequelize.useCLS(namespace);
new Sequelize(....);
useCLS () 메소드는 sequelize 인스턴스가 아닌 생성자에 있습니다. 모든 인스턴스는 네임스페이스를 공유하고 CLS는 전부 또는 아무것도 아닙니다. 일부 인스턴스에 대해서만 활성화 할 수 없습니다.
CLS는 콜백을위한 스레드 로컬 스토리지처럼 작동합니다. 이것이 실제로 의미하는 것은 다른 콜백 체인은 CLS 네임스페이스를 사용하여 로컬 변수를 접근할 수 있습니다. CLS를 활성화 한 sequelize는 트랜잭션을 생성할 때 네임스페이스에서 transaction
속성을 설정할 수 있습니다. 콜백 체인 내에 설정된 변수는 해당 체인 전용이므로 여러 개의 동시 트랜잭션이 동시에 존재할 수 있습니다.
sequelize.transaction((t1) => {
namespace.get('transaction') === t1; // true
});
sequelize.transaction((t2) => {
namespace.get('transaction') === t2; // true
});
모든 쿼리는 네임 스페이스에서 트랜잭션을 자동으로 찾기 때문에 대부분의 경우 namespace.get ( ‘transaction’)에 직접 액세스 할 필요가 없습니다.
sequelize.transaction((t1) => {
// With CLS enabled, the user will be created inside the transaction
return User.create({ name: 'Alice' });
});
Sequelize.useCLS()
를 사용한 후에는 sequelize에서 반환 된 모든 Promise가 CLS 컨텍스트를 유지하기 위해 패치됩니다. CLS는 복잡한 주제입니다. cls-bluebird에 대한 자세한 내용은 CLS와 함께 블루 버드 Promise을 만드는 데 사용되는 패치입니다.
참고: CLS는 cls-hooked 패키지를 사용할 때 현재 async/await만 지원합니다.
동시 / 부분 트랜잭션¶
일련의 쿼리 내에서 동시 트랜잭션을 수행하거나 일부 트랜잭션을 트랜잭션에서 제외시킬 수 있습니다. {transaction :}
옵션을 사용하여 쿼리가 속하는 트랜잭션을 제어합니다.
경고: SQLite는 동시에 둘 이상의 트랜잭션을 지원하지 않습니다.
CLS를 사용하지 않는경우¶
sequelize.transaction((t1) => {
return sequelize.transaction((t2) => {
// With CLS enable, 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
]);
});
});
Isolation(격리) 수준¶
트랜잭션을 시작할 때 사용할 수 있는 격리 수준:
Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ"
Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE"
기본적으로 sequelize는 데이터베이스의 격리 수준을 사용합니다. 다른 격리 수준을 원한다면, 첫 번째 인자로 원하는 격리수준을 전달합니다.
return sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
}, (t) => {
// your transactions
});
격리 수준은 Sequelize
인스턴스를 초기화 할 때 전역으로 설정하거나 모든 트랜잭션에 대해 로컬로 설정할 수 있습니다.
// globally
new Sequelize('db', 'user', 'pw', {
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
// locally
sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
참고: MSSQL의 경우 SET ISOLATION LEVEL 쿼리가 기록되지 않습니다.
Unmanaged 트랜잭션(then-callback)¶
Unmanaged 트랜잭션은 rollback과 commit을 수동으로 동작합니다. 만약 하지 않는다면, 트랜잭션은 타임아웃 에러가 발생합니다. Unmanaged 트랜잭션을 시작하기 위해 콜백(여전히 옵션 객체를 전달할 수 있습니다.)과 Promise로 반환된 then
없이 sequelize.transaction()
을 호출합니다. commit()
및 rollback()
은 promise를 반환합니다.
return sequelize.transaction().then(t => {
return User.create({
firstName: 'Bart',
lastName: 'Simpson'
}, {transaction: t}).then(user => {
return user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, {transaction: t});
}).then(() => {
return t.commit();
}).catch((err) => {
return t.rollback();
});
});
다른 sequelize 메소드와 사용하기¶
transaction
옵션은 대부분의 다른 옵션과 함께 제공됩니다. 다른 옵션은 보통 메서드의 첫 번째 인자로 전달합니다. .create
, .update()
등과 같은 값을 갖는 메소드의 경우 transaction
은 두 번째 인수의 옵션으로 전달합니다. 확실하지 않은 경우 서명을 확인하는 데 사용하는 방법에 대한 API 설명서를 참조하십시오
커밋 훅 이후¶
Transaction
객체는 commit 될 때 트래킹을 허용합니다.
afterCommit
훅은 Managed 또느 UnManaged 트랜잭션 객체를 추가할 수 있습니다.
sequelize.transaction(t => {
t.afterCommit((transaction) => {
// Your logic
});
});
sequelize.transaction().then(t => {
t.afterCommit((transaction) => {
// Your logic
});
return t.commit();
})
afterCommit에 전달 된 함수는 트랜잭션을 작성한 Promise 체인이 해결되기 전에 해결할 Promise을 선택적으로 리턴 할 수 있습니다.
트랜잭션이 롤백되면 afterCommit
후크가 발생하지 않습니다.
afterCommit
후크는 표준 후크와 달리 트랜잭션의 리턴 값을 수정하지 않습니다.
afterCommit
후크를 모델 후크와 함께 사용하여 트랜잭션 외부에서 인스턴스가 저장되고 사용 가능한시기를 알 수 있습니다.
model.afterSave((instance, options) => {
if (options.transaction) {
// Save done within a transaction, wait until transaction is committed to
// notify listeners the instance has been saved
options.transaction.afterCommit(() => /* Notify */)
return;
}
// Save done outside a transaction, safe for callers to fetch the updated model
// Notify
})
Locks(락)¶
트랜잭션 내 쿼리는 잠금으로 수행 할 수 있습니다.
return User.findAll({
limit: 1,
lock: true,
transaction: t1
})
트랜잭션 내의 쿼리는 잠긴 행을 건너 뛸 수 있습니다.
return User.findAll({
limit: 1,
lock: true,
skipLocked: true,
transaction: t2
})