PHPのDoctrineでSQLが乱発される事象について

Lazy LoadによってSQLが乱発されデータベースの負荷が高くなる恐れがあるので注意しましょう。

Lazy Loadとは、必要なときにSQLを発行する仕組みです。

これから、例を示しながら説明していきます。
※この記事ではDoctrine 2.4.7を使用しています。

以下は、userとpostが1対多のリレーションを持っているEntityです。

class User implements UserInterface
{

public function __construct()
{
$this->posts = new ArrayCollection();
}

/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string $name
*
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;

/**
* @ORM\OneToMany(targetEntity="Post", mappedBy="user")
* @var Post[] $posts
*/
private $posts;

/**
*
* @return integer
*/
public function getId()
{
return $this->id;
}

/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}

/**
* @return string
*/
public function getName()
{
return $this->name;
}

/**
* @return Post[]
*/
public function getPosts()
{
return $this->posts;
}

/**
* @param Post $post
*/
public function addPost($post)
{
$post->setUser($this);
$this->posts->add($post);
}
}

 

class Post
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string
*
* @ORM\Column(name="title", type="string", length=100, nullable=true, unique=false)
*/
private $title;

/**
* @var string
*
* @ORM\Column(name="body", type="text")
*/
private $body;

/**
* @var User
*
* @ORM\ManyToOne(targetEntity="User")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
/**
*
* @return integer
*/
public function getId()
{
return $this->id;
}

/**
*
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}

/**
*
* @return string
*/
public function getTitle()
{
return $this->title;
}

/**
*
* @param string $body
*/
public function setBody($body)
{
$this->body = $body;
}

/**
*
* @return string
*/
public function getBody()
{
return $this->body;
}

/**
*
* @return User
*/
public function getUser()
{
return $this->user;
}

/**
*
* @param User $user
*/
public function setUser($user)
{
$this->user = $user;
}
}

そして、以下はユーザーとそれに紐づく投稿記事を取得するDQLです。

$qb = $this->createQueryBuilder('user');
$qb->select('user')
      ->leftJoin('user.posts', 'posts')
      ->where($qb->expr()->eq('user.id', $userId))

return $qb->getQuery()->getSingleResult();

上記の結果をuser変数に入れて、twigで使用すると

ユーザー名: {{ user.getName }}

投稿記事
{% for post in user.getPosts %}
     タイトル: {{ post.getTitle }}

     内容
     {{ post.getBody }}
{% endfor %}

HTMLなどは省略しましたが、このようになります。
ポイントはfor文のuser.postsの部分です。
ここで新たにuserに紐づくpostを取得するためのSQLが発行されます。
よって、SQLが最低2回

$qb->getQuery()->getSingleResult()

の時と

user.getPosts

の時に発行されることになります。
postのデータが複数ある場合、その回数分SQLが発行されます。
なぜなら、DQLのselect句($qb->select('user'))でユーザーしかセレクトしていないため
そのオブジェクトのリレーションプロパティをgetするだけで「Doctrine が無言で二つ目のクエリを発行する」からです。

Lazy Loadを発生させないためには

$qb->select('user')

ではなく

$qb->select('user', 'posts')

としなけらばならないのです。
こうすることで、

$qb->getQuery()->getSingleResult()

の時の一回のSQLで、postの情報まで取得することができます。

 

投稿者プロフィール

スカイブロガー

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


Time limit is exhausted. Please reload CAPTCHA.