From 555cd69f46ae68d4686ba4a8e07a8d77a1ee3aad Mon Sep 17 00:00:00 2001 From: Umed Khudoiberdiev Date: Mon, 3 Jul 2017 17:31:13 +0500 Subject: [PATCH] fixed issues with query runner manager and repo instances --- src/connection/Connection.ts | 83 ++++---------- src/driver/Driver.ts | 1 + src/driver/mongodb/MongoDriver.ts | 1 + src/driver/mongodb/MongoQueryRunner.ts | 6 - src/driver/mysql/MysqlDriver.ts | 1 + src/driver/mysql/MysqlQueryRunner.ts | 14 +-- src/driver/oracle/OracleDriver.ts | 1 + src/driver/oracle/OracleQueryRunner.ts | 14 +-- src/driver/postgres/PostgresDriver.ts | 1 + src/driver/postgres/PostgresQueryRunner.ts | 14 +-- src/driver/sqlite/SqliteDriver.ts | 1 + src/driver/sqlite/SqliteQueryRunner.ts | 14 +-- src/driver/sqlserver/SqlServerDriver.ts | 3 +- src/driver/sqlserver/SqlServerQueryRunner.ts | 14 +-- src/driver/websql/WebsqlDriver.ts | 1 + src/driver/websql/WebsqlQueryRunner.ts | 14 +-- src/entity-manager/EntityManager.ts | 107 +++++++++++++----- src/entity-manager/MongoEntityManager.ts | 18 +-- src/metadata/EntityMetadata.ts | 5 - src/query-runner/QueryRunner.ts | 5 - src/repository/MongoRepository.ts | 4 +- src/repository/Repository.ts | 10 +- src/repository/RepositoryFactory.ts | 21 ++-- .../single-query-runner/entity/Post.ts | 14 +++ .../single-query-runner.ts | 30 +++++ 25 files changed, 229 insertions(+), 168 deletions(-) create mode 100644 test/functional/transaction/single-query-runner/entity/Post.ts create mode 100644 test/functional/transaction/single-query-runner/single-query-runner.ts diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index 65935b39d..262d14fb0 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -29,6 +29,7 @@ import {DriverFactory} from "../driver/DriverFactory"; import {ConnectionMetadataBuilder} from "./ConnectionMetadataBuilder"; import {QueryRunner} from "../query-runner/QueryRunner"; import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder"; +import {SqliteDriver} from "../driver/sqlite/SqliteDriver"; /** * Connection is a single database ORM connection to a specific DBMS database. @@ -269,7 +270,7 @@ export class Connection { * Gets repository for the given entity. */ getRepository(target: ObjectType|string): Repository { - return this.getMetadata(target).repository; + return this.manager.getRepository(target); } /** @@ -277,17 +278,7 @@ export class Connection { * Only tree-type entities can have a TreeRepository, like ones decorated with @ClosureEntity decorator. */ getTreeRepository(target: ObjectType|string): TreeRepository { - if (this.driver instanceof MongoDriver) - throw new Error(`You cannot use getTreeRepository for MongoDB connections.`); - - if (!this.hasMetadata(target)) - throw new RepositoryNotFoundError(this.name, target); - - const repository = this.getMetadata(target).repository; - if (!(repository instanceof TreeRepository)) - throw new RepositoryNotTreeError(target); - - return repository; + return this.manager.getTreeRepository(target); } /** @@ -298,10 +289,7 @@ export class Connection { if (!(this.driver instanceof MongoDriver)) throw new Error(`You can use getMongoRepository only for MongoDB connections.`); - if (!this.hasMetadata(target)) - throw new RepositoryNotFoundError(this.name, target); - - return this.getMetadata(target).repository as MongoRepository; + return this.manager.getRepository(target) as MongoRepository; } /** @@ -315,32 +303,8 @@ export class Connection { * Wraps given function execution (and all operations made there) into a transaction. * All database operations must be executed using provided entity manager. */ - async transaction(runInTransaction: (entityManger: EntityManager) => Promise, queryRunner?: QueryRunner): Promise { - if (this instanceof MongoEntityManager) - throw new Error(`Transactions aren't supported by MongoDB.`); - - if (queryRunner && queryRunner.isReleased) - throw new QueryRunnerProviderAlreadyReleasedError(); - - const usedQueryRunner = queryRunner || this.createQueryRunner(); - const transactionEntityManager = new EntityManagerFactory().create(this, usedQueryRunner); - - try { - await usedQueryRunner.startTransaction(); - const result = await runInTransaction(transactionEntityManager); - await usedQueryRunner.commitTransaction(); - return result; - - } catch (err) { - try { // we throw original error even if rollback thrown an error - await usedQueryRunner.rollbackTransaction(); - } catch (rollbackError) { } - throw err; - - } finally { - if (!queryRunner) // if we used a new query runner provider then release it - await usedQueryRunner.release(); - } + async transaction(runInTransaction: (entityManger: EntityManager) => Promise): Promise { + return this.manager.transaction(runInTransaction); } /** @@ -406,26 +370,31 @@ export class Connection { * This may be useful if you want to perform all db queries within one connection. * After finishing with entity manager, don't forget to release it (to release database connection back to pool). */ - createIsolatedManager(queryRunner?: QueryRunner): EntityManager { - if (queryRunner && queryRunner.manager && queryRunner.manager !== this.manager) - return queryRunner.manager; + createIsolatedManager(): EntityManager { + if (this.driver instanceof MongoDriver) + throw new Error(`You can use createIsolatedManager only for non MongoDB connections.`); - if (!queryRunner) - queryRunner = this.createQueryRunner(); + // sqlite has a single query runner and does not support isolated managers + if (this.driver instanceof SqliteDriver) + return this.manager; - Object.assign(queryRunner, { manager: new EntityManagerFactory().create(this, queryRunner) }); - return queryRunner.manager; + return new EntityManagerFactory().create(this, this.driver.createQueryRunner()); } /** * Creates a new repository with a single opened connection to the database. * This may be useful if you want to perform all db queries within one connection. - * After finishing with entity manager, don't forget to release it (to release database connection back to pool). + * After finishing with repository, don't forget to release its query runner (to release database connection back to pool). */ - createIsolatedRepository(entityClassOrName: ObjectType|string, queryRunner?: QueryRunner): Repository { - if (!queryRunner) - queryRunner = this.createQueryRunner(); - return new RepositoryFactory().create(this, this.getMetadata(entityClassOrName), queryRunner); + createIsolatedRepository(entityClassOrName: ObjectType|string): Repository { + if (this.driver instanceof MongoDriver) + throw new Error(`You can use createIsolatedRepository only for non MongoDB connections.`); + + // sqlite has a single query runner and does not support isolated repositories + if (this.driver instanceof SqliteDriver) + return this.manager.getRepository(entityClassOrName); + + return this.createIsolatedManager().getRepository(entityClassOrName); } // ------------------------------------------------------------------------- @@ -465,7 +434,6 @@ export class Connection { protected buildMetadatas(): void { const connectionMetadataBuilder = new ConnectionMetadataBuilder(this); - const repositoryFactory = new RepositoryFactory(); const entityMetadataValidator = new EntityMetadataValidator(); // create subscribers instances if they are not disallowed from high-level (for example they can disallowed from migrations run process) @@ -482,11 +450,6 @@ export class Connection { const migrations = connectionMetadataBuilder.buildMigrations(this.options.migrations || []); Object.assign(this, { migrations: migrations }); - // initialize repositories for all entity metadatas - this.entityMetadatas.forEach(metadata => { - metadata.repository = repositoryFactory.create(this, metadata); - }); - // validate all created entity metadatas to make sure user created entities are valid and correct entityMetadataValidator.validateMany(this.entityMetadatas); } diff --git a/src/driver/Driver.ts b/src/driver/Driver.ts index 925050e81..e7daf315e 100644 --- a/src/driver/Driver.ts +++ b/src/driver/Driver.ts @@ -4,6 +4,7 @@ import {ObjectLiteral} from "../common/ObjectLiteral"; import {ColumnType} from "./types/ColumnTypes"; import {MappedColumnTypes} from "./types/MappedColumnTypes"; import {SchemaBuilder} from "../schema-builder/SchemaBuilder"; +import {EntityManager} from "../entity-manager/EntityManager"; import {DataTypeDefaults} from "./types/DataTypeDefaults"; /** diff --git a/src/driver/mongodb/MongoDriver.ts b/src/driver/mongodb/MongoDriver.ts index b95f3b65c..40349c035 100644 --- a/src/driver/mongodb/MongoDriver.ts +++ b/src/driver/mongodb/MongoDriver.ts @@ -12,6 +12,7 @@ import {MongoConnectionOptions} from "./MongoConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; import {MongoSchemaBuilder} from "../../schema-builder/MongoSchemaBuilder"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/mongodb/MongoQueryRunner.ts b/src/driver/mongodb/MongoQueryRunner.ts index 534d566bd..871aed3fa 100644 --- a/src/driver/mongodb/MongoQueryRunner.ts +++ b/src/driver/mongodb/MongoQueryRunner.ts @@ -54,11 +54,6 @@ export class MongoQueryRunner implements QueryRunner { */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -83,7 +78,6 @@ export class MongoQueryRunner implements QueryRunner { constructor(connection: Connection, databaseConnection: Db) { this.connection = connection; - this.manager = connection.manager; this.databaseConnection = databaseConnection; } diff --git a/src/driver/mysql/MysqlDriver.ts b/src/driver/mysql/MysqlDriver.ts index 92a72e430..60f1c63f8 100644 --- a/src/driver/mysql/MysqlDriver.ts +++ b/src/driver/mysql/MysqlDriver.ts @@ -13,6 +13,7 @@ import {RdbmsSchemaBuilder} from "../../schema-builder/RdbmsSchemaBuilder"; import {MysqlConnectionOptions} from "./MysqlConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/mysql/MysqlQueryRunner.ts b/src/driver/mysql/MysqlQueryRunner.ts index cef7a6c12..57ead7efe 100644 --- a/src/driver/mysql/MysqlQueryRunner.ts +++ b/src/driver/mysql/MysqlQueryRunner.ts @@ -23,16 +23,16 @@ export class MysqlQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: MysqlDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -72,9 +72,9 @@ export class MysqlQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: MysqlDriver) { + constructor(driver: MysqlDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/driver/oracle/OracleDriver.ts b/src/driver/oracle/OracleDriver.ts index 4766b4311..12259b3dd 100644 --- a/src/driver/oracle/OracleDriver.ts +++ b/src/driver/oracle/OracleDriver.ts @@ -12,6 +12,7 @@ import {RdbmsSchemaBuilder} from "../../schema-builder/RdbmsSchemaBuilder"; import {OracleConnectionOptions} from "./OracleConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/oracle/OracleQueryRunner.ts b/src/driver/oracle/OracleQueryRunner.ts index 1056636b6..d45aaa14e 100644 --- a/src/driver/oracle/OracleQueryRunner.ts +++ b/src/driver/oracle/OracleQueryRunner.ts @@ -25,16 +25,16 @@ export class OracleQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: OracleDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -74,9 +74,9 @@ export class OracleQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: OracleDriver) { + constructor(driver: OracleDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/driver/postgres/PostgresDriver.ts b/src/driver/postgres/PostgresDriver.ts index f2f86c585..62734edb8 100644 --- a/src/driver/postgres/PostgresDriver.ts +++ b/src/driver/postgres/PostgresDriver.ts @@ -14,6 +14,7 @@ import {PostgresConnectionOptions} from "./PostgresConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; import {QueryRunner} from "../../query-runner/QueryRunner"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index 326a846da..033cf00ad 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -23,16 +23,16 @@ export class PostgresQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: PostgresDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -77,9 +77,9 @@ export class PostgresQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: PostgresDriver) { + constructor(driver: PostgresDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/driver/sqlite/SqliteDriver.ts b/src/driver/sqlite/SqliteDriver.ts index 1a52dfd65..1236f1946 100644 --- a/src/driver/sqlite/SqliteDriver.ts +++ b/src/driver/sqlite/SqliteDriver.ts @@ -12,6 +12,7 @@ import {SqliteConnectionOptions} from "./SqliteConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; import {QueryRunner} from "../../query-runner/QueryRunner"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/sqlite/SqliteQueryRunner.ts b/src/driver/sqlite/SqliteQueryRunner.ts index 5da355001..198520cdc 100644 --- a/src/driver/sqlite/SqliteQueryRunner.ts +++ b/src/driver/sqlite/SqliteQueryRunner.ts @@ -27,16 +27,16 @@ export class SqliteQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: SqliteDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -66,9 +66,9 @@ export class SqliteQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: SqliteDriver) { + constructor(driver: SqliteDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/driver/sqlserver/SqlServerDriver.ts b/src/driver/sqlserver/SqlServerDriver.ts index 2c1179d45..c65ac7f89 100644 --- a/src/driver/sqlserver/SqlServerDriver.ts +++ b/src/driver/sqlserver/SqlServerDriver.ts @@ -13,6 +13,7 @@ import {RdbmsSchemaBuilder} from "../../schema-builder/RdbmsSchemaBuilder"; import {SqlServerConnectionOptions} from "./SqlServerConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** @@ -112,7 +113,7 @@ export class SqlServerDriver implements Driver { varchar: { length: 255 }, nvarchar: { length: 255 } }; - + // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- diff --git a/src/driver/sqlserver/SqlServerQueryRunner.ts b/src/driver/sqlserver/SqlServerQueryRunner.ts index 5fd32f907..c13577841 100644 --- a/src/driver/sqlserver/SqlServerQueryRunner.ts +++ b/src/driver/sqlserver/SqlServerQueryRunner.ts @@ -23,16 +23,16 @@ export class SqlServerQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: SqlServerDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -76,9 +76,9 @@ export class SqlServerQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: SqlServerDriver) { + constructor(driver: SqlServerDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/driver/websql/WebsqlDriver.ts b/src/driver/websql/WebsqlDriver.ts index 79bfbe353..cebcdf7a8 100644 --- a/src/driver/websql/WebsqlDriver.ts +++ b/src/driver/websql/WebsqlDriver.ts @@ -10,6 +10,7 @@ import {RdbmsSchemaBuilder} from "../../schema-builder/RdbmsSchemaBuilder"; import {WebSqlConnectionOptions} from "./WebSqlConnectionOptions"; import {MappedColumnTypes} from "../types/MappedColumnTypes"; import {ColumnType} from "../types/ColumnTypes"; +import {EntityManager} from "../../entity-manager/EntityManager"; import {DataTypeDefaults} from "../types/DataTypeDefaults"; /** diff --git a/src/driver/websql/WebsqlQueryRunner.ts b/src/driver/websql/WebsqlQueryRunner.ts index 59eff2844..54115c666 100644 --- a/src/driver/websql/WebsqlQueryRunner.ts +++ b/src/driver/websql/WebsqlQueryRunner.ts @@ -27,16 +27,16 @@ export class WebsqlQueryRunner implements QueryRunner { // Public Implemented Properties // ------------------------------------------------------------------------- + /** + * Database driver used by connection. + */ + driver: WebsqlDriver; + /** * Connection used by this query runner. */ connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. @@ -76,9 +76,9 @@ export class WebsqlQueryRunner implements QueryRunner { // Constructor // ------------------------------------------------------------------------- - constructor(protected driver: WebsqlDriver) { + constructor(driver: WebsqlDriver) { + this.driver = driver; this.connection = driver.connection; - this.manager = driver.connection.manager; } // ------------------------------------------------------------------------- diff --git a/src/entity-manager/EntityManager.ts b/src/entity-manager/EntityManager.ts index 9403fa249..55507c3dc 100644 --- a/src/entity-manager/EntityManager.ts +++ b/src/entity-manager/EntityManager.ts @@ -22,6 +22,12 @@ import {AbstractRepository} from "../repository/AbstractRepository"; import {CustomRepositoryCannotInheritRepositoryError} from "../error/CustomRepositoryCannotInheritRepositoryError"; import {QueryRunner} from "../query-runner/QueryRunner"; import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder"; +import {EntityMetadata} from "../metadata/EntityMetadata"; +import {MongoDriver} from "../driver/mongodb/MongoDriver"; +import {RepositoryNotFoundError} from "../error/RepositoryNotFoundError"; +import {RepositoryNotTreeError} from "../error/RepositoryNotTreeError"; +import {RepositoryFactory} from "../repository/RepositoryFactory"; +import {EntityManagerFactory} from "./EntityManagerFactory"; /** * Entity manager supposed to work with any entity, automatically find its repository and call its methods, @@ -36,23 +42,29 @@ export class EntityManager { /** * Connection used by this entity manager. */ - connection: Connection; + readonly connection: Connection; + + /** + * Custom query runner to be used for operations in this entity manager. + * Used only in non-global entity manager. + */ + readonly queryRunner?: QueryRunner; // ------------------------------------------------------------------------- // Protected Properties // ------------------------------------------------------------------------- - /** - * Custom query runner to be used for operations in this entity manager. - */ - protected queryRunner?: QueryRunner; - /** * Stores temporarily user data. * Useful for sharing data with subscribers. */ protected data: ObjectLiteral = {}; + /** + * Once created and then reused by en repositories. + */ + protected repositories: { metadata: EntityMetadata, repository: Repository }[] = []; + // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- @@ -72,7 +84,35 @@ export class EntityManager { * All database operations must be executed using provided entity manager. */ async transaction(runInTransaction: (entityManger: EntityManager) => Promise): Promise { - return this.connection.transaction(runInTransaction, this.queryRunner); + + if (this.connection.driver instanceof MongoDriver) + throw new Error(`Transactions aren't supported by MongoDB.`); + + if (this.queryRunner && this.queryRunner.isReleased) + throw new QueryRunnerProviderAlreadyReleasedError(); + + if (this.queryRunner && this.queryRunner.isTransactionActive) + throw new Error(`Cannot start transaction because its already started`); + + const usedQueryRunner = this.queryRunner || this.connection.createQueryRunner(); + const transactionEntityManager = new EntityManagerFactory().create(this.connection, usedQueryRunner); + + try { + await usedQueryRunner.startTransaction(); + const result = await runInTransaction(transactionEntityManager); + await usedQueryRunner.commitTransaction(); + return result; + + } catch (err) { + try { // we throw original error even if rollback thrown an error + await usedQueryRunner.rollbackTransaction(); + } catch (rollbackError) { } + throw err; + + } finally { + if (!this.queryRunner) // if we used a new query runner provider then release it + await usedQueryRunner.release(); + } } /** @@ -255,8 +295,15 @@ export class EntityManager { return Promise.resolve().then(async () => { // we MUST call "fake" resolve here to make sure all properties of lazily loaded properties are resolved. + // todo: use transaction instead if possible + await this.transaction(async transactionEntityManager => { + if (options && options.data) + transactionEntityManager.data = options.data; + + }); + const queryRunner = this.queryRunner || this.connection.createQueryRunner(); - const transactionEntityManager = this.connection.createIsolatedManager(queryRunner); + const transactionEntityManager = new EntityManagerFactory().create(this.connection, queryRunner); if (options && options.data) transactionEntityManager.data = options.data; @@ -453,7 +500,7 @@ export class EntityManager { return Promise.resolve().then(async () => { // we MUST call "fake" resolve here to make sure all properties of lazily loaded properties are resolved. const queryRunner = this.queryRunner || this.connection.createQueryRunner(); - const transactionEntityManager = this.connection.createIsolatedManager(queryRunner); + const transactionEntityManager = new EntityManagerFactory().create(this.connection, queryRunner); if (options && options.data) transactionEntityManager.data = options.data; @@ -720,17 +767,27 @@ export class EntityManager { * repository aggregator, where each repository is individually created for this entity manager. * When single database connection is not used, repository is being obtained from the connection. */ - getRepository(entityClassOrName: ObjectType|string): Repository { + getRepository(target: ObjectType|string): Repository { - // if single db connection is used then create its own repository with reused query runner - if (this.queryRunner) { - if (this.queryRunner.isReleased) - throw new QueryRunnerProviderAlreadyReleasedError(); + if (!this.connection.hasMetadata(target)) + throw new RepositoryNotFoundError(this.connection.name, target); - return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunner); + const metadata = this.connection.getMetadata(target); + let repository = this.repositories.find(repo => repo.metadata === metadata); + if (!repository) { + repository = { metadata: metadata, repository: new RepositoryFactory().create(this, metadata, this.queryRunner) }; + this.repositories.push(repository); } - return this.connection.getRepository(entityClassOrName as any); + // if single db connection is used then create its own repository with reused query runner + // if (this.queryRunner) { + // if (this.queryRunner.isReleased) + // throw new QueryRunnerProviderAlreadyReleasedError(); + // + // return this.connection.createIsolatedRepository(target, this.queryRunner); + // } + + return repository.repository; } /** @@ -739,17 +796,15 @@ export class EntityManager { * repository aggregator, where each repository is individually created for this entity manager. * When single database connection is not used, repository is being obtained from the connection. */ - getTreeRepository(entityClassOrName: ObjectType|string): TreeRepository { + getTreeRepository(target: ObjectType|string): TreeRepository { + if (this.connection.driver instanceof MongoDriver) + throw new Error(`You cannot use getTreeRepository for MongoDB connections.`); - // if single db connection is used then create its own repository with reused query runner - if (this.queryRunner) { - if (this.queryRunner.isReleased) - throw new QueryRunnerProviderAlreadyReleasedError(); + const repository = this.getRepository(target); + if (!(repository instanceof TreeRepository)) + throw new RepositoryNotTreeError(target); - return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunner) as TreeRepository; - } - - return this.connection.getTreeRepository(entityClassOrName as any); + return repository; } /** @@ -772,7 +827,7 @@ export class EntityManager { if (this.queryRunner.isReleased) throw new QueryRunnerProviderAlreadyReleasedError(); - return this.connection.createIsolatedRepository(entityClassOrName, this.queryRunner) as MongoRepository; + return this.connection.createIsolatedRepository(entityClassOrName) as MongoRepository; } return this.connection.getMongoRepository(entityClassOrName as any); diff --git a/src/entity-manager/MongoEntityManager.ts b/src/entity-manager/MongoEntityManager.ts index e84bb0b1b..53c6a3b77 100644 --- a/src/entity-manager/MongoEntityManager.ts +++ b/src/entity-manager/MongoEntityManager.ts @@ -58,6 +58,17 @@ export class MongoEntityManager extends EntityManager { super(connection); } + // ------------------------------------------------------------------------- + // Overridden Properties + // ------------------------------------------------------------------------- + + /** + * Gets query runner used to execute queries. + */ + get queryRunner(): MongoQueryRunner { + return (this.connection.driver as MongoDriver).queryRunner!; + } + // ------------------------------------------------------------------------- // Overridden Methods // ------------------------------------------------------------------------- @@ -495,13 +506,6 @@ export class MongoEntityManager extends EntityManager { // Protected Methods // ------------------------------------------------------------------------- - /** - * Gets query runner used to execute queries. - */ - protected get queryRunner(): MongoQueryRunner { - return (this.connection.driver as MongoDriver).queryRunner!; - } - /** * Converts FindManyOptions to mongodb query. */ diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index 48e9fa178..6426bc623 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -25,11 +25,6 @@ export class EntityMetadata { // Properties // ------------------------------------------------------------------------- - /** - * Repository used for this entity metadata. - */ - repository: Repository; - /** * Used to wrap lazy relations. */ diff --git a/src/query-runner/QueryRunner.ts b/src/query-runner/QueryRunner.ts index e047c0d32..3c5cc350b 100644 --- a/src/query-runner/QueryRunner.ts +++ b/src/query-runner/QueryRunner.ts @@ -19,11 +19,6 @@ export interface QueryRunner { */ readonly connection: Connection; - /** - * Entity manager isolated for this query runner. - */ - readonly manager: EntityManager; - /** * Indicates if connection for this query runner is released. * Once its released, query runner cannot run queries anymore. diff --git a/src/repository/MongoRepository.ts b/src/repository/MongoRepository.ts index 0513e5871..146744250 100644 --- a/src/repository/MongoRepository.ts +++ b/src/repository/MongoRepository.ts @@ -42,13 +42,13 @@ import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder"; export class MongoRepository extends Repository { // ------------------------------------------------------------------------- - // Protected Methods Set Dynamically + // Public Properties // ------------------------------------------------------------------------- /** * Entity Manager used by this repository. */ - protected manager: MongoEntityManager; + readonly manager: MongoEntityManager; // ------------------------------------------------------------------------- // Overridden Methods diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index 3f253e37a..d45ec602b 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -15,25 +15,23 @@ import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder"; export class Repository { // ------------------------------------------------------------------------- - // Protected Methods Set Dynamically + // Public Properties // ------------------------------------------------------------------------- - // todo: wny not to make them public? - /** * Entity Manager used by this repository. */ - protected manager: EntityManager; + readonly manager: EntityManager; /** * Entity metadata of the entity current repository manages. */ - protected metadata: EntityMetadata; + readonly metadata: EntityMetadata; /** * Query runner provider used for this repository. */ - protected queryRunner?: QueryRunner; + readonly queryRunner?: QueryRunner; // ------------------------------------------------------------------------- // Public Methods diff --git a/src/repository/RepositoryFactory.ts b/src/repository/RepositoryFactory.ts index aa5bf093d..d41b15d89 100644 --- a/src/repository/RepositoryFactory.ts +++ b/src/repository/RepositoryFactory.ts @@ -5,6 +5,7 @@ import {Repository} from "./Repository"; import {MongoDriver} from "../driver/mongodb/MongoDriver"; import {MongoRepository} from "./MongoRepository"; import {QueryRunner} from "../query-runner/QueryRunner"; +import {EntityManager} from "../entity-manager/EntityManager"; /** * Factory used to create different types of repositories. @@ -18,29 +19,33 @@ export class RepositoryFactory { /** * Creates a repository. */ - create(connection: Connection, metadata: EntityMetadata, queryRunner?: QueryRunner): Repository { + create(manager: EntityManager, metadata: EntityMetadata, queryRunner?: QueryRunner): Repository { if (metadata.isClosure) { // NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed, // however we need these properties for internal work of the class const repository = new TreeRepository(); - (repository as any)["manager"] = connection.manager; - (repository as any)["metadata"] = metadata; - (repository as any)["queryRunner"] = queryRunner; + Object.assign(repository, { + manager: manager, + metadata: metadata, + queryRunner: queryRunner, + }); return repository; } else { // NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed, // however we need these properties for internal work of the class let repository: Repository; - if (connection.driver instanceof MongoDriver) { + if (manager.connection.driver instanceof MongoDriver) { repository = new MongoRepository(); } else { repository = new Repository(); } - (repository as any)["manager"] = connection.manager; - (repository as any)["metadata"] = metadata; - (repository as any)["queryRunner"] = queryRunner; + Object.assign(repository, { + manager: manager, + metadata: metadata, + queryRunner: queryRunner, + }); return repository; } diff --git a/test/functional/transaction/single-query-runner/entity/Post.ts b/test/functional/transaction/single-query-runner/entity/Post.ts new file mode 100644 index 000000000..b65476c52 --- /dev/null +++ b/test/functional/transaction/single-query-runner/entity/Post.ts @@ -0,0 +1,14 @@ +import {Entity} from "../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {Column} from "../../../../../src/decorator/columns/Column"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + +} \ No newline at end of file diff --git a/test/functional/transaction/single-query-runner/single-query-runner.ts b/test/functional/transaction/single-query-runner/single-query-runner.ts new file mode 100644 index 000000000..edf7c4852 --- /dev/null +++ b/test/functional/transaction/single-query-runner/single-query-runner.ts @@ -0,0 +1,30 @@ +import "reflect-metadata"; +import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; +import {Connection} from "../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {expect} from "chai"; + +describe("transaction > single query runner", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should execute all operations in the method in a transaction", () => Promise.all(connections.map(async connection => { + return connection.transaction(async transactionalEntityManager => { + const originalQueryRunner = transactionalEntityManager.queryRunner; + + expect(originalQueryRunner).to.exist; + expect(transactionalEntityManager.getRepository(Post).queryRunner).to.exist; + transactionalEntityManager.getRepository(Post).queryRunner!.should.be.equal(originalQueryRunner); + transactionalEntityManager.getRepository(Post).manager.should.be.equal(transactionalEntityManager); + }); + + }))); + +});