【问题标题】:How does DBAL read data that ORM inserts but has not yet “flush”?DBAL 如何读取 ORM 插入但尚未“刷新”的数据?
【发布时间】:2019-04-26 03:21:38
【问题描述】:

由于历史原因,我使用 Symfony 运行数据库的模式是混合的。也就是说,查询使用 DBAL,插入使用 ORM。现在您需要将大量数据写入数据库。 ORM中的flush可以帮助我以最低的成本实现业务。

所有flush 操作已从项目中删除。把它放在controller__destruct 中。 但是,这样做会导致 DBAL 找不到最新更改的数据。当然,这些数据ORMs是可以正常获取的。 这是一个非常困难的问题。希望得到指导。

class BaseController extends Controller
{
    public function __destruct()
    {
        $this->getDoctrine()->getManager()->flush();
    }

    public function indexAction()
    {
        $model = new CompanyModel();
        $model->install(['company_name' => '1234']);
        $model->update(['company_name' => 'abcd'], $model->lastInsertId);
    }
}

class CompanyModel extends BaseController
{

    public function validate($data, $id = false)
    {
        $this->entityManager = $this->getDoctrine()->getManager();

        if(empty($id)){
            $this->company_class = new Company();
        }else{
            if(!$this->is_exist($id)){
                return false;
            }

            $this->company_class = $this->entityManager->getRepository(Company::class)->find($id);
        }

        if(array_key_exists('company_name', $data)){
            $this->company_class->setCompanyName($data['company_name']);
        }

        if(self::$error->validate($this->company_class)){
            return false;
        }

        return true;
    }

    public function insert($data)
    {
        if(!$this->validate($data)){
            return false;
        }

        $this->company_class->setCreateAt(new \DateTime());

        $this->entityManager->persist($this->company_class);
        //$this->entityManager->flush();

        $this->lastInsertId = $this->company_class->getId();

        return true;
    }

    public function update($data, $id)
    {
        if(empty($id)){
            self::$error->setError('param id is not null');
            return false;
        }

        if(!$this->validate($data, $id)){
            return false;
        }

        $this->company_class->setUpdateAt(new \DateTime());

        //$this->entityManager->flush();

        return true;
    }

    public function is_exist($id)
    {
        return $this->get('database_connection')->fetchColumn('...');
    }


}

执行indexActioncompany_name的最终结果是1234$ model-> update() 未成功执行。原因是接受DBAL查询的$this-> is_exist()方法没有找到ORM插入但是没有flush消息。

条件不变,运行

$this->entityManager->getRepository(Company::class)->find($id);

成功了。

【问题讨论】:

  • 为了将flush方法放入__destruct()中。它对你有用吗?
  • 您可以尝试beginTransaction/commit 将其全部包装在事务中,并在更改后立即刷新。这可能会稍微提高性能。然而,看起来你的模式很糟糕,你不应该混合。 ORM 就像一个临时区域,如果您工作正常,则不需要 id 即可使其在 orm 中工作。将其与 dbal 混合是一个问题,可能会在某些时候导致不一致和代码异味,应尽快重构。 (我会给出建议,但您提供的代码示例太少,无法显示 dbal 导致问题的位置)
  • @Jakumi 非常感谢你。我完善了我的问题。
  • @MiteshVasava 非常感谢。我完善了我的问题。

标签: php symfony orm doctrine dbal


【解决方案1】:

据我所知,问题不在于实体管理器或 dbal,而在于反模式的使用,我称之为……纠缠。您应该争取的是关注点分离。本质上:您的“CompanyModel”是 EntityManager 和/或 EntityRepository 的不充分且糟糕的包装器。

  1. 没有对象应该知道实体管理器。它应该只关心保存数据。
  2. 实体管理者应该关注持久性和确保完整性。
  3. 控制器旨在编排一个“动作”,可以是添加一家公司、编辑一家公司、批量导入/更新许多公司。
  4. 当操作变得业务逻辑繁重或重复功能时,可以实施服务。

(注意:使用 symfony 提供的所有功能,例如 ParamConverters、Form 组件、Validation 组件,可以使以下代码示例更优雅,我通常不会编写代码这种方式,但我认为其他一切都会超出你的想象 - 没有冒犯。)

在控制器中处理动作

控制器操作(实际上是服务操作)是从任务的角度看待问题。就像“我想用这个数据更新那个对象”)。那是您获取/创建该对象,然后给它数据的时候。

use Doctrine\ORM\EntityManagerInterface;

class BaseController extends Controller {

    public function __construct(EntityManagerInterface $em) {
        $this->em = $em;
    }

    public function addAction() {
        $company = new Company(['name' => '1234']); // initial setting in constructor
        $this->em->persist($company);

        // since you have the object, you can do any changes to it.
        // just change the object
        $company->update(['name' => 'abcd']); // <-- don't need id

        // updates will be flushed as well!
        $this->em->flush();
    }

    public function editAction($id, $newData) {
        $company = $this->em->find(Company::class, $id);
        if(!$company) {
            throw $this->createNotFoundException();
        }
        $company->update($newData);
        $this->em->flush();
    }

    // $companiesData should be an array of arrays, each containing 
    // a company with an id for update, or without an id for creation
    public function batchAction(array $companiesData) {
        foreach($companies as $companyData) {
            if($companyData['id']) {
                // has id -> update existing company
                $company = $this->em->find(Company::class, $companyData['id']);
                //// optional: 
                // if(!$company) { // id was given, but company does not exist
                //     continue;   // skip 
                //     //  OR 
                //     $company = new Company($companyData); // create
                //     //  OR
                //     throw new \Exception('company not found: '.$companyData['id']);
                // }
                $company->update($companyData);
            } else {
                // no id -> create new company
                $company = new Company($companyData);
                $this->em->persist($company);
            }
        }
        $this->em->flush(); // one flush.
    }
}

基本控制器应该处理创建对象和持久化它,这是非常基本的业务逻辑。有些人会争辩说,其中一些操作应该在该类的改编存储库中完成,或者应该封装在服务中。一般来说,他们是对的。

实体处理它的内部状态

现在,Company 类处理自己的属性并尝试保持一致。你只是不得不在这里做一些假设。首先:对象本身不应该关心它是否存在于数据库中。这不是它的目的!它应该自己处理。关注点分离! Company 实体内部的功能应该涉及简单的业务逻辑,即涉及其内部状态。它不需要数据库,也不应该对数据库有任何引用,它只关心它的字段。

class Company {

    /**
     * all the database fields as public $fieldname;
     */
    // ...

    /**
     * constructor for the inital state. You should never want 
     * an inconsistent state!
     */
    public function __construct(array $data=[]) {
        $this->validate($data); // set values
        if(empty($this->createAt)) {
            $this->createAt = new \DateTime();
        }
    }

    /**
     * update the data
     */
    public function update(array $data) {
        $this->validate($data); // set new values
        $this->updateAt = new \DateTime();
    }

    public function validate(array $data) {
        // this is simplified, but you can also validate 
        // here and throw exceptions and stuff
        foreach($array as $key => $value) {
            $this->$key = $value;
        }
    }
}

一些注意事项

现在,应该没有用例了,您可以在其中获取要持久化的对象,同时更新 - 带有 id - 指的是新对象......除非该对象事先被赋予了 id!然而。如果您持久化一个具有 ID 的对象,并且您调用 $this-&gt;em-&gt;find(Company::class, $id),您将取回该对象。

如果你有很多关系,总是有很好的方法来解决这个问题而不破坏关注点分离!您永远不应该将实体管理器注入实体。实体不应该管理自己的持久性!它也不应该管理链接对象的持久性。处理持久性是实体管理器或实体存储库的目的。你不应该仅仅为了处理那个对象而需要一个对象的包装器。注意不要混合服务、实体(对象)和控制器的职责。在我的示例代码中,我已经合并了服务和控制器,因为在简单的情况下,它已经足够好了。

【讨论】:

  • 非常感谢您的详细说明。我将从您的建议中学习并重新设计我的代码。但是现在,由于历史原因,我倾向于不做彻底的修改。当然,如此混乱的代码还是要付出代价的。
  • 我意识到这个问题可能没有解决方案或没有必要解决。因为我收集了很多地方的意见。最后发现我在Model中的insert和edit操作没有及时清除缓存。这可能会导致缓存占用大量时间。所以我将“flush”放回模型中并添加了“$this-> entityManager-> clear();”批量插入的速度显着提高。
猜你喜欢
  • 2017-01-16
  • 1970-01-01
  • 2013-05-30
  • 1970-01-01
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
相关资源
最近更新 更多