diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon
index 63ed37a9fc76e2196dc5bb8f4586cb30affb9385..b9c1b98485c5d2102437072148588605485bdb5c 100644
--- a/Build/phpstan/phpstan-baseline.neon
+++ b/Build/phpstan/phpstan-baseline.neon
@@ -1970,26 +1970,6 @@ parameters:
 			count: 6
 			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/Generic/SessionTest.php
 
-		-
-			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository&TYPO3\\\\TestingFramework\\\\Core\\\\AccessibleObjectInterface\\:\\:findOneByFoo\\(\\)\\.$#"
-			count: 2
-			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
-
-		-
-			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:countByFoo\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
-
-		-
-			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:findByFoo\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
-
-		-
-			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:findOneByFoo\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
-
 		-
 			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Generic\\\\Session\\:\\:method\\(\\)\\.$#"
 			count: 1
@@ -1997,7 +1977,7 @@ parameters:
 
 		-
 			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\QueryInterface\\:\\:expects\\(\\)\\.$#"
-			count: 10
+			count: 2
 			path: ../../typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
 
 		-
@@ -2120,6 +2100,31 @@ parameters:
 			count: 5
 			path: ../../typo3/sysext/extbase/Tests/Unit/Utility/ExtensionUtilityTest.php
 
+		-
+			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository&TYPO3\\\\TestingFramework\\\\Core\\\\AccessibleObjectInterface\\:\\:findOneByFoo\\(\\)\\.$#"
+			count: 2
+			path: ../../typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
+
+		-
+			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:countByFoo\\(\\)\\.$#"
+			count: 1
+			path: ../../typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
+
+		-
+			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:findByFoo\\(\\)\\.$#"
+			count: 1
+			path: ../../typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
+
+		-
+			message: "#^Call to an undefined method PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Repository\\:\\:findOneByFoo\\(\\)\\.$#"
+			count: 1
+			path: ../../typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
+
+		-
+			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\QueryInterface\\:\\:expects\\(\\)\\.$#"
+			count: 8
+			path: ../../typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
+
 		-
 			message: "#^Parameter \\#2 \\$messageTitle of method TYPO3\\\\CMS\\\\Extbase\\\\Mvc\\\\Controller\\\\ActionController\\:\\:addFlashMessage\\(\\) expects string, int given\\.$#"
 			count: 1
@@ -2150,11 +2155,6 @@ parameters:
 			count: 1
 			path: ../../typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php
 
-		-
-			message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Extensionmanager\\\\Domain\\\\Repository\\\\ExtensionRepository\\:\\:countByExtensionKey\\(\\)\\.$#"
-			count: 1
-			path: ../../typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
-
 		-
 			message: "#^Offset string does not exist on null\\.$#"
 			count: 1
diff --git a/typo3/sysext/core/Documentation/Changelog/12.3/Deprecation-100071-MagicRepositoryFindByMethods.rst b/typo3/sysext/core/Documentation/Changelog/12.3/Deprecation-100071-MagicRepositoryFindByMethods.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c74b8db8ff366af85043f9bbd2764e3570bcbfde
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.3/Deprecation-100071-MagicRepositoryFindByMethods.rst
@@ -0,0 +1,82 @@
+.. include:: /Includes.rst.txt
+
+.. _deprecation-100071-1677853787:
+
+========================================================
+Deprecation: #100071 - Magic repository findBy() methods
+========================================================
+
+See :issue:`100071`
+
+Description
+===========
+
+Extbase repositories come with a magic :php:`__call()`-method to allow calling
+the following methods without implementing:
+
+- :php:`findBy[PropertyName]($propertyValue)`
+- :php:`findOneBy[PropertyName]($propertyValue)`
+- :php:`countBy[PropertyName]($propertyValue)`
+
+These have now been marked as deprecated, as they are "magic", meaning
+that proper IDE support is not possible, and other PHP-related tooling
+functionality such as PHPStorm.
+
+In addition, with the magic methods, it is not possible for Extbase repositories
+to build their own magic method functionality, as the logic is already
+in use.
+
+Impact
+======
+
+As these methods are widely used in almost all Extbase-based extensions,
+they are marked as deprecated in TYPO3 v12, but will only trigger a deprecation
+notice in TYPO3 v13, as they will be removed in TYPO3 v14.
+
+This way, the migration towards the new API methods can be taken without
+pressure.
+
+
+Affected installations
+======================
+
+All installations with third-party extensions that use those magic methods.
+
+
+Migration
+=========
+
+A new set of methods without all those downsides have been added:
+
+- :php:`findBy(array $criteria, ...): QueryResultInterface`
+- :php:`findOneBy(array $criteria, ...):object|null`
+- :php:`count(array $criteria, ...): int`
+
+The naming of those methods follows those of `doctrine/orm` and only
+:php:`count()` differs from the formerly :php:`countBy()`. While all magic
+methods only allow for a single comparison (`propertyName` = `propertyValue`),
+those methods allow for multiple comparisons, called constraints.
+
+
+`findBy[PropertyName]($propertyValue)` can be replaced with a call to `findBy`:
+
+.. code-block:: php
+
+    $this->blogRepository->findBy(['propertyName' => $propertyValue]);
+
+
+`findOneBy[PropertyName]($propertyValue)` can be replaced with a call to `findOneBy`:
+
+.. code-block:: php
+
+    $this->blogRepository->findOneBy(['propertyName' => $propertyValue]);
+
+
+`countBy[PropertyName]($propertyValue)` can be replaced with a call to `count`:
+
+.. code-block:: php
+
+    $this->blogRepository->count(['propertyName' => $propertyValue]);
+
+
+.. index:: PHP-API, NotScanned, ext:extbase
diff --git a/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100071-IntroduceNon-magicRepositoryFindMethods.rst b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100071-IntroduceNon-magicRepositoryFindMethods.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c0f0e17cf316b376160afa38f14dc1666dc7978d
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100071-IntroduceNon-magicRepositoryFindMethods.rst
@@ -0,0 +1,51 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-100071-1677853567:
+
+==============================================================
+Feature: #100071 - Introduce non-magic repository find methods
+==============================================================
+
+See :issue:`100071`
+
+Description
+===========
+
+Extbase repositories come with a magic :php:`__call()`-method to allow calling
+the following methods without implementing:
+
+- :php:`findBy[PropertyName]($propertyValue)`
+- :php:`findOneBy[PropertyName]($propertyValue)`
+- :php:`countBy[PropertyName]($propertyValue)`
+
+Magic methods are quite handy but they have a huge disadvantage. There is no
+proper IDE support i.e. most IDEs show an error or at least a warning,
+saying method :php:`findByAuthor()` does not exist. Also, type declarations are
+impossible to use because with :php:`__call()` everything is :php:`mixed`. And
+last but not least, static code analysis - like phpstan - cannot properly
+analyse those and give meaningful errors.
+
+Therefore, there is a new set of methods without all those downsides:
+
+- :php:`findBy(array $criteria, ...): QueryResultInterface`
+- :php:`findOneBy(array $criteria, ...):object|null`
+- :php:`count(array $criteria, ...): int`
+
+The naming of those methods follows those of `doctrine/orm` and only
+:php:`count()` differs from the formerly :php:`countBy()`. While all magic
+methods only allow for a single comparison (`propertyName` = `propertyValue`),
+those methods allow for multiple comparisons, called constraints.
+
+Example:
+
+.. code-block:: php
+
+    $this->blogRepository->findBy(['author' => 1, 'published' => true]);
+
+Impact
+======
+
+Those new methods support a broader feature set, support IDEs, static code
+analysers and type declarations.
+
+.. index:: PHP-API, NotScanned, ext:extbase
diff --git a/typo3/sysext/extbase/Classes/Persistence/Repository.php b/typo3/sysext/extbase/Classes/Persistence/Repository.php
index 507da27aeeb183759fbd246077a88e12489b1742..72eae9a267031a937473e590affa00e4f210bad7 100644
--- a/typo3/sysext/extbase/Classes/Persistence/Repository.php
+++ b/typo3/sysext/extbase/Classes/Persistence/Repository.php
@@ -223,16 +223,27 @@ class Repository implements RepositoryInterface, SingletonInterface
      * @param array<int, mixed> $arguments The arguments of the magic method
      * @throws UnsupportedMethodException
      * @return mixed
+     * @deprecated since v12, will be removed in v14, use {@see findBy}, {@see findOneBy} and {@see count} instead
      */
     public function __call($methodName, $arguments)
     {
         if (str_starts_with($methodName, 'findBy') && strlen($methodName) > 7) {
+            // @todo Enable in version 13.0
+            // trigger_error(
+            //     'Usage of magic method ' . static::class . '->findBy[Property]() is deprecated, use method findBy() instead.',
+            //     E_USER_DEPRECATED
+            // );
             $propertyName = lcfirst(substr($methodName, 6));
             $query = $this->createQuery();
             $result = $query->matching($query->equals($propertyName, $arguments[0]))->execute();
             return $result;
         }
         if (str_starts_with($methodName, 'findOneBy') && strlen($methodName) > 10) {
+            // @todo Enable in version 13.0
+            // trigger_error(
+            //     'Usage of magic method ' . static::class . '->findOneBy[Property]() is deprecated, use method findOneBy() instead.',
+            //     E_USER_DEPRECATED
+            // );
             $propertyName = lcfirst(substr($methodName, 9));
             $query = $this->createQuery();
 
@@ -244,6 +255,11 @@ class Repository implements RepositoryInterface, SingletonInterface
                 return $result[0] ?? null;
             }
         } elseif (str_starts_with($methodName, 'countBy') && strlen($methodName) > 8) {
+            // @todo Enable in version 13.0
+            // trigger_error(
+            //     'Usage of magic method ' . static::class . '->countBy[Property]() is deprecated, use method count() instead.',
+            //     E_USER_DEPRECATED
+            // );
             $propertyName = lcfirst(substr($methodName, 7));
             $query = $this->createQuery();
             $result = $query->matching($query->equals($propertyName, $arguments[0]))->execute()->count();
@@ -252,6 +268,62 @@ class Repository implements RepositoryInterface, SingletonInterface
         throw new UnsupportedMethodException('The method "' . $methodName . '" is not supported by the repository.', 1233180480);
     }
 
+    /**
+     * @phpstan-param array<non-empty-string, mixed> $criteria
+     * @phpstan-param array<non-empty-string, QueryInterface::ORDER_*>|null $orderBy
+     * @phpstan-param 0|positive-int|null $limit
+     * @phpstan-param 0|positive-int|null $offset
+     * @phpstan-return QueryResultInterface<T>
+     * @return QueryResultInterface
+     */
+    public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): QueryResultInterface
+    {
+        $query = $this->createQuery();
+        $constraints = [];
+        foreach ($criteria as $propertyName => $propertyValue) {
+            $constraints[] = $query->equals($propertyName, $propertyValue);
+        }
+
+        if (($numberOfConstraints = count($constraints)) === 1) {
+            $query->matching(...$constraints);
+        } elseif ($numberOfConstraints > 1) {
+            $query->matching($query->logicalAnd(...$constraints));
+        }
+
+        if (is_array($orderBy)) {
+            $query->setOrderings($orderBy);
+        }
+
+        if (is_int($limit)) {
+            $query->setLimit($limit);
+        }
+
+        if (is_int($offset)) {
+            $query->setOffset($offset);
+        }
+
+        return $query->execute();
+    }
+
+    /**
+     * @phpstan-param array<non-empty-string, mixed> $criteria
+     * @phpstan-param array<non-empty-string, QueryInterface::ORDER_*>|null $orderBy
+     * @phpstan-return T|null
+     */
+    public function findOneBy(array $criteria, array $orderBy = null): object|null
+    {
+        return $this->findBy($criteria, $orderBy, 1)->getFirst();
+    }
+
+    /**
+     * @phpstan-param array<non-empty-string, mixed> $criteria
+     * @phpstan-return 0|positive-int
+     */
+    public function count(array $criteria): int
+    {
+        return $this->findBy($criteria)->count();
+    }
+
     /**
      * Returns the class name of this class.
      *
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/RepositoryTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/RepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d7814481849ec02342e2110f51bae3670db20ff
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Persistence/RepositoryTest.php
@@ -0,0 +1,308 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
+
+use ExtbaseTeam\BlogExample\Domain\Model\Post;
+use ExtbaseTeam\BlogExample\Domain\Repository\PostRepository;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class RepositoryTest extends FunctionalTestCase
+{
+    protected array $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
+
+    protected PostRepository $postRepository;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/blogs.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/posts.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/tags.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/post-tag-mm.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Persistence/Fixtures/persons.csv');
+
+        $this->postRepository = $this->get(PostRepository::class);
+    }
+
+    public function findByRespectsSingleCriteriaDataProvider(): \Generator
+    {
+        yield 'findBy(["blog" => 1]) => 10' => [
+            ['blog' => 1],
+            10,
+        ];
+
+        yield 'findBy(["blog" => 1]) => 1' => [
+            ['blog' => 2],
+            1,
+        ];
+
+        yield 'findBy(["blog" => 1]) => 3' => [
+            ['blog' => 3],
+            3,
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider findByRespectsSingleCriteriaDataProvider
+     */
+    public function findByRespectsSingleCriteria(array $criteria, int $expectedCount): void
+    {
+        self::assertCount($expectedCount, $this->postRepository->findBy($criteria));
+    }
+
+    /**
+     * @test
+     */
+    public function findByRespectsMultipleCriteria(): void
+    {
+        self::assertCount(6, $this->postRepository->findBy(['blog' => 1, 'author' => 1]));
+    }
+
+    /**
+     * @test
+     */
+    public function findByRespectsSingleOrderBy(): void
+    {
+        $posts = $this->postRepository->findBy(
+            ['blog' => 1, 'author' => 1],
+            ['title' => QueryInterface::ORDER_DESCENDING]
+        )->toArray();
+
+        $titles = array_map(fn (Post $post) => $post->getTitle(), $posts);
+
+        self::assertSame([
+            'Post9',
+            'Post8',
+            'Post7',
+            'Post5',
+            'Post4',
+            'Post10',
+        ], $titles);
+    }
+
+    /**
+     * @test
+     */
+    public function findByRespectsMultipleOrderBy(): void
+    {
+        $posts = $this->postRepository->findBy(
+            [],
+            ['blog.uid' => QueryInterface::ORDER_ASCENDING, 'title' => QueryInterface::ORDER_DESCENDING]
+        )->toArray();
+
+        self::assertSame(
+            [
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post9',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post8',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post7',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post6',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post5',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post4',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post3',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post2',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post10',
+                ],
+                [
+                    'blog.uid' => 1,
+                    'post.title' => 'Post1',
+                ],
+                [
+                    'blog.uid' => 2,
+                    'post.title' => 'post1',
+                ],
+                [
+                    'blog.uid' => 3,
+                    'post.title' => 'post with tagged author',
+                ],
+                [
+                    'blog.uid' => 3,
+                    'post.title' => 'post with tag and tagged author',
+                ],
+                [
+                    'blog.uid' => 3,
+                    'post.title' => 'post with tag',
+                ],
+            ],
+            array_map(fn (Post $post) => ['blog.uid' => $post->getBlog()->getUid(), 'post.title' => $post->getTitle()], $posts)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function findByRespectsLimit(): void
+    {
+        $posts = $this->postRepository->findBy(
+            ['author' => 1],
+            ['uid' => QueryInterface::ORDER_DESCENDING],
+            3
+        )->toArray();
+
+        $titles = array_map(fn (Post $post) => ['uid' => $post->getUid(), 'title' => $post->getTitle()], $posts);
+
+        self::assertSame([
+            [
+                'uid' => 14,
+                'title' => 'post with tag and tagged author',
+            ],
+            [
+                'uid' => 13,
+                'title' => 'post with tagged author',
+            ],
+            [
+                'uid' => 10,
+                'title' => 'Post10',
+            ],
+        ], $titles);
+    }
+
+    /**
+     * @test
+     */
+    public function findByRespectsOffset(): void
+    {
+        $posts = $this->postRepository->findBy(
+            ['author' => 1],
+            ['uid' => QueryInterface::ORDER_DESCENDING],
+            3,
+            1
+        )->toArray();
+
+        $titles = array_map(fn (Post $post) => ['uid' => $post->getUid(), 'title' => $post->getTitle()], $posts);
+
+        self::assertSame([
+            [
+                'uid' => 13,
+                'title' => 'post with tagged author',
+            ],
+            [
+                'uid' => 10,
+                'title' => 'Post10',
+            ],
+            [
+                'uid' => 9,
+                'title' => 'Post9',
+            ],
+        ], $titles);
+    }
+
+    public function findOneByRespectsSingleCriteriaDataProvider(): \Generator
+    {
+        yield 'findOneBy(["blog" => 1]) => "Post4"' => [
+            ['uid' => 1],
+            1,
+        ];
+
+        yield 'findOneBy(["blog" => 100]) => null' => [
+            ['uid' => 100],
+            null,
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider findOneByRespectsSingleCriteriaDataProvider
+     */
+    public function findOneByRespectsSingleCriteria(array $criteria, int|null $expectedUid): void
+    {
+        /** @var Post|null $post */
+        $post = $this->postRepository->findOneBy($criteria);
+
+        self::assertSame($expectedUid, $post?->getUid());
+    }
+
+    /**
+     * @test
+     * @group not-postgres
+     */
+    public function findOneByRespectsMultipleCriteria(): void
+    {
+        $post = $this->postRepository->findOneBy(['blog' => 1, 'author' => 1]);
+
+        self::assertSame('Post4', $post?->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function findOneByRespectsOrderBy(): void
+    {
+        $post = $this->postRepository->findOneBy(
+            ['blog' => 1, 'author' => 1],
+            ['title' => QueryInterface::ORDER_DESCENDING]
+        );
+
+        self::assertSame('Post9', $post?->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function countRespectsSingleCriteria(): void
+    {
+        self::assertSame(
+            10,
+            $this->postRepository->count(
+                ['blog' => 1],
+            )
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function countRespectsMultipleCriteria(): void
+    {
+        self::assertSame(
+            1,
+            $this->postRepository->count(
+                ['blog' => 1, 'author' => 3],
+            )
+        );
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
index 17a74bb84af9ac61e10a7935a5d540893be91058..dfec862b45d5730e6b6f8dba1b7008694091c6d5 100644
--- a/typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
@@ -226,67 +226,6 @@ class RepositoryTest extends UnitTestCase
         $this->repository->update($object);
     }
 
-    /**
-     * @test
-     */
-    public function magicCallMethodAcceptsFindBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
-    {
-        $mockQueryResult = $this->createMock(QueryResultInterface::class);
-        $mockQuery = $this->createMock(QueryInterface::class);
-        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
-        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
-        $mockQuery->expects(self::once())->method('execute')->with()->willReturn($mockQueryResult);
-
-        $repository = $this->getMockBuilder(Repository::class)
-            ->onlyMethods(['createQuery'])
-            ->getMock();
-        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
-
-        self::assertSame($mockQueryResult, $repository->findByFoo('bar'));
-    }
-
-    /**
-     * @test
-     */
-    public function magicCallMethodAcceptsFindOneBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
-    {
-        $object = new \stdClass();
-        $mockQueryResult = $this->createMock(QueryResultInterface::class);
-        $mockQueryResult->expects(self::once())->method('getFirst')->willReturn($object);
-        $mockQuery = $this->createMock(QueryInterface::class);
-        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
-        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
-        $mockQuery->expects(self::once())->method('setLimit')->willReturn($mockQuery);
-        $mockQuery->expects(self::once())->method('execute')->willReturn($mockQueryResult);
-
-        $repository = $this->getMockBuilder(Repository::class)
-            ->onlyMethods(['createQuery'])
-            ->getMock();
-        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
-
-        self::assertSame($object, $repository->findOneByFoo('bar'));
-    }
-
-    /**
-     * @test
-     */
-    public function magicCallMethodAcceptsCountBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
-    {
-        $mockQuery = $this->createMock(QueryInterface::class);
-        $mockQueryResult = $this->createMock(QueryResultInterface::class);
-        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
-        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
-        $mockQuery->expects(self::once())->method('execute')->willReturn($mockQueryResult);
-        $mockQueryResult->expects(self::once())->method('count')->willReturn(2);
-
-        $repository = $this->getMockBuilder(Repository::class)
-            ->onlyMethods(['createQuery'])
-            ->getMock();
-        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
-
-        self::assertSame(2, $repository->countByFoo('bar'));
-    }
-
     /**
      * @test
      */
@@ -389,34 +328,4 @@ class RepositoryTest extends UnitTestCase
         $this->repository->_set('objectType', 'Foo');
         $this->repository->update(new \stdClass());
     }
-
-    /**
-     * @test
-     */
-    public function magicCallMethodReturnsFirstArrayKeyInFindOneBySomethingIfQueryReturnsRawResult(): void
-    {
-        $queryResultArray = [
-            0 => [
-                'foo' => 'bar',
-            ],
-        ];
-        $this->mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
-        $this->mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($this->mockQuery);
-        $this->mockQuery->expects(self::once())->method('setLimit')->with(1)->willReturn($this->mockQuery);
-        $this->mockQuery->expects(self::once())->method('execute')->willReturn($queryResultArray);
-        self::assertSame(['foo' => 'bar'], $this->repository->findOneByFoo('bar'));
-    }
-
-    /**
-     * @test
-     */
-    public function magicCallMethodReturnsNullInFindOneBySomethingIfQueryReturnsEmptyRawResult(): void
-    {
-        $queryResultArray = [];
-        $this->mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
-        $this->mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($this->mockQuery);
-        $this->mockQuery->expects(self::once())->method('setLimit')->with(1)->willReturn($this->mockQuery);
-        $this->mockQuery->expects(self::once())->method('execute')->willReturn($queryResultArray);
-        self::assertNull($this->repository->findOneByFoo('bar'));
-    }
 }
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5382adbb2aa43f1ea53a4bfb2f24c4f6e4f8d231
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/UnitDeprecated/Persistence/RepositoryTest.php
@@ -0,0 +1,194 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Persistence;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
+use TYPO3\CMS\Extbase\Persistence\Generic\Backend;
+use TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface;
+use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
+use TYPO3\CMS\Extbase\Persistence\Generic\QueryFactory;
+use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
+use TYPO3\CMS\Extbase\Persistence\Generic\Session;
+use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
+use TYPO3\CMS\Extbase\Persistence\Repository;
+use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class RepositoryTest extends UnitTestCase
+{
+    /**
+     * @var Repository|MockObject|AccessibleObjectInterface
+     */
+    protected $repository;
+
+    /**
+     * @var QueryFactory
+     */
+    protected $mockQueryFactory;
+
+    /**
+     * @var BackendInterface
+     */
+    protected $mockBackend;
+
+    /**
+     * @var Session
+     */
+    protected $mockSession;
+
+    /**
+     * @var PersistenceManagerInterface
+     */
+    protected $mockPersistenceManager;
+
+    /**
+     * @var QueryInterface
+     */
+    protected $mockQuery;
+
+    /**
+     * @var QuerySettingsInterface
+     */
+    protected $mockQuerySettings;
+
+    /**
+     * @var ConfigurationManager
+     */
+    protected $mockConfigurationManager;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->mockQueryFactory = $this->createMock(QueryFactory::class);
+        $this->mockQuery = $this->createMock(QueryInterface::class);
+        $this->mockQuerySettings = $this->createMock(QuerySettingsInterface::class);
+        $this->mockQuery->method('getQuerySettings')->willReturn($this->mockQuerySettings);
+        $this->mockQueryFactory->method('create')->willReturn($this->mockQuery);
+        $this->mockSession = $this->createMock(Session::class);
+        $this->mockConfigurationManager = $this->createMock(ConfigurationManager::class);
+        $this->mockBackend = $this->getAccessibleMock(Backend::class, null, [$this->mockConfigurationManager], '', false);
+        $this->mockBackend->_set('session', $this->mockSession);
+        $this->mockPersistenceManager = $this->getAccessibleMock(
+            PersistenceManager::class,
+            ['createQueryForType'],
+            [
+                $this->mockQueryFactory,
+                $this->mockBackend,
+                $this->mockSession,
+            ]
+        );
+        $this->mockBackend->setPersistenceManager($this->mockPersistenceManager);
+        $this->mockPersistenceManager->method('createQueryForType')->willReturn($this->mockQuery);
+        $this->repository = $this->getAccessibleMock(Repository::class, null);
+        $this->repository->injectPersistenceManager($this->mockPersistenceManager);
+    }
+
+    /**
+     * @test
+     */
+    public function magicCallMethodAcceptsFindBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
+    {
+        $mockQueryResult = $this->createMock(QueryResultInterface::class);
+        $mockQuery = $this->createMock(QueryInterface::class);
+        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
+        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
+        $mockQuery->expects(self::once())->method('execute')->with()->willReturn($mockQueryResult);
+
+        $repository = $this->getMockBuilder(Repository::class)
+            ->onlyMethods(['createQuery'])
+            ->getMock();
+        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
+
+        self::assertSame($mockQueryResult, $repository->findByFoo('bar'));
+    }
+
+    /**
+     * @test
+     */
+    public function magicCallMethodAcceptsFindOneBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
+    {
+        $object = new \stdClass();
+        $mockQueryResult = $this->createMock(QueryResultInterface::class);
+        $mockQueryResult->expects(self::once())->method('getFirst')->willReturn($object);
+        $mockQuery = $this->createMock(QueryInterface::class);
+        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
+        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
+        $mockQuery->expects(self::once())->method('setLimit')->willReturn($mockQuery);
+        $mockQuery->expects(self::once())->method('execute')->willReturn($mockQueryResult);
+
+        $repository = $this->getMockBuilder(Repository::class)
+            ->onlyMethods(['createQuery'])
+            ->getMock();
+        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
+
+        self::assertSame($object, $repository->findOneByFoo('bar'));
+    }
+
+    /**
+     * @test
+     */
+    public function magicCallMethodAcceptsCountBySomethingCallsAndExecutesAQueryWithThatCriteria(): void
+    {
+        $mockQuery = $this->createMock(QueryInterface::class);
+        $mockQueryResult = $this->createMock(QueryResultInterface::class);
+        $mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
+        $mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($mockQuery);
+        $mockQuery->expects(self::once())->method('execute')->willReturn($mockQueryResult);
+        $mockQueryResult->expects(self::once())->method('count')->willReturn(2);
+
+        $repository = $this->getMockBuilder(Repository::class)
+            ->onlyMethods(['createQuery'])
+            ->getMock();
+        $repository->expects(self::once())->method('createQuery')->willReturn($mockQuery);
+
+        self::assertSame(2, $repository->countByFoo('bar'));
+    }
+
+    /**
+     * @test
+     */
+    public function magicCallMethodReturnsFirstArrayKeyInFindOneBySomethingIfQueryReturnsRawResult(): void
+    {
+        $queryResultArray = [
+            0 => [
+                'foo' => 'bar',
+            ],
+        ];
+        $this->mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
+        $this->mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($this->mockQuery);
+        $this->mockQuery->expects(self::once())->method('setLimit')->with(1)->willReturn($this->mockQuery);
+        $this->mockQuery->expects(self::once())->method('execute')->willReturn($queryResultArray);
+        self::assertSame(['foo' => 'bar'], $this->repository->findOneByFoo('bar'));
+    }
+
+    /**
+     * @test
+     */
+    public function magicCallMethodReturnsNullInFindOneBySomethingIfQueryReturnsEmptyRawResult(): void
+    {
+        $queryResultArray = [];
+        $this->mockQuery->expects(self::once())->method('equals')->with('foo', 'bar')->willReturn('matchCriteria');
+        $this->mockQuery->expects(self::once())->method('matching')->with('matchCriteria')->willReturn($this->mockQuery);
+        $this->mockQuery->expects(self::once())->method('setLimit')->with(1)->willReturn($this->mockQuery);
+        $this->mockQuery->expects(self::once())->method('execute')->willReturn($queryResultArray);
+        self::assertNull($this->repository->findOneByFoo('bar'));
+    }
+}
diff --git a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
index 47d755a251f451f46324595673e9fb4dd2b17802..7dde0a0d67895ff7cd4e3ff22184ea22a294b812 100644
--- a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
+++ b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
@@ -400,7 +400,7 @@ class DependencyUtility implements SingletonInterface
      */
     protected function isExtensionDownloadableFromRemote(string $extensionKey): bool
     {
-        return $this->extensionRepository->countByExtensionKey($extensionKey) > 0;
+        return $this->extensionRepository->count(['extensionKey' => $extensionKey]) > 0;
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
index 9fb34ac29bc6097249754298242b87fd0811a384..bc8cb698227a00600564c88cda0e4217bcf10109 100644
--- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
+++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
@@ -299,9 +299,9 @@ class DependencyUtilityTest extends UnitTestCase
     public function isExtensionDownloadableFromRemoteReturnsTrueIfOneVersionExists(): void
     {
         $extensionRepositoryMock = $this->getMockBuilder(ExtensionRepository::class)
-            ->addMethods(['countByExtensionKey'])
+            ->onlyMethods(['count'])
             ->getMock();
-        $extensionRepositoryMock->expects(self::once())->method('countByExtensionKey')->with('test123')->willReturn(1);
+        $extensionRepositoryMock->expects(self::once())->method('count')->with(['extensionKey' => 'test123'])->willReturn(1);
         $dependencyUtility = $this->getAccessibleMock(DependencyUtility::class, null);
         $dependencyUtility->injectExtensionRepository($extensionRepositoryMock);
         $count = $dependencyUtility->_call('isExtensionDownloadableFromRemote', 'test123');
@@ -315,9 +315,9 @@ class DependencyUtilityTest extends UnitTestCase
     public function isExtensionDownloadableFromRemoteReturnsFalseIfNoVersionExists(): void
     {
         $extensionRepositoryMock = $this->getMockBuilder(ExtensionRepository::class)
-            ->addMethods(['countByExtensionKey'])
+            ->onlyMethods(['count'])
             ->getMock();
-        $extensionRepositoryMock->expects(self::once())->method('countByExtensionKey')->with('test123')->willReturn(0);
+        $extensionRepositoryMock->expects(self::once())->method('count')->with(['extensionKey' => 'test123'])->willReturn(0);
         $dependencyUtility = $this->getAccessibleMock(DependencyUtility::class, null);
         $dependencyUtility->injectExtensionRepository($extensionRepositoryMock);
         $count = $dependencyUtility->_call('isExtensionDownloadableFromRemote', 'test123');