在实际项目中,文章通常属于某个频道,并由某用户发布。本文以 MspArticle(文章)、MspChannel(频道)、MspUser(用户)三实体为例,演示如何通过 YAML 定义多对一/一对多关联,并使用 DQL 完成高效的联表查询。
# src/Acme/MspadminBundle/Resources/config/doctrine/MspArticle.orm.yml
Acme\MspadminBundle\Entity\MspArticle:
type: entity
table: msp_article
manyToOne:
Channel:
targetEntity: MspChannel
inversedBy: Articles
joinColumn:
name: channel_id
referencedColumnName: channel_id
User:
targetEntity: MspUser
inversedBy: Userone
joinColumn:
name: user_id
referencedColumnName: user_id
# 此处省略 id、title、content 等字段映射
# MspChannel.orm.yml
Acme\MspadminBundle\Entity\MspChannel:
type: entity
table: msp_channel
oneToMany:
Articles:
targetEntity: MspArticle
mappedBy: Channel
# 字段映射省略
# MspUser.orm.yml
Acme\MspadminBundle\Entity\MspUser:
type: entity
table: msp_user
oneToMany:
Userone:
targetEntity: MspArticle
mappedBy: User
# 字段映射省略
在 Repository 或 Controller 中:
<?php
// 获取 EntityManager
$em = $this->getDoctrine()->getManager();
// DQL:选择所需字段,使用别名简化
$dql = "SELECT a.id, a.checkStatus, a.title, a.releaseSysDate, a.visitTotal,
u.userName, n.name
FROM AcmeMspadminBundle:MspArticle a
JOIN a.User u
JOIN a.Channel n
WHERE a.checkStatus = 0";
$query = $em->createQuery($dql);
$articles = $query->getResult(); // 返回数组,每项包含选中的标量值
// 若需获取对象图(含关联对象),可用:
// $dql = "SELECT a, u, n FROM AcmeMspadminBundle:MspArticle a JOIN a.User u JOIN a.Channel n WHERE a.checkStatus = 0";
// $articles = $query->getResult(); // 返回 MspArticle 对象集合,关联对象已预加载
$qb = $em->createQueryBuilder();
$qb->select('a.id, a.title, u.userName, n.name')
->from('AcmeMspadminBundle:MspArticle', 'a')
->innerJoin('a.User', 'u')
->innerJoin('a.Channel', 'n')
->where('a.checkStatus = :status')
->setParameter('status', 0);
$results = $qb->getQuery()->getResult();
若在模板中遍历 article.User.userName,默认会触发懒加载产生 N+1 查询。解决方案:
JOIN ... SELECT a, u, n(如上)。fetch: EAGER(慎用,可能导致过度加载)。$dql = "SELECT NEW Acme\MspadminBundle\DTO\ArticleListItem(a.id, a.title, u.userName, n.name)
FROM AcmeMspadminBundle:MspArticle a JOIN a.User u JOIN a.Channel n
WHERE a.checkStatus = 0";
// 需在 DTO 类定义对应构造函数
| 误区 | 正确做法 |
|---|---|
| 在 DQL 中写 SQL 表名 | 必须使用实体类名和别名,如 AcmeBundle:Article a |
| JOIN 后未 SELECT 关联字段 | 若只 SELECT a,关联对象不会被加载,模板访问时仍会触发懒加载 |
| 混用 YAML 与 Annotation 映射 | 一个 Bundle 只能使用一种映射驱动,避免冲突 |
Doctrine 的关联映射与 DQL 提供了强大的面向对象查询能力。掌握 ManyToOne/OneToMany 映射、JOIN ... SELECT 预加载、QueryBuilder 动态构建,能编写出既符合 OOP 又高效的数据访问代码。生产环境建议结合 Profiler 监控 SQL 条数,必要时引入二级缓存或读写分离。