看来我有两个学说实体经理,但我只想要一个! o.O [英] It seems I have two Doctrine Entity Managers but I want only one! o.O
问题描述
我花了最后几天解决我的捆绑中的一个非常微妙的错误。
实际上我得到一个作业
实体。该实体与属性<$ c上的另一个作业
实体之间具有一对一
的自引用关系$ c> retryOf 。
当我尝试更新检索到的作业#状态
属性,我得到以下异常抛出:
[Doctrine\ORM\ORMInvalidArgumentException]
一个新的实体通过关系
'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job#retryOf'找到
未配置为级联实体的持久性操作:
SerendipityHQ\Bundle\\ \\CommandsQueuesBundle\Entity\Job @ 000000004ab2727c0000000065
d15d74。要解决这个问题:在这个未知实体上显式调用
EntityManager#persist()或配置级联
在映射中保持此关联,例如
@ManyToOne(..,cascade = {坚持})。如果你找不到哪个
实体导致问题,实现
'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job #__ toString()'到
得到一个线索。
最后我发现我用来检索和持久化/刷新实体的 EntityManager
与我的实体不同,用于管理自己和检索相关实体。
所以,我的问题是:假设我注入正确的我的服务中的EntityManager
( @ doctrine.orm.default_entity_manager
):
- 为什么我的服务使用一个,存储库使用另一个?
- 如何使我的实体使用相同的注入服务? (或者如何注入我的实体使用的相同)相同)?
我如何注意 EntityManager
services:
queues:
class:SerendipityHQ\ Bundle\CommandsQueuesBundle\Service\QueuesManager
参数:[@ commands_queues.do_not_use.entity_manager]
#此服务是由QueuesRunCommand私有使用
commands_queues.do_not_use.daemon :
class:SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
参数:[@ commands_queues.do_not_use.entity_manager]
EntityManager
是在 SHQCommandsQueuesExtension
(< a href =https://github.com/Aerendir/bundle-commands-queues/blob/master/DependencyInjection/SHQCommandsQueuesExtension.php =nofollow noreferrer>请参阅GitHub上的代码)。 p>
问题em真的很难,因为我收到错误,但我不能再坚持服务使用的实体经理:这导致数据库中的行重复。
< h2>如何指定
JobRepository
CLASS / **
*基本属性和方法oa Job。
*
* @ ORM\Entity(repositoryClass =SerendipityHQ\Bundle\CommandsQueuesBundle\Repository\JobRepository)
* @ ORM\Table(name =queues_scheduled_jobs )
* /
class Job
{
...
}
/ **
* {@inheritdoc}
* /
类JobRepository扩展EntityRepository
{
/ **
* @param int $ id
*
* @return null |对象| Job
* /
public function findOneById(int $ id)
{
return parent :: findOneBy(['id'=> $ id]);
}
/ **
*返回可以运行的作业。
*
*如果将来没有startDate,并且其父作业已经以
* success结束,则可以运行作业。
*
* @return null | Job
* /
public function findNextRunnableJob()
{
//收集必须排除的作业下一个findNextJob()调用
$ excludedJobs = [];
while(null!== $ job = $ this-> findNextJob($ excludedJobs)){
//如果可以运行...
if(false === $ job-> hasNotFinishedParentJobs()){
// ...返回
return $ job;
}
//作业无法运行或无法获取锁定
$ excludedJobs [] = $ job-> getId();
//从实体管理器中删除它以释放一些内存
$ this-> _em-> detach($ job);
}
}
/ **
*查找要处理的下一个作业。
*
* @param array $ excludedJobs必须从SELECT
*
* @return作业中排除的作业| null
* /
private function findNextJob(array $ excludedJobs = [])
{
$ queryBuilder = $ this-> getEntityManager() - > createQueryBuilder();
$ queryBuilder-> select('j') - > from('SHQCommandsQueuesBundle:Job','j')
- > orderBy('j.priority','ASC')
- > addOrderBy('j.createdAt','ASC')
//状态必须是NEW
- > where($ queryBuilder-> expr() - > eq ('j.status',':status')) - > setParameter('status',Job :: STATUS_NEW)
//它没有一个executeAfterTime设置或设置的时间过去是
- > andWhere(
$ queryBuilder-> expr() - > orX(
$ queryBuilder-> expr() - > isNull('j.executeAfterTime'),
$ queryBuilder-> expr() - > lt('j.executeAfterTime',':now')
)
) - > setParameter('now',new \DateTime ), '约会时间');
//如果有排除的作业...
if(false === empty($ excludedJobs)){
//该ID不是其中之一
$ queryBuilder-> andWhere(
$ queryBuilder-> expr() - > notIn('j.id',':excludedJobs')
) - > setParameter 'excludedJobs',$ excludedJobs,Connection :: PARAM_INT_ARRAY);
}
return $ queryBuilder-> getQuery() - > setMaxResults(1) - > getOneOrNullResult();
}
}
存储库仅在<$ c $ c code code code code code code code code code code code $ c >
验证两个 EntityManager
不同
测试1。再次在 EntityManager
中继续使用我在服务中使用:作品,不再抛出异常
这将强制我做这样的事情:
...
// SerendipityHQ\Bundle\ CommandsQueuesBundle\Service\QueuesDaemon
if($ job-> isRetry()){
//这里我必须在我注入的实体管理器中坚持AGAIN由Job#retryOf $ b $引用的Entity b $ this-> entityManager-> persist($ job-> getRetryOf());
...
}
...
如果我不这样做,则再次抛出异常。
所以似乎注释
使用 EntityManager
和我的服务加载实体,而是使用我指定的实体管理器这是非常奇怪的,从来没有发生过我! OO
TEST 2. VarDump
:显示两个不同的唯一标识符
使用简单的 VarDumper :: dump($ passedEntityManager)
和 VarDumper :: dump($ entity)
我发现他们使用两个不同的实体经理:
...
// SerendipityHQ \Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if($ job-> isRetry()){
// $ job-> setRetryOf($ this-> entityManager-> getRepository( 'SHQCommandsQueuesBundle:作业') - > findOneById($求职> getRetryOf() - >的getId()));
VarDumper :: dump($ this-> entityManager);
VarDumper :: dump($ job);
死
...
}
...
结果是:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate:DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1 {#773 ... 2}
-container:appDevDebugProjectContainer {#495 ... 12}
-config:null
-conn:null
-metadataFactory:null
-unitOfWork:null
-eventManager:null
-proxyFactory:null
-repositoryFactory:null
-expressionBuilder:null
-closed:false
-filterCollection:null
-cache:null
}
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#730
-id:3
。 ..
-childDependencies:Doctrine\ORM\PersistentCollection {#41028
...
-em:Doctrine\ORM\EntityManager {#788 ... 11}
...
#initialized:true
}
-parentDependencies:Doctrine\ORM\PersistentCollection {#41019
...
-em :Doctrine\ORM\EntityManager {#788 ... 11}
...
#initialized:true
}
...
-processedBy:SerendipityHQ \\ Bundle\CommandsQueuesBundle\Entity\Daemon {#806
...
-processedJobs:Doctrine\ORM\PersistentCollection {#819
...
- em:Doctrine\ORM\EntityManager {#788 ... 11}
...
#initialized:true
}
}
...
-childDependencies:Doctrine\ORM\PersistentCollection {#40899
...
-em:Doctrine\ORM\EntityManager {#788 ... 11}
...
#initialized:false
}
-parentDependencies:Doctrine\ORM\PersistentCollection {#40901
...
-em:Doctrine\ORM\Entity经理{#788 ... 11}
...
#initialized:true
}
...
}
实体经理是#788
:
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#725
-id:3
-command:queues:test
...
-childDependencies:Doctrine\ORM\PersistentCollection {#41028
-snapshot:[]
-owner:SerendipityHQ\\ \\ Bundle\CommandsQueuesBundle\Entity\Job {#725}
-association:array:16 [... 16]
-em:Doctrine\ORM\EntityManager {#788 ... 11}
而我注入的实体经理是 768
:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate:DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9 230d68d81c1。 {#773 ... 2}
测试3.证明我有不止一个 EntityManager
:我有
Aerendir $ app / console debug:container | grep EntityManager
aws_ses_monitor.entity_manager Doctrine\ORM\EntityManager
commands_queues.do_not_use.entity_manager Doctrine\ORM\EntityManager
doctrine.orm.default_entity_manager EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ ORM\EntityManager
stripe_bundle.entity_manager Doctrine\ORM\EntityManager
这是正确的因为我实际上有4个实体经理,而那些报告的是我期望存在的。
TEST 4.确定哪个 EntityManager
被我的服务使用,哪个在我的仓库中使用
// SerendipityHQ\Bundle\CommandsQueuesBundle\ Service\QueuesDaemon
if($ job-> isRetry()){
VarDumper :: dump('entityManager');
VarDumper :: dump(spl_object_hash($ this-> entityManager));
VarDumper :: dump('aws_ses_monitor.entity_manager');
VarDumper :: dump(spl_object_hash($ this-> container-> get('aws_ses_monitor.entity_manager')));
VarDumper :: dump('commands_queues.do_not_use.entity_manager');
VarDumper :: dump(spl_object_hash($ this-> container-> get('commands_queues.do_not_use.entity_manager')));
VarDumper :: dump('doctrine.orm.default_entity_manager');
VarDumper :: dump(spl_object_hash($ this-> container-> get('doctrine.orm.default_entity_manager')));
VarDumper :: dump('stripe_bundle.entity_manager');
VarDumper :: dump(spl_object_hash($ this-> container-> get('stripe_bundle.entity_manager')));
die;
...
}
在 JobRepository
类中:
私有函数findNextJob(array $ excludedJobs = [])
{
VarDumper :: dump('Inside repository') ;
VarDumper :: dump(spl_object_hash($ this-> getEntityManager()));
...
结果:
00000000326f44a000000000730f8ec4
entityManager
00000000326f44b400000000730f8ec4
aws_ses_monitor.entity_manager
00000000326f44b400000000730f8ec4
commands_queues.do_not_use.entity_manager
00000000326f44b400000000730f8ec4
doctrine.orm.default_entity_manager
00000000326f44b400000000730f8ec4
stripe_bundle.entity_manager
00000000326f44b400000000730f8ec4
这是一回事!这真的是意想不到的!如果 EntityManager
是一样的:
- 为什么
Job#retryOf
对象尚未持久化,我必须再次坚持不要抛出异常? - 为什么
VarDumper
给我两个不同的内部对象句柄如果对象是一样的?
测试5。尝试删除所有调用 $ this-> entityManager-> detach()
解决!问题是一个 detach()
,没有检查状态:如果一个作业
失败,我只是分离它在$ code> EntityManager 中没有浮动,随着捆绑启动守护进程,我需要释放内存。但是,只有当 Job
绝对失败时,才需要解除它,而如果必须重试,那么我不需要分离它,或至少我有如果处理 Job
指的是重新加载...
建立一个守护进程需要很多注意细节!
I've spent last days solving a very subtle bug in my bundle.
Practically I get a Job
entity from the database. This entity has a one-to-one
, self-referencing relation to another Job
entity on the property retryOf
.
When I try to update the retrieved Job#status
property, I get the following exception thrown:
[Doctrine\ORM\ORMInvalidArgumentException]
A new entity was found through the relationship 'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job#retryOf' that was not configured to cascade persist operations for entity: SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job@000000004ab2727c0000000065 d15d74. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job#__toString()' to get a clue.
In the end I discovered that the EntityManager
I use to retrieve and persist/flush entities is different from the one used by my entities to manage themselves and retrieve related entities.
So, my question is: assuming I'm injecting the right EntityManager
in my services (@doctrine.orm.default_entity_manager
):
- Why my services use one and the repositories use another?
- How can I make my entities use the same I inject in services? (or how can I inject the same used by my entities - is the same)?
HOW I INJECT THE EntityManager
services:
queues:
class: SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesManager
arguments: ["@commands_queues.do_not_use.entity_manager"]
#This service is meant to be privately used by QueuesRunCommand
commands_queues.do_not_use.daemon:
class: SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
arguments: ["@commands_queues.do_not_use.entity_manager"]
The EntityManager
is created in the SHQCommandsQueuesExtension
(see its code on GitHub).
The problem is really hard, as I receive the error but I cannot persist again in the entity manager used by services: this cause a duplication of rows in the database!
HOW I SPECIFY THE JobRepository
CLASS
/**
* Basic properties and methods o a Job.
*
* @ORM\Entity(repositoryClass="SerendipityHQ\Bundle\CommandsQueuesBundle\Repository\JobRepository")
* @ORM\Table(name="queues_scheduled_jobs")
*/
class Job
{
...
}
/**
* {@inheritdoc}
*/
class JobRepository extends EntityRepository
{
/**
* @param int $id
*
* @return null|object|Job
*/
public function findOneById(int $id)
{
return parent::findOneBy(['id' => $id]);
}
/**
* Returns a Job that can be run.
*
* A Job can be run if it hasn't a startDate in the future and if its parent Jobs are already terminated with
* success.
*
* @return null|Job
*/
public function findNextRunnableJob()
{
// Collects the Jobs that have to be excluded from the next findNextJob() call
$excludedJobs = [];
while (null !== $job = $this->findNextJob($excludedJobs)) {
// If it can be run...
if (false === $job->hasNotFinishedParentJobs()) {
// ... Return it
return $job;
}
// The Job cannot be run or its lock cannot be acquired
$excludedJobs[] = $job->getId();
// Remove it from the Entity Manager to free some memory
$this->_em->detach($job);
}
}
/**
* Finds the next Job to process.
*
* @param array $excludedJobs The Jobs that have to be excluded from the SELECT
*
* @return Job|null
*/
private function findNextJob(array $excludedJobs = [])
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select('j')->from('SHQCommandsQueuesBundle:Job', 'j')
->orderBy('j.priority', 'ASC')
->addOrderBy('j.createdAt', 'ASC')
// The status MUST be NEW
->where($queryBuilder->expr()->eq('j.status', ':status'))->setParameter('status', Job::STATUS_NEW)
// It hasn't an executeAfterTime set or the set time is in the past
->andWhere(
$queryBuilder->expr()->orX(
$queryBuilder->expr()->isNull('j.executeAfterTime'),
$queryBuilder->expr()->lt('j.executeAfterTime', ':now')
)
)->setParameter('now', new \DateTime(), 'datetime');
// If there are excluded Jobs...
if (false === empty($excludedJobs)) {
// The ID hasn't to be one of them
$queryBuilder->andWhere(
$queryBuilder->expr()->notIn('j.id', ':excludedJobs')
)->setParameter('excludedJobs', $excludedJobs, Connection::PARAM_INT_ARRAY);
}
return $queryBuilder->getQuery()->setMaxResults(1)->getOneOrNullResult();
}
}
The repository is simply specified in the @Entity
annotation in the Job
entity class.
TESTS
Proving the two EntityManager
s are different
TEST 1. Persist again in the EntityManager
I'm using in services: works, the exception is not thrown anymore
This forces me to do something like this:
...
// SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
// Here I have to persist AGAIN in my injected entity manager the Entity referenced by Job#retryOf
$this->entityManager->persist($job->getRetryOf());
...
}
...
If I don't do this, the exception is again thrown.
So it seems that the annotations
loads entities using an EntityManager
and my service, instead, use the entity manager I've specified... This is very strange and never happened to me! O.O
TEST 2. VarDump
: Shows two different unique identifiers
With a simple VarDumper::dump($passedEntityManager)
and VarDumper::dump($entity)
I've discovered that they use two different entity managers:
...
// SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
//$job->setRetryOf($this->entityManager->getRepository('SHQCommandsQueuesBundle:Job')->findOneById($job->getRetryOf()->getId()));
VarDumper::dump($this->entityManager);
VarDumper::dump($job);
die;
...
}
...
The result is:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate: DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1 {#773 …2}
-container: appDevDebugProjectContainer {#495 …12}
-config: null
-conn: null
-metadataFactory: null
-unitOfWork: null
-eventManager: null
-proxyFactory: null
-repositoryFactory: null
-expressionBuilder: null
-closed: false
-filterCollection: null
-cache: null
}
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#730
-id: 3
...
-childDependencies: Doctrine\ORM\PersistentCollection {#41028
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
-parentDependencies: Doctrine\ORM\PersistentCollection {#41019
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
...
-processedBy: SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Daemon {#806
...
-processedJobs: Doctrine\ORM\PersistentCollection {#819
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
}
...
-childDependencies: Doctrine\ORM\PersistentCollection {#40899
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: false
}
-parentDependencies: Doctrine\ORM\PersistentCollection {#40901
...
-em: Doctrine\ORM\EntityManager {#788 …11}
...
#initialized: true
}
...
}
In entities the Entity manager is #788
:
SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#725
-id: 3
-command: "queues:test"
...
-childDependencies: Doctrine\ORM\PersistentCollection {#41028
-snapshot: []
-owner: SerendipityHQ\Bundle\CommandsQueuesBundle\Entity\Job {#725}
-association: array:16 [ …16]
-em: Doctrine\ORM\EntityManager {#788 …11}
while my injected entity manager is 768
:
EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager {#768
-delegate: DoctrineORMEntityManager_000000007aac762a000000007cf8d85284b5df468960200ce73b9230d68d81c1. {#773 …2}
TEST 3. Proving I have more than one EntityManager
: I have
Aerendir$ app/console debug:container | grep EntityManager
aws_ses_monitor.entity_manager Doctrine\ORM\EntityManager
commands_queues.do_not_use.entity_manager Doctrine\ORM\EntityManager
doctrine.orm.default_entity_manager EntityManager58a434fb99fbf_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager
stripe_bundle.entity_manager Doctrine\ORM\EntityManager
This is right as I effectively have 4 entity managers and the ones reported are the ones I expect exist.
TEST 4. Identify which EntityManager
is used by my services and which one is used in my repositories
// SerendipityHQ\Bundle\CommandsQueuesBundle\Service\QueuesDaemon
if ($job->isRetry()) {
VarDumper::dump('entityManager');
VarDumper::dump(spl_object_hash($this->entityManager));
VarDumper::dump('aws_ses_monitor.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('aws_ses_monitor.entity_manager')));
VarDumper::dump('commands_queues.do_not_use.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('commands_queues.do_not_use.entity_manager')));
VarDumper::dump('doctrine.orm.default_entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('doctrine.orm.default_entity_manager')));
VarDumper::dump('stripe_bundle.entity_manager');
VarDumper::dump(spl_object_hash($this->container->get('stripe_bundle.entity_manager')));
die; ... }
And in the JobRepository
class:
private function findNextJob(array $excludedJobs = [])
{
VarDumper::dump('Inside repository');
VarDumper::dump(spl_object_hash($this->getEntityManager()));
...
Result:
"Inside repository"
"00000000326f44a000000000730f8ec4"
"entityManager"
"00000000326f44b400000000730f8ec4"
"aws_ses_monitor.entity_manager"
"00000000326f44b400000000730f8ec4"
"commands_queues.do_not_use.entity_manager"
"00000000326f44b400000000730f8ec4"
"doctrine.orm.default_entity_manager"
"00000000326f44b400000000730f8ec4"
"stripe_bundle.entity_manager"
"00000000326f44b400000000730f8ec4"
It's ever the same! This is really unexpected!!! If the EntityManager
is ever the same:
- Why the
Job#retryOf
object is not already persisted and I have to persist it again to not get the thrown exception? - Why the
VarDumper
gives me two different internal object handles if the object is ever the same?
TEST 5. Try to remove all the calls to $this->entityManager->detach()
SOLVED! The problem was a detach()
that didn't check for the status: if a Job
failed I simply detached it to not have it floating around in the EntityManager
and as the bundle launches a daemon, I need to free up memory. But I need to free it up only if the Job
is definitely failed, while if it has to be retried, I not have to detach it... Or at least I have to reload it if the processing Job
refers to it...
Damn, building a daemon requires a lot of attention to details!
这篇关于看来我有两个学说实体经理,但我只想要一个! o.O的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!