ORM Party

Transactions

Select ORMs to compare

Pick one or more ORMs from the bar above

Basic Transaction

SQL

BEGIN;

INSERT INTO users (name, email)
  VALUES ('Alice', 'alice@example.com');

UPDATE accounts SET balance = balance - 100
  WHERE user_id = 1;

COMMIT;

Django ORM

from django.db import transaction
from django.db.models import F

with transaction.atomic():
    user = User.objects.create(
        name="Alice", email="alice@example.com"
    )
    Account.objects.filter(user=user).update(
        balance=F("balance") - 100
    )

SQLAlchemy

from sqlalchemy import update

with Session(engine) as session:
    with session.begin():
        user = User(name="Alice", email="alice@example.com")
        session.add(user)
        session.flush()
        session.execute(
            update(Account)
            .where(Account.user_id == user.id)
            .values(balance=Account.balance - 100)
        )

SQLModel

from sqlalchemy import update

with Session(engine) as session:
    with session.begin():
        user = User(name="Alice", email="alice@example.com")
        session.add(user)
        session.flush()

        session.exec(
            update(Account)
            .where(Account.user_id == user.id)
            .values(balance=Account.balance - 100)
        )

Tortoise ORM

from tortoise.transactions import in_transaction
from tortoise.expressions import F

async with in_transaction() as conn:
    user = await User.create(
        name="Alice",
        email="alice@example.com",
        using_db=conn,
    )
    await Account.filter(
        user=user
    ).using_db(conn).update(
        balance=F("balance") - 100
    )

Peewee

with db.atomic():
    user = User.create(
        name="Alice",
        email="alice@example.com",
    )
    Account.update(
        balance=Account.balance - 100
    ).where(Account.user == user).execute()

Prisma

await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { name: "Alice", email: "alice@example.com" },
  });
  await tx.account.update({
    where: { userId: user.id },
    data: { balance: { decrement: 100 } },
  });
});

Drizzle

await db.transaction(async (tx) => {
  const [user] = await tx.insert(users)
    .values({ name: "Alice", email: "alice@example.com" })
    .returning();
  await tx.update(accounts)
    .set({ balance: sql`${accounts.balance} - 100` })
    .where(eq(accounts.userId, user.id));
});

Kysely

import { sql } from 'kysely'

await db.transaction().execute(async (trx) => {
  const user = await trx
    .insertInto('users')
    .values({
      name: 'Alice',
      email: 'alice@example.com',
    })
    .returning('id')
    .executeTakeFirstOrThrow()

  await trx
    .updateTable('accounts')
    .set({ balance: sql<number>`balance - 100` })
    .where('user_id', '=', user.id)
    .executeTakeFirst()
})

TypeORM

await dataSource.transaction(async (manager) => {
  const user = manager.create(User, {
    name: "Alice",
    email: "alice@example.com",
  });
  await manager.save(user);
  await manager.update(Account, { userId: user.id }, {
    balance: () => "balance - 100",
  });
});

MikroORM

import { raw } from "@mikro-orm/core";

await orm.em.transactional(async (em) => {
  const user = em.create(User, {
    name: "Alice",
    email: "alice@example.com",
  });
  await em.persistAndFlush(user);

  await em.nativeUpdate(Account, { user: user.id }, {
    balance: raw("balance - 100"),
  });
});

Sequelize

await sequelize.transaction(async (tx) => {
  const user = await User.create(
    { name: "Alice", email: "alice@example.com" },
    { transaction: tx },
  );

  await Account.decrement("balance", {
    by: 100,
    where: { userId: user.id },
    transaction: tx,
  });
});

Rollback on Error

SQL

BEGIN;

UPDATE accounts SET balance = balance - 100
  WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100
  WHERE user_id = 2;

-- on error:
ROLLBACK;
-- on success:
COMMIT;

Django ORM

from django.db import transaction
from django.db.models import F

try:
    with transaction.atomic():
        Account.objects.filter(user_id=1).update(
            balance=F("balance") - 100
        )
        Account.objects.filter(user_id=2).update(
            balance=F("balance") + 100
        )
        if error_condition:
            raise ValueError("Transfer failed")
except ValueError:
    pass  # transaction rolled back automatically

SQLAlchemy

from sqlalchemy import update

with Session(engine) as session:
    try:
        session.execute(
            update(Account)
            .where(Account.user_id == 1)
            .values(balance=Account.balance - 100)
        )
        session.execute(
            update(Account)
            .where(Account.user_id == 2)
            .values(balance=Account.balance + 100)
        )
        session.commit()
    except Exception:
        session.rollback()
        raise

SQLModel

from sqlalchemy import update

with Session(engine) as session:
    try:
        session.exec(
            update(Account)
            .where(Account.user_id == 1)
            .values(balance=Account.balance - 100)
        )
        session.exec(
            update(Account)
            .where(Account.user_id == 2)
            .values(balance=Account.balance + 100)
        )
        if error_condition:
            raise ValueError("Transfer failed")
        session.commit()
    except ValueError:
        session.rollback()

Tortoise ORM

from tortoise.transactions import in_transaction
from tortoise.expressions import F

try:
    async with in_transaction() as conn:
        await Account.filter(
            user_id=1
        ).using_db(conn).update(
            balance=F("balance") - 100
        )
        await Account.filter(
            user_id=2
        ).using_db(conn).update(
            balance=F("balance") + 100
        )
        if error_condition:
            raise ValueError("Transfer failed")
except ValueError:
    pass  # transaction auto-rolled back

Peewee

try:
    with db.atomic():
        Account.update(
            balance=Account.balance - 100
        ).where(Account.user == 1).execute()

        Account.update(
            balance=Account.balance + 100
        ).where(Account.user == 2).execute()

        if error_condition:
            raise ValueError("Transfer failed")
except ValueError:
    pass  # transaction rolled back automatically

Prisma

try {
  await prisma.$transaction(async (tx) => {
    await tx.account.update({
      where: { userId: 1 },
      data: { balance: { decrement: 100 } },
    });
    await tx.account.update({
      where: { userId: 2 },
      data: { balance: { increment: 100 } },
    });
    if (errorCondition) {
      throw new Error("Transfer failed");
    }
  });
} catch (e) {
  // transaction rolled back automatically
}

Drizzle

try {
  await db.transaction(async (tx) => {
    await tx.update(accounts)
      .set({ balance: sql`${accounts.balance} - 100` })
      .where(eq(accounts.userId, 1));
    await tx.update(accounts)
      .set({ balance: sql`${accounts.balance} + 100` })
      .where(eq(accounts.userId, 2));
    if (errorCondition) {
      tx.rollback();
    }
  });
} catch (e) {
  // transaction rolled back
}

Kysely

import { sql } from 'kysely'

try {
  await db.transaction().execute(async (trx) => {
    await trx
      .updateTable('accounts')
      .set({ balance: sql<number>`balance - 100` })
      .where('user_id', '=', 1)
      .executeTakeFirst()

    await trx
      .updateTable('accounts')
      .set({ balance: sql<number>`balance + 100` })
      .where('user_id', '=', 2)
      .executeTakeFirst()

    if (error_condition) {
      throw new Error('Transfer failed')
    }
  })
} catch {
  // transaction rolled back automatically
}

TypeORM

const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
try {
  await queryRunner.manager.update(
    Account, { userId: 1 },
    { balance: () => "balance - 100" }
  );
  await queryRunner.manager.update(
    Account, { userId: 2 },
    { balance: () => "balance + 100" }
  );
  await queryRunner.commitTransaction();
} catch (e) {
  await queryRunner.rollbackTransaction();
} finally {
  await queryRunner.release();
}

MikroORM

import { raw } from "@mikro-orm/core";

try {
  await orm.em.transactional(async (em) => {
    await em.nativeUpdate(Account, { user: 1 }, {
      balance: raw("balance - 100"),
    });

    await em.nativeUpdate(Account, { user: 2 }, {
      balance: raw("balance + 100"),
    });

    if (error_condition) {
      throw new Error("Transfer failed");
    }
  });
} catch {
  // transaction rolled back automatically
}

Sequelize

try {
  await sequelize.transaction(async (tx) => {
    await Account.decrement("balance", {
      by: 100,
      where: { userId: 1 },
      transaction: tx,
    });

    await Account.increment("balance", {
      by: 100,
      where: { userId: 2 },
      transaction: tx,
    });

    if (error_condition) {
      throw new Error("Transfer failed");
    }
  });
} catch {
  // transaction rolled back automatically
}

Savepoint / Nested

SQL

BEGIN;

INSERT INTO orders (user_id, amount)
  VALUES (1, 100)
  RETURNING id AS order_id;

SAVEPOINT sp1;

INSERT INTO order_items (order_id, product_id)
  VALUES (order_id, 42);

-- rollback only the savepoint
ROLLBACK TO sp1;

COMMIT;

Django ORM

from django.db import transaction

with transaction.atomic():
    order = Order.objects.create(
        user_id=1, amount=100
    )

    try:
        with transaction.atomic():
            OrderItem.objects.create(
                order_id=order.id, product_id=42
            )
            raise ValueError("item failed")
    except ValueError:
        pass  # only inner transaction rolled back

SQLAlchemy

with Session(engine) as session:
    with session.begin():
        order = Order(user_id=1, amount=100)
        session.add(order)
        session.flush()

        nested = session.begin_nested()
        try:
            session.add(
                OrderItem(
                    order_id=order.id, product_id=42
                )
            )
            nested.commit()
        except Exception:
            nested.rollback()

SQLModel

with Session(engine) as session:
    with session.begin():
        order = Order(user_id=1, amount=100)
        session.add(order)
        session.flush()

        try:
            with session.begin_nested():
                session.add(
                    OrderItem(
                        order_id=order.id,
                        product_id=42,
                    )
                )
                raise ValueError("item failed")
        except ValueError:
            pass  # only nested transaction rolled back

Tortoise ORM

from tortoise.transactions import in_transaction

async with in_transaction() as conn:
    order = await Order.create(
        user_id=1, amount=100, using_db=conn
    )

    try:
        async with in_transaction() as nested:
            await OrderItem.create(
                order_id=order.id,
                product_id=42,
                using_db=nested,
            )
            raise ValueError("item failed")
    except ValueError:
        pass  # only nested transaction rolled back

Peewee

with db.atomic():
    order = Order.create(
        user=1,
        amount=100,
    )

    try:
        with db.atomic():
            OrderItem.create(
                order=order,
                product=42,
            )
            raise ValueError("item failed")
    except ValueError:
        pass  # only inner transaction rolled back

Prisma

// No first-class savepoint API in Prisma Client;
// use raw SQL for true savepoints
await prisma.$transaction(async (tx) => {
  const order = await tx.order.create({
    data: { userId: 1, amount: 100 },
  });

  await tx.$executeRaw`SAVEPOINT sp1`;
  try {
    await tx.orderItem.create({
      data: { orderId: order.id, productId: 42 },
    });
  } catch {
    await tx.$executeRaw`ROLLBACK TO sp1`;
  }
});

Drizzle

// Drizzle has no savepoint API;
// use raw SQL within a transaction
await db.transaction(async (tx) => {
  const [order] = await tx.insert(orders)
    .values({ userId: 1, amount: 100 })
    .returning();

  try {
    await tx.execute(sql`SAVEPOINT sp1`);
    await tx.insert(orderItems)
      .values({ orderId: order.id, productId: 42 });
  } catch {
    await tx.execute(sql`ROLLBACK TO sp1`);
  }
});

Kysely

await db.transaction().execute(async (trx) => {
  const order = await trx
    .insertInto('orders')
    .values({ user_id: 1, amount: 100 })
    .returning('id')
    .executeTakeFirstOrThrow()

  try {
    await trx.transaction().execute(async (inner) => {
      await inner
        .insertInto('order_items')
        .values({ order_id: order.id, product_id: 42 })
        .executeTakeFirst()

      throw new Error('item failed')
    })
  } catch {
    // only inner transaction rolled back
  }
})

TypeORM

const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
try {
  const order = await queryRunner.manager.save(
    Order, { userId: 1, amount: 100 }
  );

  await queryRunner.query("SAVEPOINT sp1");
  try {
    await queryRunner.manager.save(
      OrderItem, { orderId: order.id, productId: 42 }
    );
  } catch {
    await queryRunner.query("ROLLBACK TO sp1");
  }

  await queryRunner.commitTransaction();
} catch (e) {
  await queryRunner.rollbackTransaction();
} finally {
  await queryRunner.release();
}

MikroORM

await orm.em.transactional(async (em) => {
  const order = em.create(Order, {
    user: 1,
    amount: 100,
  });
  await em.persistAndFlush(order);

  try {
    await em.transactional(async (inner) => {
      const item = inner.create(OrderItem, {
        order: order.id,
        product: 42,
      });
      await inner.persistAndFlush(item);
      throw new Error("item failed");
    });
  } catch {
    // only inner transaction rolled back
  }
});

Sequelize

await sequelize.transaction(async (outerTx) => {
  const order = await Order.create(
    { userId: 1, amount: 100 },
    { transaction: outerTx },
  );

  try {
    await sequelize.transaction(
      { transaction: outerTx },
      async (innerTx) => {
        await OrderItem.create(
          { orderId: order.id, productId: 42 },
          { transaction: innerTx },
        );
        throw new Error("item failed");
      },
    );
  } catch {
    // only nested transaction rolled back
  }
});