Doctrine ORM为开发人员提供了获取数据的便捷方法。这是一个功能强大的DQL,可以以面向对象的方式工作,它是一个方便的Query Builder,使用起来简单直观。它们满足了大多数需求,但是有时有必要使用针对特定DBMS进行了优化或特定的SQL查询。要使用代码中的查询结果,了解Doctrine中的映射如何工作非常重要。
原则ORM基于数据映射器模式,该模式将关系表示与对象隔离,并在对象之间转换数据。此过程的关键组成部分之一是ResultSetMapping对象,该对象描述了如何将查询结果从关系模型转换为对象。Doctrine始终使用ResultSetMapping表示查询结果,但是通常该对象是基于注释或yaml,xml配置创建的,它对开发人员而言是隐藏的,因此并不是每个人都知道其功能。对于使用ResultSetMapping的示例,我将使用MySQL和employee示例数据库。让我们描述实体部门,雇员,薪水,我们将继续与之合作。单位<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
class Department
{
private $deptNo;
private $deptName;
private $empNo;
public function __construct()
{
$this->empNo = new \Doctrine\Common\Collections\ArrayCollection();
}
}
雇员<?php名称空间App \ Entity;使用Doctrine \ ORM \ Mapping作为ORM;/ ***员工**ORM\表(名称=“员工”)*ORM\Entity
*/
class Employee
{
/**
*
var int
*
*
ORM\Column(name=«emp_no», type=«integer», nullable=false)
*
ORM\Id
*
ORM\GeneratedValue(strategy=«IDENTITY»)
*/
private $empNo;
/**
*
var \DateTime
*
*
ORM\Column(name=«birth_date», type=«date», nullable=false)
*/
private $birthDate;
/**
*
var string
*
*
ORM\Column(name=«first_name», type=«string», length=14, nullable=false)
*/
private $firstName;
/**
*
var string
*
*
ORM\Column(name=«last_name», type=«string», length=16, nullable=false)
*/
private $lastName;
/**
*
var string
*
*
ORM\Column(name=«gender», type=«string», length=0, nullable=false)
*/
private $gender;
/**
*
var \DateTime
*
*
ORM\Column(name=«hire_date», type=«date», nullable=false)
*/
private $hireDate;
/**
*
var \Doctrine\Common\Collections\Collection
*
*
ORM\ManyToMany(targetEntity=«Department», inversedBy=«empNo»)
*
ORM\JoinTable(name=«dept_manager»,
* joinColumns={
*
ORM\JoinColumn(name=«emp_no», referencedColumnName=«emp_no»)
* },
* inverseJoinColumns={
*
ORM\JoinColumn(name=«dept_no», referencedColumnName=«dept_no»)
* }
* )
*/
private $deptNo;
/**
* Constructor
*/
public function __construct()
{
$this->deptNo = new \Doctrine\Common\Collections\ArrayCollection();
}
}
薪水<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Salaries
*
*
ORM\Table(name=«salaries», indexes={@ORM\Index(name=«IDX_E6EEB84BA2F57F47», columns={«emp_no»})})
*
ORM\Entity
*/
class Salary
{
/**
*
var \DateTime
*
*
ORM\Column(name=«from_date», type=«date», nullable=false)
*
ORM\Id
*
ORM\GeneratedValue(strategy=«NONE»)
*/
private $fromDate;
/**
*
var int
*
*
ORM\Column(name=«salary», type=«integer», nullable=false)
*/
private $salary;
/**
*
var \DateTime
*
*
ORM\Column(name=«to_date», type=«date», nullable=false)
*/
private $toDate;
/**
*
var Employee
*
*
ORM\Id
*
ORM\OneToOne(targetEntity=«Employee»)
*
ORM\JoinColumns({
*
ORM\JoinColumn(name=«emp_no», referencedColumnName=«emp_no»)
* })
*/
private $empNo;
/**
*
var Employee
*
*/
private $employee;
}
实体结果
让我们从一个简单的开始,从基础中选择所有部门并将它们投影到部门上: $rsm = new Query\ResultSetMapping();
$rsm->addEntityResult(Department::class, 'd');
$rsm->addFieldResult('d', 'dept_no', 'deptNo');
$rsm->addFieldResult('d', 'dept_name', 'deptName');
$sql = 'SELECT * FROM departments';
$query = $this->entityManager->createNativeQuery($sql, $rsm);
$result = $query->getResult();
addEntityResult
方法指示将样本投影到哪个类上,而addFieldResult方法指示样本列与对象字段之间的映射。传递给createNativeQuery方法的SQL查询将以这种形式传输到数据库,Doctrine不会以任何方式对其进行修改。值得记住的是,Entity必须具有唯一的标识符,该标识符必须包含在fieldResult中。合并实体结果
我们选择2000年初以后雇用员工的部门以及这些员工。 $rsm = new Query\ResultSetMapping();
$rsm->addEntityResult(Department::class, 'd');
$rsm->addFieldResult('d', 'dept_no', 'deptNo');
$rsm->addFieldResult('d', 'dept_name', 'deptName');
$rsm->addJoinedEntityResult(Employee::class, 'e', 'd', 'empNo');
$rsm->addFieldResult('e', 'first_name', 'firstName');
$rsm->addFieldResult('e', 'last_name', 'lastName');
$rsm->addFieldResult('e', 'birth_date', 'birthDate');
$rsm->addFieldResult('e', 'gender', 'gender');
$rsm->addFieldResult('e', 'hire_date', 'hireDate');
$sql = "
SELECT *
FROM departments d
JOIN dept_emp ON d.dept_no = dept_emp.dept_no
JOIN employees e on dept_emp.emp_no = e.emp_no
WHERE e.hire_date > DATE ('1999-12-31')
";
$query = $this->entityManager->createNativeQuery($sql, $rsm);
$result = $query->getResult();
ResultSetMappingBuilder
如您所见,直接使用ResultSetMapping对象有点复杂,并且它非常详细地描述了选择对象与对象的比较。此外,Doctrine提供了更方便的工具ResultSetMappingBuilder,它是RSM的包装,并增加了用于映射的更方便的方法。例如,generateSelectClause方法,它允许您为查询的SELECT部分创建一个参数,其中包含选择所需字段的描述。可以以更简单的形式重写先前的请求。 $sql = "
SELECT {$rsm->generateSelectClause()}
FROM departments d
JOIN dept_emp ON d.dept_no = dept_emp.dept_no
JOIN employees e on dept_emp.emp_no = e.emp_no
WHERE e.hire_date > DATE ('1999-12-31')
";
值得注意的是,如果不指定创建对象的所有字段,Doctrine将返回部分对象,在某些情况下可以合理使用它,但是它们的行为很危险,因此不建议这样做。相反,使用ResultSetMappingBuilder,您无需指定最终类的每个字段,而可以使用通过注释(yaml,xml配置)描述的实体。考虑到以下方法,我们从上一个请求重写了我们的RSM: $rsm = new Query\ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata(Department::class, 'd');
$rsm->addJoinedEntityFromClassMetadata(Employee::class, 'e', 'd', 'empNo');
标量结果
所描述的实体的广泛使用是不合理的,这可能会导致性能问题,因为Doctrine需要创建许多将来将无法完全使用的对象。对于此类情况,提供了标量结果映射工具addScalarResult。选择每个部门的平均工资: $rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addScalarResult('dept_name', 'department', 'string');
$rsm->addScalarResult('avg_salary', 'salary', 'integer');
$sql = "
SELECT d.dept_name, AVG(s.salary) AS avg_salary
FROM departments d
JOIN dept_emp de on d.dept_no = de.dept_no
JOIN employees e on de.emp_no = e.emp_no
JOIN salaries s on e.emp_no = s.emp_no
GROUP BY d.dept_name
";
$query = $this->entityManager->createNativeQuery($sql, $rsm);
$result = $query->getResult();
addScalarResult
方法的第一个参数是结果集中列的名称,第二个参数是Doctrine将返回的结果数组的键。第三个参数是结果值的类型。返回值将始终是一个数组数组。DTO映射
但是使用数组,特别是当它们具有复杂的结构时,并不是很方便。您需要牢记键的名称,字段类型。教义DQL可以在选择结果中使用简单的DTO,例如: SELECT NEW DepartmentSalary(d.dept_no, avg_salary) FROM …
原生查询和RSM呢?Doctrine没有提供用于创建新DTO的文档化功能,但是通过使用newObjectMappings属性,我们可以指定要将选择结果映射到的对象。与Entity不同,这些对象将不受UnitOfWork的控制,并且不需要位于配置中指定的名称空间中。补充上一个示例中的RSM: $rsm->newObjectMappings['dept_name'] = [
'className' => DepartmentSalary::class,
'argIndex' => 0,
'objIndex' => 0,
];
$rsm->newObjectMappings['avg_salary'] = [
'className' => DepartmentSalary::class,
'argIndex' => 1,
'objIndex' => 0,
];
newObjectMappings字段的数组中的键指向结果列,但其值是另一个数组,该数组描述了要创建的对象。如果要从选择的每一行中获取多个对象,则className键定义新对象的类名,argIndex-对象的构造函数中参数的顺序,objIndex-对象的顺序。元结果
元结果用于获取列的元数据,例如外键或鉴别符列。不幸的是,我无法基于employees数据库提供一个示例,因此我不得不将自己局限于文档中的描述和示例。 $rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
在这种情况下,使用addMetaResult方法,我们告诉Doctine用户表在地址上有一个外键,但是我们创建了一个代理对象来存储实体标识符,并在访问时加载,而不是将关联加载到内存中(急于加载)。她从数据库中。经典的关系数据库不提供表继承机制,而继承在对象模型中却很普遍。根据某些属性,我们的选择结果可以投影到类的层次结构上,该属性是结果选择中列<discriminator_column>的值。在这种情况下,我们可以使用setDiscriminatorColumn方法告诉RSM Doctrine哪个列应确定实例化的类。结论
Doctrine具有非常丰富的各种功能,包括并非所有开发人员都知道的功能。在这篇文章中,我试图介绍对ORM关键组件之一的操作-ResultSetMapping与本机查询的结合的理解。在保持示例的可用性和可理解性的同时,使用真正复杂且特定于平台的查询将是一项艰巨的任务,因为重点在于理解ResultSetMapping的工作。之后,您可以在非常复杂的数据库查询中使用它。