코드이그나이터4 데이터베이스 다루기 - 8 - 모델 조인

koeunyeon·2021년 4월 5일
0

모델 조인

이번 챕터의 소스는 https://github.com/koeunyeon/ci4/tree/model-join에서 찾으실 수 있습니다.

데이터베이스 테이블 조인의 정의

관계형 데이터베이스를 다루다보면 테이블 하나만 다루는 경우는 거의 없습니다. 대부분 여러 테이블의 데이터를 엮어서 가지고 오죠. 여러 테이블의 데이터를 엮는 것을 조인(join)이라고 부릅니다. 이번에는 코드이그나이터4에서 모델을 조인하는 방법을 알아보겠습니다.
우리가 만들 테이블의 간단한 ERD 는 아래와 같습니다.

부모 테이블(sample_parent) 과 자식 테이블(sample_child) 이 서로 1대 다(1:N)의 관계를 가집니다. 즉 부모 행 하나에 여러 자식 행이 있을 수 있다는 뜻입니다.(0개일 수도 있습니다.)
부모 테이블과 자식 테이블 사이는 외래키(Foreign Key)로 연결됩니다. 자식테이블의 sample_parent_id 열의 데이터는 부모 테이블의 sample_parent_id 중 하나여야 합니다. 부모가 없는 자식은 존재할 수 없기 때문입니다.

두 테이블을 연결하는 SQL 쿼리는 아래와 같습니다.

select * from
sample_parent 
join sample_child
on sample_parent.sample_parent_id = sample_child.sample_parent_id

join 키워드 이후 연결할 테이블 이름을 넣고, on으로 연결할 조건을 지정합니다.

마이그레이션을 통한 테이블 생성

터미널에서 relation 마이그레이션 파일을 생성합니다.

php spark migrate:create relation

app/Database/Migrations 디렉토리 아래 relation 마이그레이션 파일을 아래와 같이 수정합니다.

<?php namespace App\Database\Migrations;


use CodeIgniter\Database\Migration;


class Relation extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'sample_parent_id'          => [
                'type'           => 'BIGINT',
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'parent_name'       => [
                'type'       => 'VARCHAR',
                'constraint' => '40',
            ]
        ]);
        $this->forge->addKey('sample_parent_id', true);
        $this->forge->createTable('sample_parent');

        $this->forge->reset(); // (1)
        $this->forge->addField([
            'sample_child_id'          => [
                'type'           => 'BIGINT',
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'sample_parent_id'       => [
                'type'       => 'BIGINT',
                'unsigned'       => true,
            ],
            'child_name'       => [
                'type'       => 'VARCHAR',
                'constraint' => '40',
            ]
        ]);
        $this->forge->addKey('sample_child_id', true);
        $this->forge->addForeignKey('sample_parent_id', 'sample_parent', 'sample_parent_id'); // (2)
        $this->forge->createTable('sample_child');


    }

    //--------------------------------------------------------------------

    public function down()
    {
        $this->forge->dropTable('sample_child');
        $this->forge->dropTable('sample_parent');

    }
}

코드를 확인해 보겠습니다.
(1) 이번 마이그레이션은 동시에 두개의 테이블을 만듭니다. sample_parentsample_child입니다. forge 객체는 멤버변수여서 유일하기 때문에, 테이블 두 개 이상을 하나의 마이그레이션에서 생성하려면 첫번째 테이블을 생성 => 포지 초기화 => 두번째 테이블 생성의 절차를 거쳐야 합니다.
포지를 초기화하기 위해서는 $this->forge->reset(); 메소드를 사용합니다.

(2) 외래키를 등록합니다. 관계형 데이터베이스에서 외래키가 등록되면 테이블 두 개 사이에 강하게 관계가 엮입니다. 외래키가 있으면 만약 부모 테이블에 없는 주 키가 자식 테이블에 입력될 경우 데이터베이스가 오류를 뱉어냅니다.
코드이그나이터4의 마이그레이션에서 외래키는 addForeignKey(자식 테이블 외래키 열 이름, 부모 테이블 이름, 부모 테이블의 주 키 이름) 메소드로 등록할 수 있습니다.


마이그레이션을 통해 데이터베이스 테이블을 생성합시다.

php spark migrate

모델 만들기

조인 기능을 확인하기 위한 모델 클래스 두 개를 만듭니다.
첫번째 모델을 만들겠습니다. /app/Models 디렉토리 아래에 SampleParentModel.php 클래스를 생성합니다. 이 모델은 부모 클래스에 대응합니다.
/app/Models/SampleParentModel.php

<?php


namespace App\Models;


use CodeIgniter\Model;

class SampleParentModel extends Model
{
    protected $table = 'sample_parent';
    protected $allowedFields = ['parent_name'];
}

특별한 코드는 없으므로 설명은 생략합니다.


자식 테이블에 대응하는 클래스도 만들어보겠습니다. 이름은 SampleChildModel 입니다.
/app/Models/SampleChildModel.php

<?php
namespace App\Models;
use CodeIgniter\Model;

class SampleChildModel extends Model
{
    protected $table = 'sample_child';
    protected $allowedFields = ['sample_parent_id', 'child_name']; // (1)
}

(1) $allowedFields 내에 sample_parent_id가 있습니다. 외래키는 직접 입력해야 하기 때문에 읽고 쓰기 속성에 추가합니다.

시더를 통한 데이터 생성

시더는 테이블에 초기 데이터를 등록하기 위한 방법입니다.
개발을 하다 보면 기초 데이터를 정의하거나, 아니면 임의 데이터를 넣음으로서 일단 동작을 테스트하는 경우가 있죠. 시더는 하나씩 데이터베이스에 직접 SQL문을 통해 데이터를 입력하는 대신, 프로그램을 통해 데이터를 입력하기 위한 방법입니다.

이번 예제는 Faker 라는 라이브러리를 사용합니다. 코드이그나이터 4.0.4 버전에는 기본으로 의존성이 포함되어 있으나, 혹시 코드이그나이터 4.0.4 이상 버전에서 의존성이 빠질 경우를 대비해서 컴포저를 통해 설치하는 방법을 기재합니다.

composer require --dev fakerphp/faker

시더를 터미널에서 생성합니다.

php spark make:seeder RelationSeeder

app/Database/Seeds/RelationSeeder.php 파일이 생성된 것을 확인할 수 있습니다. 아래와 같이 수정하겠습니다.
app/Database/Seeds/RelationSeeder.php

<?php namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;
use CodeIgniter\Test\Fabricator;
use App\Models\SampleChildModel;
use App\Models\SampleParentModel;

class RelationSeeder extends Seeder
{
   public function run()
   {
        // 임의 데이터 세팅
        foreach (range(1,5) as $parent_idx) { // (1)
            $sampleParentFabricator = new Fabricator(SampleParentModel::class); // (2)
            $parent = $sampleParentFabricator->makeArray(); // (3)
            $sampleParentModel = new SampleParentModel();
            $parent_id = $sampleParentModel->insert($parent); // (4)


            foreach (range(1,random_int(3,8)) as $parent_idx) { // (5)
                $sampleChildFabricator = new Fabricator(SampleChildModel::class); // (6)
                $child = $sampleChildFabricator->makeArray();
                $child['sample_parent_id'] = $parent_id; // (7)
                $sampleChildModel = new SampleChildModel();
                $sampleChildModel->insert($child);
            }
        }
   }
}

(1) 임의의 데이터를 등록하기 위해 반복문을 시작합니다. 예제에서는 부모테이블에 5개의 행을, 자식테이블에 각 부모 테이블 하나당 3-8개 사이의 데이터를 넣을 예정입니다.

(2) 사실 개발 테스트를 위해서 직접 손으로 데이터를 넣고 있는 과정은 꽤나 지루하고, 실수하기도 쉽습니다. 이 과정을 자동화하기 위해서 우리는 코드이그나이터4에 포함된 데이터 자동 생성 기능을 사용하겠습니다.
코드이그나이터4는 FAKER라는 이름의 가짜 데이터 생성기를 이용해 자동으로 가짜 데이터를 만드는 Fabricator(패브리케이터)라는 클래스가 존재합니다. 생성자에 모델 클래스를 전달하면 자동으로 모델 클래스의 $allowedFields 의 값에 따라 가짜 데이터를 만들어줍니다.

(3) 패브리케이터가 데이터를 생성했다면 배열로 바꿔보겠습니다. makeArray() 메소드를 이용하면 패브리케이터가 생성한 데이터를 연관배열로 반환합니다.

(4) 부모 테이블에 데이터를 넣습니다. 이 때 부모 테이블의 주 키는 자식 테이블에서 사용해야 하므로 $parent_id 변수에 담아둡니다.

(5) 자식 테이블에 데이터를 넣기 위한 반복문을 시작합니다. 각 부모 테이블마다 3-8개의 데이터를 넣기로 했으므로 random_int(3,8)을 이용해 최소 3번, 최대 8번까지 반복합니다.

(6) 자식 클래스의 패브리케이터를 생성합니다.

(7) 자식 클래스의 임의 데이터가 생성되었다면, 이번엔 부모 테이블의 주 키 정보를 설정해야 합니다. 외래키가 설정되었으므로 부모 테이블의 주 키가 입력되지 않으면 데이터가 저장되지 않기 때문입니다.


시더를 통해서 데이터를 생성합니다.

php spark db:seed RelationSeeder

데이터가 만들어진 것을 확인할 수 있습니다.

컨트롤러 만들기

실제 조인을 할 컨트롤러를 만들어 봅니다.
app/Controllers/Join.php

<?php
namespace App\Controllers;
use App\Models\SampleParentModel;

class Join extends BaseController
{
    public function index()
    {
        $parentModel = new SampleParentModel();
        $all_result = $parentModel
            ->join("sample_child", "sample_parent.sample_parent_id = sample_child.sample_parent_id") // (1)
            ->orderBy("sample_parent.parent_name") // (2)
            ->findAll();

        return view("join", ['all_result' => $all_result]); // (3)
    }
}

코드를 확인해 보겠습니다.
(1) 개별 모델은 join 메소드를 가집니다. 데이터베이스의 join에 해당하는데요. 첫번째 파라미터는 연결할 테이블, 두번째 파라미터는 연결 조건입니다. 각각 쿼리문의 join, on에 해당합니다.

(2) 조인을 해도 정렬을 할 수 있습니다. 다만 테이블이 2개이기 때문에 열 이름이 유일하지 않다면 테이블명.열 이름 형태로 정렬 조건을 설정해야 합니다. 이는 SQL문에서도 동일합니다.

(3) 데이터가 꽤 많기 때문에 뷰를 반환해서 확인하겠습니다.

뷰 만들기

app/views/join.php 파일을 생성합니다.
app/views/join.php

<table>
    <tr>
        <td>parent_name</td>
        <td>child_name</td>
        <td>parent_id</td>
        <td>child_id</td>
    </tr>

<?php foreach ($all_result as $row) : ?>
    <tr>
        <td><?= $row['parent_name'] ?></td>
        <td><?= $row['child_name'] ?></td>
        <td><?= $row['sample_parent_id'] ?></td>
        <td><?= $row['sample_child_id'] ?></td>
    </tr>
<?php endforeach; ?>
</table>

뷰 코드는 특별한 것이 없으므로 설명은 생략합니다.


http://localhost:8080/join 주소에서 확인해 봅니다.

profile
스타트업에 관심이 많은 10 + n년차 웹 개발자. 자바 스프링 (혹은 부트), 파이썬 플라스크, PHP를 주로 다룹니다.

0개의 댓글