Last active
September 4, 2024 09:29
-
-
Save albasyir/24c09f722c60b5a3d0fc0728e0eab30a to your computer and use it in GitHub Desktop.
Typeorm transaction POC with MySQL including logs detail
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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