Skip to content

Instantly share code, notes, and snippets.

@albasyir
Last active September 4, 2024 09:29
Show Gist options
  • Save albasyir/24c09f722c60b5a3d0fc0728e0eab30a to your computer and use it in GitHub Desktop.
Save albasyir/24c09f722c60b5a3d0fc0728e0eab30a to your computer and use it in GitHub Desktop.
Typeorm transaction POC with MySQL including logs detail
import { DataSource, Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn, RelationId, Repository, QueryRunner, EntityManager, DeepPartial, SaveOptions } from "typeorm";
/**
* Post Entity
*/
@Entity("posts")
class Post {
@PrimaryGeneratedColumn()
id!: number;
@Column()
content?: string;
@OneToMany(() => Comment, (comment) => comment.post,
{
// NOT RECOMMENDED DO WITH SERVICE PLEASE
cascade: true
}
)
comments!: Comment[];
@RelationId((post: Post) => post.comments)
commentIds!: number[];
}
/**
* Comment Entity
*/
@Entity("comments")
class Comment {
@PrimaryGeneratedColumn()
id!: number;
@Column()
content?: string;
@ManyToOne(() => Post, (post) => post.comments)
@JoinColumn({ name: "postId" })
post!: Post;
@RelationId((comment: Comment) => comment.post)
@Column({ nullable: false })
postId!: number;
}
const datasource = new DataSource({
type: "mysql",
username: "admin",
host: "localhost",
port: 3306,
password: "123456",
database: "__example",
logging: true,
entities: [Post, Comment]
});
(async () => {
await datasource.initialize();
await datasource.dropDatabase();
await datasource.synchronize();
/**
* DEFINE REPOSITORY
*/
class PostRepository extends Repository<Post> {
manager: EntityManager;
constructor(manager?: EntityManager) {
super(Post, manager)
this.manager = manager || datasource.manager;
}
// STILL BUG
// save<T = DeepPartial<Post>>(entities: T[], options?: SaveOptions): Promise<(T & Post)[]>;
// save<T = DeepPartial<Post>>(entity: T, options?: SaveOptions): Promise<T & Post>{
// const _options: SaveOptions = {
// transaction: !this.manager
// }
// console.log(_options)
// return super.save(entity, _options)
// }
}
class CommentRepository extends Repository<Comment> {
constructor(manager?: EntityManager) {
super(Comment, manager || datasource.manager)
}
}
/**
* How to use custom repository
*/
const postRepository = new PostRepository();
const commentRepository = new CommentRepository();
/**
* how to save to one table
*/
console.log("Post Repository - Entity Manager", (await postRepository.query("SELECT CONNECTION_ID()"))[0])
await postRepository.save({
content: "Single Data"
});
/**
* how to save more than 2 table in single repository
*
* NOT RECOMMENDED!
*
* on post entity option `{ cascade: true | ["insert"] }` must be defined
*
* NOTE: this better using their own service
*/
console.log("Post Repository - Entity Manager", (await postRepository.query("SELECT CONNECTION_ID()"))[0])
await postRepository.save({
content: "Multi Insertation Table In One Repository",
comments: [
{
content: "Yes, this will work"
}
]
});
console.log("========================================================================");
/**
*
* NOTE: don't need to add { transaction: false }
*/
await datasource.manager.transaction(async (manager) => {
console.log("DATASOURCE CONNECTION", (await datasource.manager.query("SELECT CONNECTION_ID()"))[0])
console.log("TRANSACTION CONNECTION", (await manager.query("SELECT CONNECTION_ID()"))[0])
const postDitryData = postRepository.create({
content: "Multi Insertation Table In 2 Repository"
})
const post = await manager.save(postDitryData)
const comment = commentRepository.create({
content: "Comment on Multi Insertation Table In 2 Repository",
postId: post.id
})
const comment2 = commentRepository.create({
content: "Comment on Multi Insertation Table In 2 Repository",
postId: post.id
})
await manager.save([comment, comment2])
})
console.log("========================================================================");
/**
* try with many transaction
*/
await datasource.manager.transaction(async (manager) => {
const a = datasource.createQueryRunner()
const b = datasource.createQueryRunner()
await a.connect()
await b.connect()
console.log("QUERY RUNNER A", (await a.query("SELECT CONNECTION_ID()"))[0])
console.log("QUERY RUNNER B", (await b.query("SELECT CONNECTION_ID()"))[0])
console.log("DATASOURCE CONNECTION", (await datasource.manager.query("SELECT CONNECTION_ID()"))[0])
console.log("TRANSACTION CONNECTION", (await manager.query("SELECT CONNECTION_ID()"))[0])
await a.release();
await b.release();
});
console.log("========================================================================");
/**
* how to save more than 2 table in different repository and same query runner
*/
const injectedQueryRunner = datasource.createQueryRunner()
await injectedQueryRunner.connect()
await injectedQueryRunner.startTransaction()
console.log("DATASOURCE CONNECTION", (await datasource.manager.query("SELECT CONNECTION_ID()"))[0])
console.log("INJECTED QUERY RUNNER CONNECTION", (await injectedQueryRunner.manager.query("SELECT CONNECTION_ID()"))[0])
const postRepositoryWithSameRunner = new PostRepository(injectedQueryRunner.manager)
const commentRepositoryWithSameRunner = new CommentRepository(injectedQueryRunner.manager)
const post = await postRepositoryWithSameRunner.save({
content: "Multi Insertation Table In 2 Repository with Injected Qeury Runner"
}, { transaction: false });
await commentRepositoryWithSameRunner.insert({
content: "Comment on Multi Insertation Table In 2 Repository with Injected Qeury Runner",
postId: post.id
});
// TEST TO REJECT WHEN TRANSACTION IS NOT COMPLETE
// await injectedQueryRunner.rollbackTransaction();
// throw Error("Reject by example");
await commentRepositoryWithSameRunner.insert({
content: "Comment on Multi Insertation Table In 2 Repository with Injected Qeury Runner",
postId: post.id
})
await injectedQueryRunner.commitTransaction()
// await injectedQueryRunner.release();
console.log("========================================================================");
/**
* This latest for implementation in NESTJS
*
* this will like this
*/
await datasource.transaction(async (manager) => {
// ini kemungkinan sama connection_idnya sama `injectedQueryRunner` tp klo itu gk di release dia beda kok
// artinya aman2 aja sih harusnya klo barengan orang panggil ini
// pembuktianya coba aja comment `injectedQueryRunner.release();`
console.log("INJECTED MANAGER CONNECTION", (await manager.query("SELECT CONNECTION_ID()"))[0])
// khusus datasource ini agak aneh emang, dia loncat2 gk jelas, tp bisa
// intinya kita gk patokanya darasource, selama transaction manager beda connection_id artinya aman
console.log("DATASOURCE CONNECTION", (await datasource.manager.query("SELECT CONNECTION_ID()"))[0])
// paling nanti mikirin inject by request managernya
// jadi tekniknya mirip kyk reqeustId, terbang kalau di panggil
const postRepo = new PostRepository(manager);
const commentRepo = new CommentRepository(manager);
// ini cara purba, ntar kita upgrade aja reponya
// default transaction false klo managernya di kasih / apa gitu
const post = await postRepo.save({
content: "NESTJS Implementation target"
}, { transaction: false });
// ini INSERT biar blazing fast aja, klo gk butuh output gk perlu method `save`
commentRepo.insert({
content: "Comment on NESTJS Implementation target",
postId: Number(post.id)
})
});
await datasource.destroy()
})().catch(async (e) => {
await datasource.destroy();
throw e;
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment