EntityManager đã bị đóng


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

Sau khi tôi nhận được ngoại lệ DBAL khi chèn dữ liệu, EntityManager sẽ đóng và tôi không thể kết nối lại nó.

Tôi đã thử như vậy nhưng nó không nhận được kết nối.

$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();

Bất cứ ai có ý tưởng làm thế nào để kết nối lại?


Tại sao người quản lý thực thể đóng cửa?
Jay Sheth

2
@JaySheth Trình quản lý thực thể có thể đóng sau một ngoại lệ DBAL hoặc nếu bạn đang thực hiện EntityManager-> clear () trước khi xả. Tôi đã thấy một số người sử dụng các ngoại lệ DBAL để phân nhánh luồng thực thi và sau đó kết thúc bằng lỗi đóng EntityManager. Nếu bạn gặp lỗi này, có điều gì đó sai trong quy trình thực thi trong chương trình của bạn.
ILikeTacos

5
@AlanChavez - Tôi gặp lỗi này vì tôi đang sử dụng Doctrine để viết cờ semaphore vào một bảng đang được nhiều luồng truy cập đồng thời. MySQL sẽ báo lỗi một trong hai luồng cạnh tranh đang cố gắng tạo semaphore, vì ràng buộc khóa có nghĩa là chỉ một trong số chúng có thể thành công. IMO có một lỗ hổng trong Doctrine không cho phép bạn xử lý các lỗi MySQL dự kiến một cách an toàn . Tại sao toàn bộ kết nối MySQL phải bị ngắt kết nối vì một câu lệnh INSERT có xung đột?
StampyCode

2
Bạn cũng sẽ thấy lỗi này nếu bạn đang cố gắng ghi các ngoại lệ vào cơ sở dữ liệu trong app.exception_listenerngoại lệ (chẳng hạn như vi phạm ràng buộc) đã đóng kết nối.
Lg102

Câu trả lời:


24

Đây là một vấn đề rất phức tạp vì, ít nhất là đối với Symfony 2.0 và Doctrine 2.1, không thể mở lại EntityManager sau khi nó đóng.

Cách duy nhất tôi tìm thấy để khắc phục vấn đề này là tạo lớp Kết nối DBAL của riêng bạn, bọc Doctrine một và cung cấp xử lý ngoại lệ (ví dụ: thử lại nhiều lần trước khi đưa ngoại lệ ra EntityManager). Nó hơi hacky và tôi e rằng nó có thể gây ra một số sự không nhất quán trong môi trường giao dịch (tức là tôi không thực sự chắc chắn về điều gì sẽ xảy ra nếu truy vấn không thành công ở giữa một giao dịch).

Một cấu hình ví dụ để thực hiện theo cách này là:

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

Lớp học sẽ bắt đầu ít nhiều như thế này:

namespace Your\DBAL;

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

Một điều rất khó chịu là bạn phải ghi đè từng phương thức của Kết nối cung cấp trình bao bọc xử lý ngoại lệ của bạn. Sử dụng đóng cửa có thể làm dịu cơn đau ở đó.


71

Giải pháp của tôi.

Trước khi làm bất cứ điều gì, hãy kiểm tra:

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

Tất cả các thực thể sẽ được lưu. Nhưng nó rất hữu ích cho một số lớp hoặc một số trường hợp cụ thể. Nếu bạn có một số dịch vụ với entitymanager được chèn vào, nó vẫn bị đóng.


cách này tốt hơn khi bản thân vùng chứa di không có sẵn. Cảm ơn bạn.
Hari KT

1
bạn cũng có thể muốn chuyển $ this-> entityManager-> getEventManager () trong tham số thứ 3.
Medhat Gayed

34

Symfony 2.0 :

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

Symfony 2.1+ :

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

6
CẢNH BÁO: resetEntityManager không được dùng nữa kể từ Symfony 2.1. Sử dụng resetManagerthay vì
Francesco Casula

Điều này cũng đặt lại Unit Of Work?
cúm

@flu Xem xét lớp EntityManager quản lý lớp UnitOfWork, tôi nghi ngờ là có. Tuy nhiên, tôi chưa thử nghiệm điều này nên không thể chắc chắn.
Ryall

26

Đây là cách tôi giải quyết Doctrine "EntityManager bị đóng." vấn đề. Về cơ bản, mỗi lần có một ngoại lệ (tức là khóa trùng lặp) hoặc không cung cấp dữ liệu cho một cột bắt buộc sẽ khiến Doctrine đóng Trình quản lý thực thể. Nếu bạn vẫn muốn tương tác với cơ sở dữ liệu, bạn phải đặt lại Trình quản lý thực thể bằng cách gọi resetManager()phương thức như được đề cập bởi JGrinon .

Trong ứng dụng của mình, tôi đang chạy nhiều người tiêu dùng RabbitMQ đều làm cùng một việc: kiểm tra xem một thực thể có ở đó trong cơ sở dữ liệu hay không, nếu có, hãy trả lại nó, nếu không hãy tạo nó và sau đó trả lại nó. Trong vài phần nghìn giây giữa việc kiểm tra xem thực thể đó đã tồn tại hay chưa và tạo ra nó, một người tiêu dùng khác đã tình cờ làm như vậy và tạo ra thực thể bị thiếu khiến người tiêu dùng khác phải chịu một ngoại lệ khóa trùng lặp ( điều kiện chủng tộc ).

Điều này dẫn đến một vấn đề thiết kế phần mềm. Về cơ bản những gì tôi đang cố gắng làm là tạo tất cả các thực thể trong một giao dịch. Điều này có thể cảm thấy tự nhiên với hầu hết nhưng chắc chắn là sai về mặt khái niệm trong trường hợp của tôi. Hãy xem xét vấn đề sau: Tôi đã phải lưu trữ một thực thể Trận đấu bóng đá có những phụ thuộc này.

  • một nhóm (ví dụ: Nhóm A, Nhóm B ...)
  • một vòng (ví dụ như Bán kết ...)
  • một địa điểm (tức là sân vận động nơi trận đấu đang diễn ra)
  • trạng thái trận đấu (ví dụ: nửa thời gian, toàn thời gian)
  • hai đội chơi trận đấu
  • trận đấu chính nó

Bây giờ, tại sao việc tạo địa điểm phải được thực hiện trong cùng một giao dịch với trận đấu? Có thể là tôi vừa nhận được một địa điểm mới mà nó không có trong cơ sở dữ liệu của tôi nên tôi phải tạo nó trước. Nhưng nó cũng có thể là địa điểm đó có thể tổ chức một trận đấu khác nên một người tiêu dùng khác có thể sẽ cố gắng tạo ra nó cùng lúc. Vì vậy, những gì tôi phải làm là tạo tất cả các phụ thuộc trước trong các giao dịch riêng biệt để đảm bảo rằng tôi đang đặt lại trình quản lý thực thể trong một ngoại lệ khóa trùng lặp. Tôi muốn nói rằng tất cả các thực thể trong đó bên cạnh trận đấu có thể được định nghĩa là "được chia sẻ" vì chúng có thể là một phần của các giao dịch khác ở những người tiêu dùng khác. Thứ gì đó không được "chia sẻ" trong đó là bản thân kết quả trùng khớp sẽ không có khả năng được tạo bởi hai người tiêu dùng cùng một lúc.

Tất cả điều này cũng dẫn đến một vấn đề khác. Nếu bạn đặt lại Trình quản lý thực thể, tất cả các đối tượng bạn đã truy xuất trước khi đặt lại dành cho Doctrine hoàn toàn mới. Vì vậy, Doctrine sẽ không cố chạy CẬP NHẬT trên chúng mà là CHÈN ! Vì vậy, hãy đảm bảo rằng bạn tạo tất cả các phụ thuộc của mình trong các giao dịch chính xác một cách hợp lý và sau đó lấy lại tất cả các đối tượng của bạn từ cơ sở dữ liệu trước khi đặt chúng thành thực thể đích. Hãy coi đoạn mã sau làm ví dụ:

$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.
 */

Vì vậy, đây là cách tôi nghĩ nó nên được thực hiện.

$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();

Tôi hy vọng nó sẽ giúp :)


Giải thích tuyệt vời. Tôi đã tìm thấy một cái gì đó tương tự và nghĩ rằng sẽ rất vui nếu đóng góp cho câu trả lời của bạn. Cảm ơn rât nhiều.
Anjana Silva

17

Bạn có thể đặt lại EM của mình để

// 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

Trong Symfony 4.2+, bạn phải sử dụng gói:

composer require symfony/proxy-manager-bridge

khác, bạn có được ngoại lệ:

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

Ngoài ra, bạn có thể đặt lại entityManager như thế này:

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

Trong bộ điều khiển.

Ngoại lệ đóng Trình quản lý thực thể. Điều này gây rắc rối cho việc chèn hàng loạt. Để tiếp tục, cần xác định lại nó.

/** 
* @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++;
    }               
  }                      
}


1

Đối với những gì đáng giá, tôi thấy sự cố này đang xảy ra trong lệnh nhập hàng loạt do vòng lặp try / catch bắt lỗi SQL (với em->flush()) mà tôi không làm gì cả. Trong trường hợp của tôi, đó là vì tôi đang cố gắng chèn một bản ghi có thuộc tính không thể null được để lại là null.

Thông thường, điều này sẽ gây ra một ngoại lệ quan trọng xảy ra và lệnh hoặc bộ điều khiển tạm dừng, nhưng tôi chỉ ghi lại sự cố này thay thế và tiếp tục. Lỗi SQL đã khiến trình quản lý thực thể đóng.

Kiểm tra dev.logtệp của bạn xem có bất kỳ lỗi SQL ngớ ngẩn nào như thế này không vì nó có thể là lỗi của bạn. :)


1

Tôi gặp phải vấn đề tương tự khi thử nghiệm các thay đổi trong Symfony 4.3.2

Tôi đã hạ cấp nhật ký xuống INFO

Và chạy thử nghiệm lại

Và nhật ký cho thấy điều này:

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?"} []

Điều này có nghĩa là một số lỗi trong mã gây ra:

Doctrine\ORM\ORMException: The EntityManager is closed.

Vì vậy, bạn nên kiểm tra nhật ký


Bạn có thể cung cấp thêm thông tin về cách thứ nhất liên quan đến thứ hai không?
George Novik

1

Symfony v4.1.6

Doctrine v2.9.0

Xử lý chèn các bản sao vào một kho lưu trữ

  1. Nhận quyền truy cập vào sổ đăng ký trong kho của bạn


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

  1. Bọc mã rủi ro vào giao dịch và đặt lại trình quản lý trong trường hợp ngoại lệ


    //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

Tôi đã có vấn đề này. Đây là cách tôi sửa nó.

Kết nối dường như đóng trong khi cố gắng xóa hoặc tồn tại. Cố gắng mở lại nó là một lựa chọn tồi vì tạo ra những vấn đề mới. Tôi đã cố gắng tìm hiểu lý do tại sao kết nối bị đóng và nhận thấy rằng tôi đã thực hiện quá nhiều sửa đổi trước khi vẫn tồn tại.

Kiên trì () trước đó đã giải quyết vấn đề.


0

Hãy thử sử dụng:

$em->getConnection()->[setNestTransactionsWithSavepoints][1](true);

trước khi bắt đầu giao dịch.

Trên Connection::rollbackphương thức nó kiểm tra nestTransactionsWithSavepointstài sản.


3
Bạn có thể mở rộng về điều này?
paul.ago

0

Đây thực sự là vấn đề cũ, nhưng tôi vừa gặp vấn đề tương tự. Tôi đã làm một cái gì đó như thế này:

// 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();

Vấn đề là xóa tất cả các thực thể bao gồm cả thực thể đầu tiên và lỗi ném . EntityManager bị đóng.

Trong trường hợp của tôi, giải pháp là chỉ làm rõ về loại Đối tượng riêng biệt và $entityOnevẫn để lại dưới EM:

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

0

Vấn đề tương tự, được giải quyết bằng một cấu trúc lại mã đơn giản. Vấn đề đôi khi xuất hiện khi một trường bắt buộc là rỗng, trước khi làm điều gì đó, hãy thử cấu trúc lại mã của bạn. Quy trình làm việc tốt hơn có thể giải quyết vấn đề.


-1

Tôi đã gặp lỗi tương tự khi sử dụng Symfony 5 / Doctrine 2. Một trong các trường của tôi được đặt tên bằng từ "order" dành riêng cho MySQL, gây ra lỗi DBALException. Khi bạn muốn sử dụng một từ dành riêng, bạn phải thoát khỏi tên của nó bằng cách sử dụng dấu tích. Ở dạng chú thích:

@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

Tôi phải đối mặt với cùng một vấn đề. Sau khi xem xét một số nơi ở đây là cách tôi xử lý nó.

//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();
}

Hy vọng điều này sẽ giúp ai đó!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.