undeexcepturi
Version:
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.
95 lines (64 loc) • 5.63 kB
Markdown
---
title: Unit of Work and transactions
sidebar_label: Unit of Work
---
MikroORM uses the Identity Map pattern to track objects. Whenever you fetch an object from the database, MikroORM will keep a reference to this object inside its `UnitOfWork`.
This allows MikroORM room for optimizations. If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance:
```typescript
const authorRepository = orm.em.getRepository(Author);
const jon1 = await authorRepository.findOne(1);
const jon2 = await authorRepository.findOne(1);
// identity map in action
console.log(jon1 === jon2); // true
```
Only one SELECT query will be fired against the database here. In the second `findOne()` call MikroORM will check the identity map first and will skip the database round trip as it will find the entity already loaded.
The identity map being indexed by primary keys only allows shortcuts when you ask for objects by primary key. When you query by other properties, you will still get the same reference, but two separate database calls will be made:
```typescript
const authorRepository = orm.em.getRepository(Author);
const jon1 = await authorRepository.findOne({ name: 'Jon Snow' });
const jon2 = await authorRepository.findOne({ name: 'Jon Snow' });
// identity map in action
console.log(jon1 === jon2); // true
```
MikroORM only knows objects by id, so a query for different criteria has to go to the database, even if it was executed just before. But instead of creating a second `Author` object MikroORM first gets the primary key from the row and checks if it already has an object inside the `UnitOfWork` with that primary key.
The identity map has a second use-case. When you call `EntityManager
The following code WILL update your database with the changes made to the `Author` object, even if you did not call `EntityManager
```typescript
const authorRepository = orm.em.getRepository(Author);
const jon = await authorRepository.findOne(1);
jon.email = 'foo@bar.com';
await authorRepository.flush(); // calling orm.em.flush() has same effect
```
MikroORM is a data-mapper that tries to achieve persistence-ignorance (PI). This means you map JS objects into a relational database that do not necessarily know about the database at all. A natural question would now be, "how does MikroORM even detect objects have changed?".
For this MikroORM keeps a second map inside the `UnitOfWork`. Whenever you fetch an object from the database MikroORM will keep a copy of all the properties and associations inside the `UnitOfWork`.
Now whenever you call `EntityManager
First and most important implication of having Unit of Work is that it allows handling transactions automatically.
When you call `EntityManager
> You can also control the transaction boundaries manually via `em.transactional(cb)`.
```typescript
const user = await em.findOne(User, 1);
user.email = 'foo@bar.com';
const car = new Car();
user.cars.add(car);
// thanks to bi-directional cascading we only need to persist user entity
// flushing will create a transaction, insert new car and update user with new email
await em.persistAndFlush(user);
```
You can find more information about transactions in [Transactions and concurrency](transactions.md) page.
Originally there was only `EntityManager
```typescript
const orm = await MikroORM.init({
autoFlush: false,
// ...
});
await orm.em.persist(new Entity()); // no auto-flushing now
await orm.em.flush();
await orm.em.persist(new Entity(), true); // you can still use second parameter to auto-flush
```
When using driver that supports transactions (all SQL drivers), you should either disable auto-flushing, or use `persistLater()` method instead, as otherwise each `persist()` call will immediately create new transaction to run the query.
This behaviour will be changed in v3, `autoFlush` will stay configurable but the default value will be `false`. Users are encouraged to use `persistAndFlush()` instead if they need the immediate persist.
> This part of documentation is highly inspired by [doctrine internals docs](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/unitofwork.html) as the behaviour here is pretty much the same.