EntityManager ถูกปิด


87
[Doctrine\ORM\ORMException]   
The EntityManager is closed.  

หลังจากที่ฉันได้รับข้อยกเว้น DBAL เมื่อแทรกข้อมูล EntityManager จะปิดและฉันไม่สามารถเชื่อมต่อใหม่ได้

ฉันลองแบบนี้ แต่ไม่ได้รับการเชื่อมต่อ

$this->em->close();
$this->set('doctrine.orm.entity_manager', null);
$this->set('doctrine.orm.default_entity_manager', null);
$this->get('doctrine')->resetEntityManager();
$this->em = $this->get('doctrine')->getEntityManager();

ใครมีความคิดที่จะเชื่อมต่อใหม่?


เหตุใดผู้จัดการนิติบุคคลจึงปิด
Jay Sheth

2
@JaySheth ตัวจัดการเอนทิตีสามารถปิดได้หลังจากข้อยกเว้น DBAL หรือถ้าคุณกำลังทำ EntityManager-> clear () ก่อนที่จะล้าง ฉันเคยเห็นบางคนใช้ข้อยกเว้น DBAL เพื่อแยกขั้นตอนการดำเนินการแล้วจบลงด้วยข้อผิดพลาดปิด EntityManager หากคุณได้รับข้อผิดพลาดนี้แสดงว่ามีบางอย่างผิดปกติในขั้นตอนการดำเนินการในโปรแกรมของคุณ
ILikeTacos

6
@AlanChavez - ฉันได้รับข้อผิดพลาดนี้เนื่องจากฉันใช้ Doctrine เพื่อเขียนแฟล็กเซมาฟอร์ลงในตารางที่มีการเข้าถึงหลายเธรดพร้อมกัน MySQL จะเกิดข้อผิดพลาดหนึ่งในสองเธรดที่แข่งขันกันพยายามสร้างเซมาฟอร์เนื่องจากข้อ จำกัด ของคีย์หมายความว่ามีเพียงเธรดเดียวเท่านั้นที่สามารถประสบความสำเร็จได้ IMO มีข้อบกพร่องใน Doctrine ที่ไม่อนุญาตให้คุณจัดการกับข้อผิดพลาด MySQL ที่คาดไว้ได้อย่างปลอดภัย เหตุใดการเชื่อมต่อ MySQL ทั้งหมดจึงควรถูกตัดการเชื่อมต่อเนื่องจากคำสั่ง INSERT หนึ่งมีข้อขัดแย้ง
StampyCode

2
นอกจากนี้คุณจะเห็นข้อผิดพลาดนี้หากคุณพยายามบันทึกข้อยกเว้นของฐานข้อมูลในapp.exception_listenerแต่ข้อยกเว้น (เช่นการละเมิดข้อ จำกัด ) ปิดการเชื่อมต่อ
Lg102

คำตอบ:


25

นี่เป็นปัญหาที่ยุ่งยากมากเนื่องจากอย่างน้อยสำหรับ Symfony 2.0 และ Doctrine 2.1 จะไม่สามารถเปิด EntityManager ได้อีกครั้งหลังจากปิดไปแล้ว

วิธีเดียวที่ฉันพบในการเอาชนะปัญหานี้คือการสร้างคลาส DBAL Connection ของคุณเองห่อ Doctrine one และจัดเตรียมการจัดการข้อยกเว้น (เช่นลองใหม่หลาย ๆ ครั้งก่อนที่จะเปิดข้อยกเว้นไปยัง EntityManager) มันค่อนข้างแฮ็กและฉันเกรงว่าอาจทำให้เกิดความไม่สอดคล้องกันในสภาพแวดล้อมการทำธุรกรรม (เช่นฉันไม่แน่ใจจริงๆว่าจะเกิดอะไรขึ้นหากการสืบค้นที่ล้มเหลวอยู่ระหว่างการทำธุรกรรม

ตัวอย่างการกำหนดค่าสำหรับวิธีนี้คือ:

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        driver:   %database_driver%
        host:     %database_host%
        user:     %database_user%
        password: %database_password%
        charset:  %database_charset%
        wrapper_class: Your\DBAL\ReopeningConnectionWrapper

ชั้นเรียนควรเริ่มมากขึ้นหรือน้อยลงดังนี้:

namespace Your\DBAL;

class ReopeningConnectionWrapper extends Doctrine\DBAL\Connection {
  // ...
}

สิ่งที่น่ารำคาญมากคือคุณต้องลบล้างวิธีการเชื่อมต่อแต่ละวิธีที่ให้เครื่องห่อหุ้มข้อยกเว้นของคุณ การใช้ผ้าปิดสามารถบรรเทาความเจ็บปวดได้


74

ทางออกของฉัน

ก่อนทำการตรวจสอบ:

if (!$this->entityManager->isOpen()) {
    $this->entityManager = $this->entityManager->create(
        $this->entityManager->getConnection(),
        $this->entityManager->getConfiguration()
    );
}

เอนทิตีทั้งหมดจะถูกบันทึก แต่มีประโยชน์สำหรับคลาสเฉพาะหรือบางกรณี หากคุณมีบริการบางอย่างที่มีผู้จัดการเอนทิตีที่ถูกแทรกไว้บริการนั้นจะยังคงปิดอยู่


นี่เป็นวิธีที่ดีกว่าเมื่อไม่มีคอนเทนเนอร์ di ขอขอบคุณ.
Hari KT

1
คุณอาจต้องการส่ง $ this-> entityManager-> getEventManager () ในพารามิเตอร์ที่ 3
Medhat Gayed

34

Symfony 2.0 :

$em = $this->getDoctrine()->resetEntityManager();

Symfony 2.1+ :

$em = $this->getDoctrine()->resetManager();

6
คำเตือน: resetEntityManagerเลิกใช้งานตั้งแต่ Symfony 2.1 ใช้resetManagerแทน
Francesco Casula

นี่เป็นการรีเซ็ตหน่วยการทำงานด้วยหรือไม่
ไข้หวัด

@flu เมื่อพิจารณาจากคลาส EntityManager จัดการคลาส UnitOfWork ฉันสงสัยว่ามันจะ อย่างไรก็ตามฉันยังไม่ได้ทดสอบจึงไม่แน่ใจ
Ryall

28

นี่คือวิธีที่ฉันแก้ไขหลักคำสอน"The EntityManager is closed" ปัญหา. โดยทั่วไปทุกครั้งที่มีข้อยกเว้น (เช่นคีย์ซ้ำ) หรือไม่ให้ข้อมูลสำหรับคอลัมน์บังคับจะทำให้ Doctrine ปิดตัวจัดการเอนทิตี หากคุณยังคงต้องการที่จะติดต่อกับฐานข้อมูลที่คุณต้องรีเซ็ตกิจการ Manger โดยการเรียกresetManager()วิธีการดังกล่าวโดยJGrinon

ในแอปพลิเคชันของฉันฉันใช้งานผู้บริโภค RabbitMQ หลายรายที่ทำสิ่งเดียวกันทั้งหมด: ตรวจสอบว่ามีเอนทิตีอยู่ในฐานข้อมูลหรือไม่ถ้าใช่ส่งคืนหากไม่สร้างแล้วส่งคืน ในช่วงเวลาไม่กี่มิลลิวินาทีระหว่างการตรวจสอบว่าเอนทิตีนั้นมีอยู่แล้วหรือไม่และสร้างขึ้นโดยผู้บริโภครายอื่นเกิดขึ้นเพื่อทำแบบเดียวกันและสร้างเอนทิตีที่ขาดหายไปทำให้ผู้บริโภครายอื่นเกิดข้อยกเว้นคีย์ที่ซ้ำกัน ( เงื่อนไขการแข่งขัน )

สิ่งนี้นำไปสู่ปัญหาการออกแบบซอฟต์แวร์ โดยพื้นฐานแล้วสิ่งที่ฉันพยายามทำคือการสร้างเอนทิตีทั้งหมดในธุรกรรมเดียว สิ่งนี้อาจรู้สึกเป็นธรรมชาติสำหรับคนส่วนใหญ่ แต่ในกรณีของฉันผิดแนวคิดอย่างแน่นอน พิจารณาปัญหาต่อไปนี้: ฉันต้องจัดเก็บเอนทิตีการแข่งขันฟุตบอลที่มีการอ้างอิงเหล่านี้

  • กลุ่ม (เช่นกลุ่ม A กลุ่ม B ... )
  • รอบ (เช่นรอบรองชนะเลิศ ... )
  • สถานที่จัดงาน (เช่นสนามกีฬาที่มีการแข่งขัน)
  • สถานะการแข่งขัน (เช่นครึ่งเวลาเต็มเวลา)
  • ทั้งสองทีมเล่นการแข่งขัน
  • การแข่งขันนั้นเอง

ตอนนี้ทำไมการสร้างสถานที่ควรเป็นธุรกรรมเดียวกันกับการแข่งขัน? อาจเป็นไปได้ว่าฉันเพิ่งได้รับสถานที่ใหม่ซึ่งไม่ได้อยู่ในฐานข้อมูลของฉันดังนั้นฉันจึงต้องสร้างขึ้นก่อน แต่อาจเป็นไปได้ว่าสถานที่นั้นอาจเป็นเจ้าภาพจัดการแข่งขันอีกครั้งดังนั้นผู้บริโภครายอื่นอาจพยายามสร้างมันขึ้นมาด้วยในเวลาเดียวกัน ดังนั้นสิ่งที่ฉันต้องทำคือสร้างการอ้างอิงทั้งหมดก่อนในธุรกรรมแยกต่างหากเพื่อให้แน่ใจว่าฉันได้รีเซ็ตตัวจัดการเอนทิตีในข้อยกเว้นคีย์ที่ซ้ำกัน ฉันขอบอกว่าเอนทิตีทั้งหมดที่อยู่ในนั้นข้างการจับคู่สามารถกำหนดเป็น "แชร์" ได้เนื่องจากอาจเป็นส่วนหนึ่งของธุรกรรมอื่นในผู้บริโภครายอื่น สิ่งที่ไม่ "แชร์" ในนั้นคือการจับคู่ที่ไม่น่าจะสร้างขึ้นโดยผู้บริโภคสองคนในเวลาเดียวกัน

ทั้งหมดนี้ยังนำไปสู่อีกประเด็นหนึ่ง หากคุณรีเซ็ตตัวจัดการเอนทิตีอ็อบเจ็กต์ทั้งหมดที่คุณได้รับก่อนที่จะรีเซ็ตจะเป็นของ Doctrine ใหม่ทั้งหมด ดังนั้น Doctrine จะไม่พยายามเรียกใช้UPDATEกับพวกเขา แต่INSERT ! ดังนั้นตรวจสอบให้แน่ใจว่าคุณได้สร้างการอ้างอิงทั้งหมดของคุณในธุรกรรมที่ถูกต้องตามเหตุผลจากนั้นดึงอ็อบเจ็กต์ทั้งหมดของคุณกลับจากฐานข้อมูลก่อนตั้งค่าเป็นเอนทิตีเป้าหมาย พิจารณาโค้ดต่อไปนี้เป็นตัวอย่าง:

$group = $this->createGroupIfDoesNotExist($groupData);

$match->setGroup($group); // this is NOT OK!

$venue = $this->createVenueIfDoesNotExist($venueData);

$round = $this->createRoundIfDoesNotExist($roundData);

/**
 * If the venue creation generates a duplicate key exception
 * we are forced to reset the entity manager in order to proceed
 * with the round creation and so we'll loose the group reference.
 * Meaning that Doctrine will try to persist the group as new even
 * if it's already there in the database.
 */

นี่คือวิธีที่ฉันคิดว่าควรทำ

$group = $this->createGroupIfDoesNotExist($groupData); // first transaction, reset if duplicated
$venue = $this->createVenueIfDoesNotExist($venueData); // second transaction, reset if duplicated
$round = $this->createRoundIfDoesNotExist($roundData); // third transaction, reset if duplicated

// we fetch all the entities back directly from the database
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);

// we finally set them now that no exceptions are going to happen
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);

// match and teams relation...
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);

$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);

$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);

// last transaction!
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();

ฉันหวังว่ามันจะช่วย :)


คำอธิบายที่ยอดเยี่ยม ฉันพบบางสิ่งที่คล้ายกันและคิดว่าน่าจะช่วยตอบคำตอบของคุณ ขอบคุณมาก.
อัญจนาซิลวา

17

คุณสามารถรีเซ็ต EM ได้

// reset the EM and all aias
$container = $this->container;
$container->set('doctrine.orm.entity_manager', null);
$container->set('doctrine.orm.default_entity_manager', null);
// get a fresh EM
$em = $this->getDoctrine()->getManager();

10

ในSymfony 4.2+คุณต้องใช้แพ็คเกจ:

composer require symfony/proxy-manager-bridge

อื่น ๆ คุณจะได้รับข้อยกเว้น:

Resetting a non-lazy manager service is not supported. Declare the "doctrine.orm.default_entity_manager" service as lazy.  

กว่าที่คุณจะสามารถรีเซ็ต entityManager ได้ดังนี้:

services.yaml:

App\Foo:
    - '@doctrine.orm.entity_manager'
    - '@doctrine'

Foo.php:

use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;


 try {
    $this->entityManager->persist($entity);
    $this->entityManager->flush();
} catch (DBALException $e) {
    if (!$this->entityManager->isOpen()) {
        $this->entityManager = $this->doctrine->resetManager();
    }
}

4

ในตัวควบคุม

ข้อยกเว้นปิดตัวจัดการเอนทิตี ทำให้เกิดปัญหาในการแทรกจำนวนมาก หากต้องการดำเนินการต่อต้องกำหนดนิยามใหม่

/** 
* @var  \Doctrine\ORM\EntityManager
*/
$em = $this->getDoctrine()->getManager();

foreach($to_insert AS $data)
{
    if(!$em->isOpen())
    {
        $this->getDoctrine()->resetManager();
        $em = $this->getDoctrine()->getManager();
    }

  $entity = new \Entity();
  $entity->setUniqueNumber($data['number']);
  $em->persist($entity);

  try
  {
    $em->flush();
    $counter++;
  }
  catch(\Doctrine\DBAL\DBALException $e)
  {
    if($e->getPrevious()->getCode() != '23000')
    {   
      /**
      * if its not the error code for a duplicate key 
      * value then rethrow the exception
      */
      throw $e;
    }
    else
    {
      $duplication++;
    }               
  }                      
}

2

สำหรับสิ่งที่คุ้มค่าฉันพบว่าปัญหานี้เกิดขึ้นในคำสั่งนำเข้าแบทช์เนื่องจากลูป try / catch จับข้อผิดพลาด SQL (ด้วยem->flush()) ที่ฉันไม่ได้ทำอะไรเลย ในกรณีของฉันเป็นเพราะฉันพยายามแทรกระเบียนที่มีคุณสมบัติที่ไม่เป็นโมฆะทิ้งไว้เป็นโมฆะ

โดยปกติสิ่งนี้จะทำให้เกิดข้อยกเว้นที่สำคัญและคำสั่งหรือตัวควบคุมหยุด แต่ฉันแค่บันทึกปัญหานี้แทนและดำเนินการต่อ ข้อผิดพลาด SQL ทำให้ตัวจัดการเอนทิตีปิด

ตรวจสอบdev.logไฟล์ของคุณเพื่อหาข้อผิดพลาด SQL โง่ ๆ เช่นนี้เนื่องจากอาจเป็นความผิดของคุณ :)



1

ฉันประสบปัญหาเดียวกันขณะทดสอบการเปลี่ยนแปลงใน Symfony 4.3.2

ฉันลดระดับบันทึกเป็น INFO

และทำการทดสอบอีกครั้ง

และบันทึกแสดงสิ่งนี้:

console.ERROR: Error thrown while running command "doctrine:schema:create". Message: "[Semantical Error] The annotation "@ORM\Id" in property App\Entity\Common::$id was never imported. Did you maybe forget to add a "use" statement for this annotation?" {"exception":"[object] (Doctrine\\Common\\Annotations\\AnnotationException(code: 0): [Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation? at C:\\xampp\\htdocs\\dirty7s\\vendor\\doctrine\\annotations\\lib\\Doctrine\\Common\\Annotations\\AnnotationException.php:54)","command":"doctrine:schema:create","message":"[Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation?"} []

ซึ่งหมายความว่าข้อผิดพลาดบางอย่างในรหัสทำให้เกิด:

Doctrine\ORM\ORMException: The EntityManager is closed.

ดังนั้นจึงควรตรวจสอบบันทึก


คุณช่วยให้ข้อมูลเพิ่มเติมได้ไหมว่ารายการแรกเกี่ยวข้องกับรายการที่สองอย่างไร
George Novik

1

Symfony v4.1.6

หลักคำสอน v2.9.0

ประมวลผลการแทรกรายการที่ซ้ำกันในที่เก็บ

  1. เข้าถึงรีจิสทรีใน repo ของคุณ


    //begin of repo
    
    /** @var RegistryInterface */
    protected $registry;
    
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
        parent::__construct($registry, YourEntity::class);
    }

  1. รวมรหัสที่มีความเสี่ยงลงในธุรกรรมและรีเซ็ตตัวจัดการในกรณีที่มีข้อยกเว้น


    //in repo method
    $em = $this->getEntityManager();
    
    $em->beginTransaction();
    try {
        $em->persist($yourEntityThatCanBeDuplicate);
        $em->flush();
        $em->commit();
    
    } catch (\Throwable $e) {
        //Rollback all nested transactions
        while ($em->getConnection()->getTransactionNestingLevel() > 0) {
            $em->rollback();
        }
        
        //Reset the default em
        if (!$em->isOpen()) {
            $this->registry->resetManager();
        }
    }


0

ฉันมีปัญหานี้ นี่คือวิธีที่ฉันแก้ไข

ดูเหมือนว่าการเชื่อมต่อจะปิดลงในขณะที่พยายามล้างหรือคงอยู่ การพยายามเปิดอีกครั้งเป็นทางเลือกที่ไม่ดีเนื่องจากสร้างประเด็นใหม่ ฉันพยายามทำความเข้าใจว่าเหตุใดการเชื่อมต่อจึงถูกปิดและพบว่าฉันทำการแก้ไขมากเกินไปก่อนที่จะยังคงมีอยู่

ยังคงมีอยู่ () แก้ไขปัญหาก่อนหน้านี้


0

นี่เป็นปัญหาเก่าจริงๆ แต่ฉันเพิ่งมีปัญหาที่คล้ายกัน ฉันกำลังทำสิ่งนี้:

// entity
$entityOne = $this->em->find(Parent::class, 1);

// do something on other entites (SomeEntityClass)
$this->em->persist($entity);
$this->em->flush();
$this->em->clear();

// and at end I was trying to save changes to first one by
$this->em->persist($entityOne);
$this->em->flush();
$this->em->clear();

ปัญหาคือการลบเอนทิตีทั้งหมดรวมทั้งรายการแรกและข้อผิดพลาด EntityManager ถูกปิด

ในกรณีของฉันวิธีแก้ปัญหาคือทำความชัดเจนกับประเภทของเอนทิตีที่แตกต่างกันและปล่อยให้$entityOneอยู่ภายใต้ EM:

$this->em->clear(SomeEntityClass::class);

1
การเรียก Doctrine \ ORM \ EntityManager :: clear () ที่มีอาร์กิวเมนต์ใด ๆ เพื่อล้างเอนทิตีเฉพาะถูกเลิกใช้และจะไม่ได้รับการสนับสนุนใน Doctrine ORM 3.0
Foued MOUSSI

0

ปัญหาเดียวกันแก้ไขได้ด้วยการปรับโครงสร้างโค้ดง่ายๆ ปัญหาเกิดขึ้นในบางครั้งเมื่อฟิลด์ที่ต้องการเป็นโมฆะก่อนที่จะทำการลบล้างให้ลองเปลี่ยนโค้ดของคุณใหม่ ขั้นตอนการทำงานที่ดีขึ้นสามารถแก้ปัญหาได้


-1

ฉันมีข้อผิดพลาดเดียวกันกับการใช้ Symfony 5 / Doctrine 2 ฟิลด์หนึ่งของฉันถูกตั้งชื่อโดยใช้คำสงวน MySQL "order" ทำให้เกิด DBALException เมื่อคุณต้องการใช้คำสงวนคุณต้องหลีกเลี่ยงชื่อโดยใช้ back-ticks ในรูปแบบคำอธิบายประกอบ:

@ORM\Column(name="`order`", type="integer", nullable=false)

-2
// first need to reset current manager
$em->resetManager();
// and then get new
$em = $this->getContainer()->get("doctrine");
// or in this way, depending of your environment:
$em = $this->getDoctrine();

-2

ฉันประสบปัญหาเดียวกัน หลังจากดูสถานที่หลายแห่งแล้วนี่คือวิธีที่ฉันจัดการกับมัน

//function in some model/utility
function someFunction($em){
    try{
        //code which may throw exception and lead to closing of entity manager
    }
    catch(Exception $e){
        //handle exception
        return false;
    }
    return true;
}

//in controller assuming entity manager is in $this->em 
$result = someFunction($this->em);
if(!$result){
    $this->getDoctrine()->resetEntityManager();
    $this->em = $this->getDoctrine()->getManager();
}

หวังว่านี่จะช่วยใครสักคน!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.