10分ぐらいで学べるSymfony2 〜Doctrine2のassociationsを見てみる〜
masterとcategoryのように複数テーブルに情報が分かれている場合でもDoctrine2はよろしくMAPしてくれます。
ただ、ORMを使っているとSQLが直接見えない分、残念なSQLになってしまう事があるので挙動を調べたメモです。
Doctirne2のテーブル間の関係性において「1対1」「1対多」「多対多」の3つがあります。
今回は「1対多」で参照の場合のメモとなります。
1. テーブル構成
hoge1が1でhoge_typeが多の関係性で初期データやデータ定義は下記とします。
# テーブル定義 mysql> desc hoge1; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(32) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) mysql> desc hoge1_type; +----------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | hoge1_id | int(11) | NO | MUL | NULL | | | name | varchar(32) | YES | | NULL | | +----------+-------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) # テーブルデータ mysql> select * from hoge1; +----+---------+ | id | name | +----+---------+ | 1 | master1 | | 2 | master2 | | 3 | master3 | | 4 | master4 | | 5 | master5 | +----+---------+ mysql> select * from hoge1_type; +----+----------+-------------+ | id | hoge1_id | name | +----+----------+-------------+ | 1 | 1 | hoge1_type1 | | 2 | 2 | hoge2_type1 | | 3 | 2 | hoge2_type2 | | 4 | 4 | hoge4_type1 | | 5 | 4 | hoge4_type2 | | 6 | 4 | hoge4_type3 | | 7 | 4 | hoge4_type4 | +----+----------+-------------+ 7 rows in set (0.00 sec) mysql>
2. リレーション設定
「mappedBy」と「inversedBy」には相手側のプロパティ名が入るので注意しましょう。
# one側 Root\TestBundle\Entity\Hoge1: type: entity oneToMany: Hoge1Types: targetEntity: Hoge1Type mappedBy: hoge1 cascade: ['all'] # many側 Root\TestBundle\Entity\Hoge1Type: type: entity table: hoge1_type manyToOne: hoge1: targetEntity: Hoge1 inversedBy: Hoge1Types joinColumn: name: hoge1_id referencedColumnName: id
3. hoge1(one)からhoge1_type(many)のデータにアクセス
(1)typeデータにアクセスしない場合
typeのデータにアクセスしなければhoge1データだけをselectします。
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->getRepository('RootTestBundle:Hoge1')->findAll(); foreach($all as $hoge1) { print $hoge1->getName().'<br />'; } # 実行結果 master1 master2 master3 master4 master5 # 発行SQL SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0
(2)typeデータにアクセスするがjoinしない場合
typeデータにアクセスする場合毎回SQLが発生します。
リストなどでTypeのデータが必要な場合limitの数だけSELECT文が発生して悲惨な事になりますので注意が必要です。
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->getRepository('RootTestBundle:Hoge1')->findAll(); foreach($all as $hoge1) { print $hoge1->getName().'<br />'; foreach($hoge1->getHoge1Types() as $type) print $type->getName().'<br />'; } # 実行結果 master1 hoge1_type1 master2 hoge2_type1 hoge2_type2 master3 master4 hoge4_type1 hoge4_type2 hoge4_type3 hoge4_type4 master5 # 発行SQL SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([1]) SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([2]) SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([3]) SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([4]) SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([5])
(3)INNER JOINする場合
INNER JOINすると1回SQLでデータを取得できますが、typeデータに関連データが無い場合のhoge1データが欠落するので注意が必要です。
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->createQuery('SELECT h1, t FROM RootTestBundle:Hoge1 h1 JOIN h1.Hoge1Types t') ->getResult() ; foreach($all as $hoge1) { print $hoge1->getName().'<br />'; foreach($hoge1->getHoge1Types() as $type) print $type->getName().'<br />'; } # 実行結果 master1 hoge1_type1 master2 hoge2_type1 hoge2_type2 master4 hoge4_type1 hoge4_type2 hoge4_type3 hoge4_type4 # 発行SQL SELECT h0_.id AS id0, h0_.name AS name1, h1_.id AS id2, h1_.hoge1_id AS hoge1_id3, h1_.name AS name4, h1_.hoge1_id AS hoge1_id5 FROM hoge1 h0_ INNER JOIN hoge1_type h1_ ON h0_.id = h1_.hoge1_id
(4)LEFT JOINする場合
これでmaster3、master5もデータを取得できました。
ただし、この場合はlimit句で5などと区切った場合master4のtypeデータに欠落が生じる事になるので注意が必要です。
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->createQuery('SELECT h1, t FROM RootTestBundle:Hoge1 h1 LEFT JOIN h1.Hoge1Types t') ->getResult() ; foreach($all as $hoge1) { print $hoge1->getName().'<br />'; foreach($hoge1->getHoge1Types() as $type) print $type->getName().'<br />'; } # 実行結果 master1 hoge1_type1 master2 hoge2_type1 hoge2_type2 master3 master4 hoge4_type1 hoge4_type2 hoge4_type3 hoge4_type4 master5 # 発行SQL SELECT h0_.id AS id0, h0_.name AS name1, h1_.id AS id2, h1_.hoge1_id AS hoge1_id3, h1_.name AS name4, h1_.hoge1_id AS hoge1_id5 FROM hoge1 h0_ LEFT JOIN hoge1_type h1_ ON h0_.id = h1_.hoge1_id
4. hoge1_type(many)からhoge1(one)のデータにアクセス
(1)hoge1を取得
一度取得したhoge1はキャッシュされる。
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->createQuery('SELECT t FROM RootTestBundle:Hoge1Type t') ->getResult() ; foreach($all as $type) { print $type->getHoge1()->getName().':'. $type->getName().'<br />'; } # 実行結果 master1:hoge1_type1 master2:hoge2_type1 master2:hoge2_type2 master4:hoge4_type1 master4:hoge4_type2 master4:hoge4_type3 # 発行SQL SELECT h0_.id AS id0, h0_.hoge1_id AS hoge1_id1, h0_.name AS name2, h0_.hoge1_id AS hoge1_id3 FROM hoge1_type h0_ SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["1"]) SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["2"]) SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["4"])
(2)JOINしてhoge1を取得
JOINしてデータが重複した場合でも、よろしくORMにMAPしてくれる
# 処理 $em = $this->getDoctrine()->getEntityManager(); $all = $em->createQuery('SELECT t, h1 FROM RootTestBundle:Hoge1Type t JOIN t.hoge1 h1') ->getResult() ; foreach($all as $type) { print $type->getHoge1()->getName().':'. $type->getName().'<br />'; } # 実行結果 master1:hoge1_type1 master2:hoge2_type1 master2:hoge2_type2 master4:hoge4_type1 master4:hoge4_type2 master4:hoge4_type3 master4:hoge4_type4 # 発行SQL SELECT h0_.id AS id0, h0_.hoge1_id AS hoge1_id1, h0_.name AS name2, h1_.id AS id3, h1_.name AS name4, h0_.hoge1_id AS hoge1_id5 FROM hoge1_type h0_ INNER JOIN hoge1 h1_ ON h0_.hoge1_id = h1_.id
以上です。