차근차근/C

OLEDB사용 관련 질문입니다.

예쁜꽃이피었으면 2015. 7. 27. 14:08

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNo=20&no=723764&ref=723735


open과 openAll은,

OLE DB에서 DB 액세스를 하려면 3가지가 필요합니다.

DataSource, Sesssion, Rowset 이죠.

이 셋을 SQL과 비교하면

DB, DB 커넥션, 쿼리 결과입니다.

 

저는 마법사가 생성해주는 open, openAll 잘 안쓰는데요..

왜냐하면 프로그램 실행할 때 DataSource 랑 Sesssion을 오픈해서 전역변수에 넣어두는게 편하니까요.

 

Session은 DB 커넥션이고 트랜잭션의 단위입니다.

따라서 불필요하게 많이 만들면 리소스 낭비가 되고

한 스레드에서 만든 세션을 다른 스레드에서 그냥 쓸 수도 없고

(마샬링을 하면 쓸 수 있을지 없을지 불확실)

한 세션에서 업데이트한게 다른 세션으로 전파되려면 약간 시간이 걸리기도 합니다.

(트랜잭션 분리 레벨이 Read Commited일지라도 시간차는 존재함)

 

그러니 명시적으로 DataSource, Sesssion, Rowset을 각각 open해주는 게 좋고,

특히 Insert, Delete, Update만 할 때는 CTable로 오픈하는게 편합니다.

간단한 1줄 짜리 delete 쿼리 같은 건 그냥 SQL로 명령을 만든 다음에

 

 tstringstream os;
 tstring cmd;
 CCommand<> rs;

 os << _T("DELETE * FROM Department WHERE DeptID=") << did;
 getline(os, cmd);
 CHECK(rs.Open(ss, cmd.c_str()));

이런 식으로 액세서 없는 CCommand 클래스로 실행시켜주는게 편하구요.

 

또한, 필요에 맞게 프로퍼티를 잘 설정해줘야 하는데,

IRowsetChange는 AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL)를 해줘야 나오고 이외에 몇가지를 더해줘야 insert, update가 가능해집니다.

아무튼 프로퍼티 종류가 많아서 이건 MSDN을 참고해야 합니다.

 

저는 예전에 업데이트 로우셋을 열 때

template <typename T>
class CUpdateCommand {
public:
 CCommand< CAccessor<T> > m_rowset;
 void Open(CSession& session, LPCTSTR command)
 {
  CDBPropSet pset(DBPROPSET_ROWSET);
  pset.AddProperty(DBPROP_OWNUPDATEDELETE, true);
  pset.AddProperty(DBPROP_IRowsetChange, true);
  pset.AddProperty(DBPROP_UPDATABILITY,
   DBPROPVAL_UP_CHANGE|DBPROPVAL_UP_INSERT|DBPROPVAL_UP_DELETE);
  pset.AddProperty(DBPROP_IRowsetLocate, true);
  pset.AddProperty(DBPROP_BOOKMARKS, true);
  pset.AddProperty(DBPROP_CANFETCHBACKWARDS, true);
  pset.AddProperty(DBPROP_IRowsetScroll, true);
  pset.AddProperty(DBPROP_CANSCROLLBACKWARDS, true);

  CHECK(m_rowset.Open(session, command, &pset));
 }
};
이런 식으로 했었습니다.

반복해서 사용하게 되니 따로 함수로 만들어 논 셈이죠. (북마크는 로우셋에서 특정 레코드를 기억해두었다가 그 레코드로 이동하기 위해 사용. 칼럼 맵에 북마크가 포함되어야 함)

 

그리고 실제 insert, update, delete는 아래와 같은 식으로 하구요.

template <typename T, template <typename T> class U>
class CRowsetChanger : public U<T> {
public:
 CRowsetChanger()
 {
 }
 ~CRowsetChanger()
 {
 }
 void GetData(const bookmark_t& bk)
 {
  CHECK(m_rowset.MoveToBookmark(bk));
  CHECK(m_rowset.GetData(0));
 }
 void GetData(const bookmark_t& bk, T& o)
 {
  CHECK(m_rowset.MoveToBookmark(bk));
  CHECK(m_rowset.GetDataHere(0, &o));  => 현재 액세서말고 다른 액세서 변수에 값 읽어서 넣기
 }
 void Insert(T& o)
 {
  CHECK(m_rowset.MoveNext()); => insert 시는 항상 Rowset이 정상 상태가 되도록..
  (T&)m_rowset = o;
  CHECK(m_rowset.Insert(0, true));
  CHECK(m_rowset.GetDataHere(0, &o));
 }
 void Delete()
 {
  _ASSERTE(T::CanDelete());
  CHECK(m_rowset.Delete());
 }
 void Delete(const bookmark_t& bk)
 {
  CHECK(m_rowset.MoveToBookmark(bk));
  Delete();
 }
 void UpdateCurrent()
 {
  CHECK(m_rowset.SetData(0));
 }
 void Update(const T& src)
 {
  (T&)m_rowset = src;
  CHECK(m_rowset.SetData(0));
 }
 void Update(const bookmark_t& bk, const T& src)
 {
  GetData(bk);
  Update(src);
 }
};

 

그리고 Insert, Update 시에는 기본키 외래키, 참조 무결성 등을 주의해야 합니다.

기본키는 업데이트를 안할 건데 그냥 업데이트를 호출하면 기본키 변경이 불가능하다고 예외가 발생합니다.

그래서 업데이트를 안할 칼럼은 STATUS 값을 명시적으로 DBSTATUS_S_IGNORE로 해줘야 합니다.

 

SELECT의 경우도, 액세서를 여러개 정의해서 쿼리의 종류에 따라 액세서를 바꿔가면서 쓸 수 있습니다.

(BEGIN_ACCESSOR 매크로의 첫번째 매개변수가 액세서 번호)

그래서 액세서도 다 직접 손으로 짜게 되죠..

 

아래 제가 만든 액세서 정의 참고하셔요.

 

class AnalysisData {
public:
 AnalysisData()
 { memset((void*)this, 0, sizeof(*this)); }
 ~AnalysisData()
 {
  if (m_data)
   m_data->Release();
 }
 static const TCHAR* GetSurvCommand(int sid)
 {
  static TCHAR* fmt = _T("SELECT * FROM AnalysisData WHERE SurveyID=%d");
  static CString cmd;
  cmd.Format(fmt, sid);
  return cmd;
 }
 static const TCHAR* GetCommand(int aid)
 {
  static TCHAR* fmt = _T("SELECT * FROM AnalysisData WHERE AnalysisID=%d");
  static CString cmd;
  cmd.Format(fmt, aid);
  return cmd;
 }
 static bool CanDelete()
 {
  return true;
 }
 void SetIgnoreAll()
 {
  ms_ID = DBSTATUS_S_IGNORE;
  ms_type = DBSTATUS_S_IGNORE;
  ms_title = DBSTATUS_S_IGNORE;
  ms_data = DBSTATUS_S_IGNORE;
  ms_sid = DBSTATUS_S_IGNORE;
 }

 int m_ID;
 int m_type;
 TEXT21 m_title;
 ISequentialStream* m_data;
 int m_sid;

 DBSTATUS ms_ID;
 DBSTATUS ms_type;
 DBSTATUS ms_title;
 DBSTATUS ms_data;
 DBSTATUS ms_sid;

 bookmark_t m_bk;

 BEGIN_ACCESSOR_MAP(AnalysisData, 3)
  BEGIN_ACCESSOR(0, false)
   BOOKMARK_ENTRY(m_bk)
   COLUMN_ENTRY_STATUS(1, m_ID, ms_ID)
   COLUMN_ENTRY_STATUS(2, m_type, ms_type)
   COLUMN_ENTRY_STATUS(3, m_title, ms_title)
   COLUMN_ENTRY_STATUS(5, m_sid, ms_sid)
  END_ACCESSOR()
  BEGIN_ACCESSOR(1, false)
   BOOKMARK_ENTRY(m_bk)
   COLUMN_ENTRY_STATUS(1, m_ID, ms_ID)
   COLUMN_ENTRY_STATUS(2, m_type, ms_type)
   COLUMN_ENTRY_STATUS(3, m_title, ms_title)
   BLOB_ENTRY_STATUS(4, IID_ISequentialStream, STGM_READ, m_data, ms_data)
   COLUMN_ENTRY_STATUS(5, m_sid, ms_sid)
  END_ACCESSOR()
  BEGIN_ACCESSOR(2, false)
   BOOKMARK_ENTRY(m_bk)
   COLUMN_ENTRY_STATUS(1, m_ID, ms_ID)
   COLUMN_ENTRY_STATUS(2, m_type, ms_type)
   COLUMN_ENTRY_STATUS(3, m_title, ms_title)
   BLOB_ENTRY_STATUS(4, IID_ISequentialStream, STGM_WRITE, m_data, ms_data)
   COLUMN_ENTRY_STATUS(5, m_sid, ms_sid)
  END_ACCESSOR()
 END_ACCESSOR_MAP()
};

 

insert, update 전에 status를 초기화 해주기 위한 SetIgnoreAll() 함수

image, text 칼럼을 액세스하기 위한 BLOB_ENTRY_STATUS 매크로도 참고하셔요..

(BLOB 칼럼의 경우는 위에 보이는 것처럼 읽기용 액세서와 쓰기용 액세서를 따로 만들어야 함)

 

반응형