znajomy mnie prosił, abym mu zrobił mały programik (zaliczenie) przy tej okazji, aby się czegoś nauczyć chciałem napisać bibliotekę do MySQL-a
coś napisałem jakoś to działa, ale jako, że do tej pory głównie PHP (przez co wiele na rzeczy nie musiałem zwracać uwagi) to nie wiem czy to jest napisane dobrze, poprawnie, czy takie coś może być i dodatkowo mam parę pytań
klasa jest Singletonem (mam nadzieję, że dobrze go w ogóle zastosowałem), chyba dobrze, ale w związku z tym pojawiają się pytania
1 - obsługa wielowątkowości, jak to wykonać, przykład gdy wątki próbują robić coś równocześnie (jak temu zapobiec, lub jak to obsłużyć) np w przypadku obiektu klasy RECORDSET jeden cos tam jeszcze myka pobiera, się wcina i przed pobraniem następuje zamkniecie (close()) obiektu recordset
2 - jak pobrać ilość zmodyfikowanych wierszy (np poprzez UPDATE, DELETE)
3 - o co jeszcze można by klasę rozszerzyć, co zmienić, co wyrzucić
chciałbym aby ta klasa była w miarę do wykorzystania w różnych sytuacjach i jakoś uniwersalna i dlatego liczę na jakieś uwagi, pomysły, wnioski itp itd i z góry za nie dziękuję, bo wiem, ze kodu jest dużo
plik nagłówkowy
#pragma once
#include <afxdb.h>
#include "stdafx.h"
class dbMySQLOperation
{
private:
//wskaźnik na instancje obiektu - STATYCZNY
static dbMySQLOperation *dbMySQLOperationInstance;
//wskaźnik na CDatabase - STATYCZNY
static CDatabase *m_pDBConn;
//wskaźnik na CRecordset - STATYCZNY
static CRecordset *m_pRecorset;
//zmienne przechowujące kod błedu i tekst błędu
CString m_strErrorInfo;
UINT m_uiErrorCode;
//konstruktor prywatny
dbMySQLOperation(void);
//metoda łącząca z bazą danych
BOOL makeConection(CString,BOOL bNoODBC=FALSE) const;
//metoda sprawdzająca czy RECORDSET jest otwarty i ZAMYKAJĄCA GO
void checkRecordsetOpen();
public:
//destruktor
~dbMySQLOperation(void);
//metoda zwarcająca instancję klasy
static dbMySQLOperation* getInstance();
//metoda łącząca z MySQL (dokładniej tworzy connection string, znaleziony na stronie MySQL.com
BOOL setConnection(CString strServer,CString strDataBase, CString strUser,CString strPass, CString strCharset=_T("UTF8"));
//metoda łącząca z MySQL (dokładniej tworzy connection string, znaleziony na stronie MySQL.com), ale z opcją noOdbcBialog
BOOL setConnectionNoODBCDlg(CString strServer, CString strUser, CString strPass, CString strDB);
//metoda do SELECTÓW - zwraca wskaźnik do recordset
CRecordset* selectRecord(CString sql,UINT uiOpenType,UINT uiOpenOption);
//metoda do wykonania zapytania wywołuje m_pDBConn->ExecuteSQL
void executeQuery(CString sqlQuery);
/*metoda wywołująca zapytanie modyfikujące (INSERT,UPDATE,DELETE),
jeśli jest INSERT zwraca LAST_INSERT_ID
jeśłi jest UPDATE,DELETE chcę aby zwracało ILOŚ ZMODYFIKOWANYCH REKORDÓW, ALE NIE WIEM JAK TO ZROBI
*/
UINT insertUpdateDelete(CString sqlQuery, CString strTable=_T(""),BOOL iInsert=FALSE);
//pobieranie informacji tekstowej o błedzie
CString getErrorInfo() const;
//pobieranie kodu błędu
UINT getErrorCode() const;
//sprawdzanie czy możliwa jest transakcja
BOOL transEnabled() const;
//rozpoczęcie transakcji
BOOL beginTrans() const;
//zakończenie transakcji - jeśli warunek jest prawda to COMMIT jeśli fałszem to ROLLBACK
BOOL endTrans(BOOL bCondition) const;
//sprawdzenie czy zakończyć transakcję - jeśli warunek jest FAŁSZEM jest ROLLBACK
BOOL checkRollback(BOOL bCondition) const;
//sprawdzenie czy baza danych jest otwarta (połączenie jest otwarte)
BOOL checkDBOpen() const;
//pobieranie jednej wartości z zapytnia (najczęsciej pewnie będzie to COUNT) znajdującej się na pierwszym miejscy w zapytaniu SELECT
void getScalarValue(CString strQuery, CDBVariant &varDB, UINT uiFieldtype=0);
};
plik CPP
#include "StdAfx.h"
#include "dbMySQLOperation.h"
//inicjalizacja zmiennych statycznych
dbMySQLOperation *dbMySQLOperation::dbMySQLOperationInstance = NULL;
CDatabase *dbMySQLOperation::m_pDBConn=NULL;
CRecordset *dbMySQLOperation::m_pRecorset=NULL;
//konstruktor
dbMySQLOperation::dbMySQLOperation(void)
{
}
//destruktor - usuwanie wskaźników, zamykanie połączenia z bazą danych
dbMySQLOperation::~dbMySQLOperation(void)
{
if(dbMySQLOperationInstance!=NULL)
delete(dbMySQLOperationInstance);
if(m_pDBConn!=NULL)
delete(m_pDBConn);
if(m_pRecorset!=NULL)
delete(m_pRecorset);
m_pRecorset=NULL;
m_pDBConn = NULL;
dbMySQLOperationInstance = NULL;
m_pDBConn->Close();
}
/*pobiernaie instancji klasy
równocześnie tworzony jest obiekt klasy CDatabase
*/
dbMySQLOperation* dbMySQLOperation::getInstance()
{
if(dbMySQLOperationInstance==NULL)
{
dbMySQLOperationInstance = new dbMySQLOperation();
m_pDBConn = new CDatabase();
}
return dbMySQLOperationInstance;
}
/*
tworzenie stringu do połączenia z bazą danych
*/
BOOL dbMySQLOperation::setConnection(CString strServer,CString strDataBase, CString strUser,CString strPass, CString strCharset)
{
CString strConn;
strConn.Format(_T("Driver={MySQL ODBC 3.51 Driver};Server=%s;charset=%s;Database=%s;User=%s; Password=%s;Option=3;"),(LPCTSTR)strServer,(LPCTSTR)strCharset,(LPCTSTR)strDataBase,(LPCTSTR)strUser,(LPCTSTR)strPass);
return makeConection(strConn);
}
/*
tworzenie stringu do połączenia z bazą danych dla OpenEx i dla noOdbcDialog
*/
BOOL dbMySQLOperation::setConnectionNoODBCDlg(CString strServer, CString strUser, CString strPass, CString strDB)
{
CString strConn;
strConn.Format(_T("DRIVER={MySQL ODBC 3.51 Driver};Server=%s;Uid=%s;Pwd=%s;Database=%s;Port=3306"),(LPCTSTR)strServer,(LPCTSTR)strUser,(LPCTSTR)strPass,(LPCTSTR)strDB);
return makeConection(strConn,TRUE);
}
/*
faktyczne połączenie z bazą danych
inicjalizacja obiektu CRecordset
*/
BOOL dbMySQLOperation::makeConection(CString strConnectionString, BOOL bNoODBCDlg) const
{
BOOL bRet=FALSE;
//jeśłi połączenie jest otwarte to nic nie rób
if(checkDBOpen())
return bRet;
if(bNoODBCDlg)
bRet=m_pDBConn->OpenEx(strConnectionString,CDatabase::noOdbcDialog);
else
bRet=m_pDBConn->Open(_T("ODBC"), FALSE, FALSE,strConnectionString);
if(bRet)
m_pRecorset = new CRecordset(m_pDBConn );
return bRet;
}
/*
metoda do pobierania rekordów
argumenty - ZAPYTANIE, TYP OTWRACIA , OPCJA OTWARCIA
*/
CRecordset* dbMySQLOperation::selectRecord(CString sql,UINT uiOpenType,UINT uiOpenOption)
{
ASSERT(uiOpenType<4);
ASSERT(m_pRecorset!=NULL);
checkRecordsetOpen();
try
{
m_pRecorset->Open(uiOpenType,sql,uiOpenOption);
m_strErrorInfo="";
m_uiErrorCode=0;
}
catch(CDBException* e)
{
m_strErrorInfo = e->m_strError;
m_uiErrorCode=e->m_nRetCode;
}
//zwraca wskaźnik do recordset
return m_pRecorset;
}
/*metoda do wykonywania zapyań INSERT UPDATE DELETE
*/
UINT dbMySQLOperation::insertUpdateDelete(CString sqlQuery, CString strTable,BOOL iInsert)
{
ASSERT(m_pRecorset!=NULL);
UINT uiRetVal=0;
checkRecordsetOpen();
try
{ sqlQuery.Trim();
executeQuery(sqlQuery);
//jeśli insert to pobierz LAST INSERT ID
if(iInsert)
{
CDBVariant varDB;
CString strLastID;
strLastID.Format(_T("SELECT LAST_INSERT_ID() AS ID FROM %s LIMIT 1"),strTable);
getScalarValue(strLastID,varDB,SQL_C_SLONG);
uiRetVal = varDB.m_iVal;
}
/*
tu chciałbym aby było
ELSE
{
ZWRÓC ILOŚC ZMODYFIKOWANYCH REKORDÓW
}
*/
m_strErrorInfo = "";
m_uiErrorCode=0;
}
catch(CDBException* e)
{
m_strErrorInfo = e->m_strError;
m_uiErrorCode=e->m_nRetCode;
}
return uiRetVal;
}
/*wykonaj zapytanie za pomocą m_pDBConn->ExecuteSQL*/
void dbMySQLOperation::executeQuery(CString sqlQuery)
{
ASSERT(m_pDBConn!=NULL);
try
{
m_pDBConn->ExecuteSQL((LPCTSTR)sqlQuery);
m_strErrorInfo="";
m_uiErrorCode=0;
}
catch(CDBException* e)
{
m_strErrorInfo = e->m_strError;
m_uiErrorCode=e->m_nRetCode;
}
}
//pobierz KOD BŁEDU
UINT dbMySQLOperation::getErrorCode() const
{
return m_uiErrorCode;
}
//pobierz TEKST BŁEDU
CString dbMySQLOperation::getErrorInfo() const
{
return m_strErrorInfo;
}
//rozpocznij transakcję
BOOL dbMySQLOperation::beginTrans() const
{
/*jeśli transkacje jest dostępna sprój rozpocząć*/
if(transEnabled())
return m_pDBConn->BeginTrans();
return FALSE;
}
/*
zakończenie transakcji - jeśli warunek to TRUE zrób COMMIT jeśli nie zrób ROLLBACK
*/
BOOL dbMySQLOperation::endTrans(BOOL bCondition) const
{
if(bCondition)
return m_pDBConn->CommitTrans();
else
return m_pDBConn->Rollback();
}
/*
sprawdź czy kontynuwać TRANSAKCJĘ
true oznacza, że już wystąpiło coś co przerwało transakcję
*/
BOOL dbMySQLOperation::checkRollback(BOOL bCondition) const
{
if(!bCondition)
return m_pDBConn->Rollback();
return FALSE;
}
//sprawdza czy można robić transakcję
BOOL dbMySQLOperation::transEnabled() const
{
return m_pDBConn->CanTransact();
}
//sprawdza czy połączenie z bazą jest otwarte
BOOL dbMySQLOperation::checkDBOpen() const
{
return m_pDBConn->IsOpen();
}
//sprawdza pobiera pojedyńczą wartość z zapytania
/*
wartość tą można uszczegółowić podając typ pola do jakiego ma być przekierowane, wartości to np
SQL_C_CHAR, SQL_C_SLONG itd
czy jest sens rozszerzyć o możliwość podania numeru pola, którego ma to dotyczyć?
*/
void dbMySQLOperation::getScalarValue(CString strQuery,CDBVariant &varDB,UINT uiFieldtype)
{
checkRecordsetOpen();
m_pRecorset->Open(m_pRecorset->snapshot, strQuery, m_pRecorset->readOnly);
if( !m_pRecorset->IsBOF() && !m_pRecorset->IsEOF() )
{
UINT uiFieldPos = 0;
m_pRecorset->GetFieldValue(uiFieldPos,varDB,uiFieldtype);
}
}
//sprawdza czy recorset jest otwarty i zamyka go jeśli jest otwarty
/*
chodzi o to, aby na zewnątrz klasy tego nie robić
ktoś tam w dialogu pobiera wartości uzupełnia Listę czy Combo
a potem coś dodaje, lub pobiera coś nowego
i przed kolejnym otworzeniem recorseta jest sprawdzane czy ten nie jest otwarty i następuje zamkniecie go
*/
void dbMySQLOperation::checkRecordsetOpen()
{
if(m_pRecorset->IsOpen())
m_pRecorset->Close();
}