TIP게시판

제목 다중 데이터베이스 접속 및 모델, 프로파일러 확장
글쓴이 kirrie 작성시각 2010/05/12 23:55:53
댓글 : 2 추천 : 0 스크랩 : 0 조회수 : 20088   RSS
일반적으로 많은 사이트들이 단일 데이터베이스를 많이 사용합니다만, 규모가 커지기 시작하면 다수의 데이터베이스를 기능에 따라 분리하거나 커넥션 풀등을 이용해서 사용하기 시작합니다.
우리의 CI도 이에 대응하여 다중 데이터베이스에 접속할 수 있는 환경을 구성해놓기는 했습니다만, 아직까지 이런 환경에서 CI를 돌리는 레퍼런스 사이트들이 많지 않은 까닭인지 이에 대한 지원이 부족합니다.

그런 연고로 CI에서 모델을 확장해 다중 데이터베이스를 쉽게 사용하고, 덤으로 프로파일러에서도 정상적으로 접속된 여러대의 데이터베이스별로 분리된 쿼리 디버깅이 가능하도록 몇가지 팁을 올려볼까 합니다.

참고로 1.7.2버전을 사용했으며, 예제 파일들은 안타깝게도 php5 이상에서만 동작합니다. (__get() 매직 메소드 때문에) 하지만 동작 원리를 이해하시면 하위 버전에서도 구현하실 수 있으리라 믿습니다.

1. 스토리라인
* www.foo.com은 DB1, DB2, DB3, DB4 총 네 대의 데이터베이스를 운용하고 있으며 이들 데이터베이스는 각각 용도를 갖고 있습니다. 경우에 따라서 한 스크립트 내에 유동적으로 이들 네 대의 데이터베이스에 접속하여 레코드를 가져올 수 있어야 합니다.
* 그러나 하나 이상의 데이터베이스에 접속할 때 그 방법이 복잡하거나 유연하지 않으면 안됩니다. (즉, 정말 쉽게 접속할 수 있어야 합니다. 직관적이어야 합니다.)
* 데이터베이스에 접속하여 쿼리를 전송하거나 레코드를 가져오는 것은 모델에 한정하는 것으로 합니다. (즉, 컨트롤러에서 직접 데이터베이스에 접속하거나 하는 상황을 전제하지 않습니다.)
* 덤으로 프로파일링시에 각각의 데이터베이스에 던진 쿼리의 내용들이 분리되어 표시될 수 있어야 합니다.

2. database.php
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the "Database Connection"
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
|	['hostname'] The hostname of your database server.
|	['username'] The username used to connect to the database
|	['password'] The password used to connect to the database
|	['database'] The name of the database you want to connect to
|	['dbdriver'] The database type. ie: mysql.  Currently supported:
				 mysql, mysqli, postgre, odbc, mssql, sqlite, oci8
|	['dbprefix'] You can add an optional prefix, which will be added
|				 to the table name when using the  Active Record class
|	['pconnect'] TRUE/FALSE - Whether to use a persistent connection
|	['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
|	['cache_on'] TRUE/FALSE - Enables/disables query caching
|	['cachedir'] The path to the folder where cache files should be stored
|	['char_set'] The character set used in communicating with the database
|	['dbcollat'] The character collation used in communicating with the database
|
| The $active_group variable lets you choose which connection group to
| make active.  By default there is only one group (the "default" group).
|
| The $active_record variables lets you determine whether or not to load
| the active record class
*/

$active_group = "DB1";
$active_record = FALSE;

$db['DB1']['hostname'] = "aaa";
$db['DB1']['username'] = "bbb";
$db['DB1']['password'] = "ccc";
$db['DB1']['database'] = "DB1";
$db['DB1']['dbdriver'] = "oci8";
$db['DB1']['dbprefix'] = "";
$db['DB1']['pconnect'] = TRUE;
$db['DB1']['db_debug'] = TRUE;
$db['DB1']['cache_on'] = FALSE;
$db['DB1']['cachedir'] = "";
$db['DB1']['char_set'] = "euckr";
$db['DB1']['dbcollat'] = "euckr_korean_ci";

$db['DB2']['hostname'] = "aaa";
$db['DB2']['username'] = "bbb";
$db['DB2']['password'] = "ccc";
$db['DB2']['database'] = "DB2";
$db['DB2']['dbdriver'] = "oci8";
$db['DB2']['dbprefix'] = "";
$db['DB2']['pconnect'] = TRUE;
$db['DB2']['db_debug'] = TRUE;
$db['DB2']['cache_on'] = FALSE;
$db['DB2']['cachedir'] = "";
$db['DB2']['char_set'] = "euckr";
$db['DB2']['dbcollat'] = "euckr_korean_ci";

$db['DB3']['hostname'] = "aaa";
$db['DB3']['username'] = "bbb";
$db['DB3']['password'] = "ccc";
$db['DB3']['database'] = "DB3";
$db['DB3']['dbdriver'] = "oci8";
$db['DB3']['dbprefix'] = "";
$db['DB3']['pconnect'] = TRUE;
$db['DB3']['db_debug'] = TRUE;
$db['DB3']['cache_on'] = FALSE;
$db['DB3']['cachedir'] = "";
$db['DB3']['char_set'] = "euckr";
$db['DB3']['dbcollat'] = "euckr_korean_ci";

$db['DB4']['hostname'] = "aaa";
$db['DB4']['username'] = "bbb";
$db['DB4']['password'] = "ccc";
$db['DB4']['database'] = "DB4";
$db['DB4']['dbdriver'] = "oci8";
$db['DB4']['dbprefix'] = "";
$db['DB4']['pconnect'] = TRUE;
$db['DB4']['db_debug'] = TRUE;
$db['DB4']['cache_on'] = FALSE;
$db['DB4']['cachedir'] = "";
$db['DB4']['char_set'] = "euckr";
$db['DB4']['dbcollat'] = "euckr_korean_ci";
/* End of file database.php */
/* Location: ./system/application/config/database.php */
일단 application/config/database.php에 필요한 만큼의 접속 정보를 씁니다. (단, 각각의 접속 정보들은 group명이 달라야 합니다.)

3. MY_Model.php
<?php
class MY_Model extends Model
{
	function MY_Model()
	{
		parent::Model();
	}
	
	function __get($name)
	{
		$CI =& get_instance();
		
		foreach (get_object_vars($CI) as $CI_object_name => $CI_object)
		{
			if (is_object($CI_object) && is_subclass_of(get_class($CI_object), 'CI_DB') && $CI_object_name == $name)
			{
				return $CI_object;
			}
		}
		
		$CI->$name = $CI->load->database($name, TRUE);
		
		return $CI->$name;
	}
}

/* End of file */
(중요) 모델을 확장합니다. (참고로, __get() 메소드는 어떤 클래스에 정의되지 않은 멤버변수를 호출할 경우 실행되는 메소드입니다. 인자로는 호출한 멤버변수의 이름이 주어집니다.)

계획된 바는 이렇습니다. user_model 안에서 $this->DB1->query('select * from users');를 호출하였을 경우, 사전에 DB1 데이터베이스에 접속해놓지 않고도 CI core 오브젝트인 $this에 DB1이라는 이름을 갖는 CI_DB 의 sub class 가 없을 경우엔 해당 데이터베이스인 DB1에 접속하여 접속된 CI_DB 클래스의 인스턴스를 다시 CI core에 할당하고, 있을 경우엔 같은 접속을 중복하지 않고 기 접속된 디비 인스턴스를 반환할 것입니다.

4. user_model.php
<?php
class User_model extends MY_Model
{
	function User_model()
	{
		parent::MY_Model();
	}
	
	function test_db1()
	{
		return $this->DB1->query('select * from tbl1')->row_array();
	}
	
	function test_db2()
	{
		return $this->DB2->query('select * from tbl2')->row_array();
	}

	function test_db3()
	{
		return $this->DB3->query('select * from tbl3')->row_array();
	}

	function test_db4()
	{
		return $this->DB4->query('select * from tbl4')->row_array();
	}	
}
/* End of file */
다음은 예제로 작성된 user_model.php 파일입니다. 보시다시피 데이터베이스 접속에 관련된 코드는 전혀 없습니다. 단, 모델들은 CI 기본 모델이 아닌 확장된 MY_Model을 상속한다는 것이 다릅니다. $this->DB1으로 선언되는 query 메소드는 쿼리를 DB1으로 보내고, 마찬가지로 DB2는 DB2로 보낼 것입니다.

5. MY_Profiler.php
사실 프로파일러에 아주 작은 버그가 있어서 자체를 수정할까 하다가 그냥 확장해서 첨부했습니다. Profiler.php의 143번째 줄에
count($this->CI->db->queries) 라는 부분이 있는데 이것은 count($db->queries)로 변경되어야 합니다.

6. main.php (테스트 컨트롤러)
<?php

class Main extends Controller {

	function Main()
	{
		parent::Controller();	
	}
	
	function index()
	{
		$this->output->enable_profiler(true);
		
		$this->user_model->test_db1();
		$this->user_model->test_db2();
		$this->user_model->test_db3();
		$this->user_model->test_db4();
	}
}

/* End of file welcome.php */
/* Location: ./system/application/controllers/welcome.php */
자, 모든 파일이 정상적이고 database.php에 설정된 각 데이터베이스의 접속정보가 옳다면 위 컨트롤러는 각각 분리된 데이터베이스에 쿼리를 전송하고 프로파일러에도 올바르게 정보가 표시될겁니다.

7. 설명한다고 했는데, 잘 이해가 안되시면 코멘트 남겨주세요. ^^

태그 다중데이터베이스,데이터베이스,다중,모델,프로파일러
첨부파일 examples.zip (2.7 KB)
 다음글 파일업로드시 타입 에러 (5)
 이전글 CodeIgniter and Ajax using jQu... (2)

댓글

배강민 / 2010/05/13 08:45:28 / 추천 0

멋쟁이.... 냠냠 맛있게 먹을껭...

변종원(웅파) / 2010/05/13 09:37:32 / 추천 0
Good~~~~~
Codeigniter1.7.3+Matchbox+한글매뉴얼에 포함해놔야겠네. ^^