在 Doctrine DBAL 中重用 QueryBuilder

Oli*_*der 6 php doctrine dbal

以下示例显示了代码示例的一些摘录。在那里调用QueryBuilderDoctrine DBAL 两次 - 一次是为了执行一条SELECT(*)语句,另一次是在执行一条语句之前COUNT(*)

表、条件、排序顺序和结果限制等常见设置将应用于重用QueryBuilder对象。

问题

  • $queryBuilder像示例中所示的隐式重用是否有缺点?
  • 是否建议仅复制粘贴单独实例的代码QueryBuilder
  • 使用有副作用clone $queryBuilder吗?

代码示例

/**
 * @param array $arguments
 * @return string
 */
private function getOutput(array $arguments)
{
    /** @var \Doctrine\DBAL\Connection $connection */
    $connection = $this->getConnection();

    $queryBuilder = $connection
        ->createQueryBuilder()
        ->from('some_table')
        ->orderBy('sorting')
        ->setMaxResults(100);

    $condition = $queryBuilder->expr()->andX();
    // ... build conditions
    $queryBuilder->where($condition);

    $count = $queryBuilder->select('COUNT(*)')->execute()->fetchColumn(0);
    if ($count === 0) {
        return 'There is nothing to show';
    }
    if ($count > 100) {
        $output = 'Showing first 100 results only:' . PHP_EOL;
    } else {
        $output = 'Showing all results:' . PHP_EOL;
    }

    // implicitly reusing previously defined settings
    // (table, where, orderBy & maxResults)
    $statement = $queryBuilder->select('*')->execute();
    foreach ($statement as $item) {
        $output .= $this->renderItem($item) . PHP_EOL;
    }

    return $output;
}
Run Code Online (Sandbox Code Playgroud)

Oli*_*der 5

2023 年 10 月更新

来自https://www.derhansen.de/2023/10/the-pitfalls-of-reusing-typo3-querybuilder-analyzing-a-performance-bottleneck.html

TL;DR:切勿重复使用 TYPO3 QueryBuilder 的实例进行查询,即使查询相同但参数不同,因为这会导致处理大量记录时性能显着下降。


原答案

Doctrine DBAL 中的 QueryBuilder 可以动态地使用来定义 SQL 查询,也可以再次覆盖查询部分。因此,通常select()在同一个 QueryBuilder 实例上调用该方法两次会覆盖先前的select查询部分。构建器内部有一个干净或脏状态的属性 - 一旦状态是脏的,就必须重新创建 SQL 字符串。例如,覆盖查询部分会触发脏状态。

因此,一般来说,从简单的技术角度来看,重用 QueryBuilder 是可能的并且很好。但是,QueryBuilder 不验证跨数据库特定逻辑。这意味着,必须手动清除多余的查询部分,以避免执行语句时查询失败。

更好的方法是将构建查询的过程分成不同的逻辑类方法 - 一种方法分配公共查询约束,另一种方法分配特定上下文(例如,计算结果与排序和限制结果集)。最后,最初的代码示例可能如下所示:

引入了两种附加方法来丰富查询

/**
 * @param QueryBuilder $queryBuilder
 */
private function addConstraints(QueryBuilder $queryBuilder)
{
    $condition = $queryBuilder->expr()->andX();
    // ... build conditions
    $queryBuilder->where($condition);
}

/**
 * @param QueryBuilder $queryBuilder
 */
private function addResultSettings(QueryBuilder $queryBuilder)
{
    $queryBuilder
        ->orderBy('sorting')
        ->setMaxResults(100);
}
Run Code Online (Sandbox Code Playgroud)

现在有两个 QueryBuilder 实例,但查询基本上是在前面显示的两个新方法中定义的。

/**
 * @param array $arguments
 * @return string
 */
private function getOutput(array $arguments)
{
    /** @var \Doctrine\DBAL\Connection $connection */
    $connection = $this->getConnection();

    // first query builder instance for counting records
    $queryBuilder = $connection->createQueryBuilder()->from('some_table');
    $this->addConstraints($queryBuilder);
    $statement = $queryBuilder->select('COUNT(*)')->execute();

    $count = $statement->fetchColumn(0);
    if ($count === 0) {
        return 'There is nothing to show';
    }
    if ($count > 100) {
        $output = 'Showing first 100 results only:' . PHP_EOL;
    } else {
        $output = 'Showing all results:' . PHP_EOL;
    }

    // second query builder instance to actually retrieve result set
    $queryBuilder = $connection->createQueryBuilder()->from('some_table');
    $this->addConstraints($queryBuilder);
    $this->addResultSettings($queryBuilder);
    $statement = $queryBuilder->select('*')->execute();

    foreach ($statement as $item) {
        $output .= $this->renderItem($item) . PHP_EOL;
    }

    return $output;
}
Run Code Online (Sandbox Code Playgroud)