考勤系统(二) MFC ADO连接MySQL数据库

  1. 引入ADO库定义文件
  2. 声明AdoMysqlHelper辅助类
  3. 实现AdoMysqlHelper辅助类
  4. 小结一下.
      我的本本是win7-64位的,MySQL是5.6.21的,需要安装MySQL驱动的可以从这里下载安装,里面的.pdf文档有本教程需要的驱动环境的配置教程点击获取访问密码 a89b(从这里也可以获取VC++6.0安装包以及32位的MySQL数据库安装包)。



  上面是整个项目的类截图,以及数据库辅助类的方法截图。本章主要介绍如何构建自己的数据库辅助类(类名可以任意)。在按照之前的教程创建单文档项目后,会自动生成入口函数所在的….App类以及stdafx.h头文件(下面会讲到)。这一步只是让你对项目框架以及我们需要修改或者创建的文件在哪里有个大概的了解。接下来,我们开始动手连接操作MySQL数据库。

第一步,引入ADO库定义文件。

1
2
// 导入ado动态链接库 并将EOF重命名为adoEOF, BOF重命名为adoBOF防止命名冲突
#import "c:/program files/common files/system/ado/msado15.dll" no_namespace rename ("EOF","adoEOF") rename("BOF","adoBOF")`

注意: import ado动态链接库时,为了防止命名冲突,将动态库中的EOF重命名为adoEOF, BOF重命名为adoBOF
  安装完MySQL驱动之后,在C盘的c:/program files/common files/system/ado/下会有ado所需库文件(可能不同系统略有差异,但大致都在类似路径下,按照相似路径可以大概找到msado15.dll所在路径),通过import将其导入我们的MFC程序。为了方便,我们直接在stdafx.h文件中添加上述代码实现导入。

第二步,声明AdoMysqlHelper辅助类。

  VC++6.0 ->File(文件) ->New(新建) ->C/C++ Header File来创建AdoMysqlHelper.h头文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* 通过ADO操作MySQL数据库辅助类 */
//
#if !defined(ADO_MYSQL_HELPER_H)
#define ADO_MYSQL_HELPER_H

#include "stdafx.h"

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

struct Record{
CString UID;
CString name;
};
typedef struct records{
CString UID;
CString name;
struct records* next;
} Records;
class AdoMysqlHelper{
public:
//连接数据库
_ConnectionPtr mySQL_connect();

//关闭数据库
void mySQL_close(_ConnectionPtr& m_pConnection);

//对数据库进行添加操作
void mySQL_add(struct Record record);

//对数据库进行删除操作
void mySQL_delete(CString uid);

//对数据库进行更新操作
void mySQL_update(CString uid, struct Record record);

//对数据库进行查询操作
struct Record mySQL_query(CString cond);

//获取数据库中所有记录
Records* mySQL_fetchAll();
};

#endif // !defined(ADO_MYSQL_HELPER_H)

  主要功能包括打开、关闭数据库;本项目使用到的数据库的增删改查。下面在实现AdoMysqlHelper辅助类中逐一介绍。

第三步, 实现AdoMysqlHelper辅助类。

  VC++6.0 ->File(文件) ->New(新建) ->C++ Source File来创建AdoMysqlHelper.cpp源文件,实现上述函数,内容会在下面逐一描述。
1. 创建连接与关闭连接。
  初始化COM组件:在使用ADO之前,需要初始化COM组件,这里介绍实现过程中使用的两种可行的方法。第一种是直接在InitInstance()函数中加入下面代码,通过AfxOleInit()函数进行初始化。

1
2
3
4
5
// 初始化COM运行环境
if(!AfxOleInit()){
AfxMessageBox("初始化COM控件失败");
return FALSE;
}

  另外一种就是每次在打开数据库连接的前调用CoInitialize函数,在关闭连接之后调用。CoUninitialize函数。

1
2
  CoInitialize(NULL); // 打开数据之前
​ CoUninitialize(); // 关闭数据库之后

  这里推荐第一种,只需执行初始化,MFC会自动释放,比较方便,而且也不需要像第二种一样每次连接就调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_ConnectionPtr AdoMysqlHelper::mySQL_connect(){
// 打开数据库,获取连接
_ConnectionPtr m_pConnection;
// CoInitialize(NULL); // 使用第二种初始化COM组件
m_pConnection.CreateInstance(__uuidof(Connection)); // 实例化智能指针
m_pConnection->ConnectionTimeout = 5; //设置连接超时时间
try {
//打开数据库连接
HRESULT hr = m_pConnection->Open("DSN=MySqlODBC;server=localhost;database=数据库名称","用户名","密码",adModeUnknown);
} catch(_com_error &e){
AfxMessageBox(e.Description());
return NULL;
}
return m_pConnection;
}

  mySQL_connect函数用于获取_ConnectionPtr智能指针,m_pConnection->Open函数的第一个参数为连接字串, DSN/database的值根据ODBC数据源中的设置决定,server一般为localhost。第二个参数为数据库登陆用户名,第三个参数为登陆密码,第四个参数指定Connection对象对数据库的更新许可权,adModeUnknown缺省,当前的许可权未设置,adModeRead只读,adModeWrite只写,adModeReadWrite可以读写。

1
2
3
4
5
6
7
8
void AdoMysqlHelper::mySQL_close(_ConnectionPtr& m_pConnection){
// 关闭一个库连接
if(m_pConnection->State){
m_pConnection->Close();
}
m_pConnection= NULL;
//CoUninitialize(); // 取消com初始化,线程结束
}

  关闭数据库连接则要简单地多,通过State判断连接状态,假如连接存在,则将其Close同时将连接置空。最后,如果是通过上述第二种方法进行COM的初始化的,关闭连接之后需要调用CoUninitialize取消COM初始化(下图是本教程使用的users表的结构)
  

2. 查询记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct Record AdoMysqlHelper::mySQL_query(CString cond){
// 首先需要获取连接
_ConnectionPtr m_pConnection;
m_pConnection = this->mySQL_connect();
struct Record record;

// 打开数据表
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance(__uuidof(Recordset)); // 创建一个实例的智能指针
_variant_t sql = "SELECT * FROM users where " + cond;

try{
m_pRecordset->Open(sql, // Source
_variant_t((IDispatch*)m_pConnection, true),
adOpenStatic, // 游标类型
adLockReadOnly, // 表示数据库的锁定类型
adCmdText); // Source的类型
}catch(_com_error e){
// Open函数需要放在try中进行异常处理
}

try
{
if(!m_pRecordset->adoEOF){
m_pRecordset->MoveFirst();
// 通过GetCollect获取数据集中数据的方法
//_variant_t UID = m_pRecordset->GetCollect("UID");
//_variant_t name = m_pRecordset->GetCollect("name");

// 通过GetValue获取数据集中数据的方法
_variant_t UID = m_pRecordset->Fields->GetItem("UID")->GetValue();
_variant_t name = m_pRecordset->Fields->GetItem("name")->GetValue();

if(UID.vt != VT_NULL){
record.UID = (LPCSTR)_bstr_t(UID);
}
if(name.vt != VT_NULL){
record.name = (LPCSTR)_bstr_t(name);
}
}
else{
record.UID = "";
record.name = "";
}
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}
this->mySQL_close(m_pConnection);

return record;
}

  cond为类似”UID=xxx”的查询条件字符串,可用于根据任一字段查询数据表中的数据。
  首先需要创建一个数据集的智能指针m_pRecordset,通过该智能指针的Open方法打开数据表,第一个参数为数据源,可以是sql语句或者表名,最后一个参数与此对应,如果Source是SQL语句,则是adCmdText,如果Source是表名,则是adCmdTable。第二个参数关联的数据库连接,通过connect函数获取的连接智能指针。第三个参数是游标类型,adOpenForwardOnly静态只能向前滚(默认值)打开仅向前类型游标;adOpenStatic打开静态类型游标(双向);adOpenDynamic动态类型,可以看到所有的操作;adOpenKeyset动态游标,但是无法看到记录被删除的操作,只能看到更新的。对此不太熟悉的,建议使用这里推荐的adOpenStatic。第四个参数是记录锁定类型。adLockReadOnly只读;adLockPessimistic保守式锁定(逐个)悲观,修改即加锁,较占用资源;adLockOptimistic开放式锁(逐个)乐观,仅在Update时才加锁;adLockBatchOptimistic批量处理。这里建议在查询操作是选择adLockReadOnly只读,在增删改操作时选择adLockOptimistic开放式锁。
注意: 打开操作操作需要放在try-catch中进行异常处理。
  获取数据集之后,我们需要通过一些属性来判断数据集的情况。可以通过m_pRecordset->adoBOF判断游标是否在开始的位置,如是,则返回true;通过m_pRecordset->adoEOF判断游标是否在最后一条记录的位置;
注意:此处的adoEOF必须与第一步导入ADO动态库中rename (“EOF”,”adoEOF”)重命名动态库中的EOF为adoEOF保持一致。
  此处的adoEOF、adoBOF为上述我们import库文件是通过rename修改的m_pRecordset智能指针的属性名。或者通过LONG nCount = m_pRecordset->GetRecordCount();获取记录总数,不过GetRecordCount返回的是数据表曾经移动到的最大记录号,返回值可能会一直为-1。建议使用m_pRecordset->adoEOF。
  为了获取数据,有时需要移动游标进行遍历:m_pRecordset->MoveFirst()移动到第一条;m_pRecordset->MoveLast()移动到最后一条;m_pRecordset->MovePrevious()移动到前一条;m_pRecordset->MoveNext()移动到下一条;如下面的fetchAll函数通过下面方式遍历数据集中的所有数据:

1
2
3
4
5
m_pRecordset->MoveFirst();
while(!m_pRecordset->adoEOF){
....;
m_pRecordset->MoveNext();
}

接下来介绍如何通过游标从数据集中获取各字段的数据。
  通过m_pRecordset->GetCollect(“字段名”)的方法;
  通过m_pRecordset->Fields->GetItem(“字段名”)->GetValue()的方法。
这两个函数的返回值类型都是_variant_t,通过其”.vt”判断字段值是否为空,不为空则通过(LPCSTR)_bstr_t(“_variant_t字段值”)转换为字符串。
下面的fetchAll函数通过控制游标,获取users表中的所有数据,通过链表的方式存放起来并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Records* AdoMysqlHelper::mySQL_fetchAll(){
Records* records = NULL;

_ConnectionPtr m_pConnection;
m_pConnection = this->mySQL_connect();

_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance(__uuidof(Recordset));
_variant_t sql = "SELECT * FROM users order by UID DESC"; // 排序方便查看

m_pRecordset->Open( sql,
_variant_t((IDispatch*)m_pConnection,true),
adOpenStatic,
adLockReadOnly,
adCmdText);

try
{
if(!m_pRecordset->adoEOF){
records = new Records; // 使用malloc报错...
m_pRecordset->MoveFirst();
_variant_t UID = m_pRecordset->GetCollect("UID");
_variant_t name = m_pRecordset->GetCollect("name");
if(UID.vt != VT_NULL){
records->UID = (LPCSTR)_bstr_t(UID);
}
if(name.vt != VT_NULL){
records->name = (LPCSTR)_bstr_t(name);
}
records->next = NULL;

m_pRecordset->MoveNext();

while(!m_pRecordset->adoEOF){
Records* tmp = new Records;
_variant_t UID = m_pRecordset->GetCollect("UID");
_variant_t name = m_pRecordset->GetCollect("name");
if(UID.vt != VT_NULL){
tmp->UID = (LPCSTR)_bstr_t(UID);
}
if(name.vt != VT_NULL){
tmp->name = (LPCSTR)_bstr_t(name);
}
tmp->next = records;
records = tmp;
m_pRecordset->MoveNext();
}
}
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}

this->mySQL_close(m_pConnection);
return records;
}

注意: 在所有操作之后,记得调用close函数关闭数据库!
3. 增加记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void AdoMysqlHelper::mySQL_add(struct Record record){
_ConnectionPtr m_pConnection;
m_pConnection = this->mySQL_connect();

_RecordsetPtr m_pRecordset;
// 创建一个实例的智能指针
m_pRecordset.CreateInstance(__uuidof(Recordset));
// 打开数据表
/* 如果Source是SQL语句,则是adCmdText,如果Source是表名,则是adCmdTable */
m_pRecordset->Open("SELECT * FROM users", // Source
_variant_t((IDispatch*)m_pConnection, true),
adOpenStatic, // 游标类型
adLockOptimistic, // 表示数据库的锁定类型
adCmdText); // Source的类型
// 判断能否进行数据插入
/*
if(!(m_pRecordset->Supports(adAddNew))){
return;
}*/
try
{
// 插入新的一行
m_pRecordset->AddNew();
// 写入各字段值
m_pRecordset->PutCollect("UID", _variant_t(record.UID));
m_pRecordset->PutCollect("name", _variant_t(record.name));
// 插入之后必须调用Update进行修改
m_pRecordset->Update();
AfxMessageBox("插入成功!");
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}
this->mySQL_close(m_pConnection);
}

  插入记录按照之前讲述的方式打开数据库,获取数据集。之后可以通过调用m_pRecordset->Supports(adAddNew)来判断能否进行插入操作,这里还是推荐直接放在try-catch里面进行操作就行了。类似的通过Supports也可以检测能否进行修改(adUpdate)、删除(adDelete)等。
  插入记录的流程是,通过m_pRecordset->AddNew()插入新一行,然后通过m_pRecordset->PutCollect(“字段名”, _variant_t(‘字段值’))插入各个字段的值,最后必须调用m_pRecordset->Update()才能生效!
4. 修改记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void AdoMysqlHelper::mySQL_update(CString uid, struct Record record){
_ConnectionPtr m_pConnection;
m_pConnection = this->mySQL_connect();

_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance(__uuidof(Recordset));
_variant_t sql = "SELECT * FROM users where UID=\'" + uid + "\'";
m_pRecordset->Open( sql,
_variant_t((IDispatch*)m_pConnection,true),
adOpenStatic,
adLockOptimistic,
adCmdText);
try
{
m_pRecordset->MoveFirst();
//m_pRecordset->PutCollect("name", _variant_t(record.name));
// 另外一种
m_pRecordset->Fields->GetItem("name")->Value = _variant_t(record.name);

m_pRecordset->Update();
AfxMessageBox("修改成功!");
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}
this->mySQL_close(m_pConnection);
}

上述的修改函数,通过指定uid找到对应的数据集,然后可以通过下面两种不同方式进行字段值的修改:
  m_pRecordset->PutCollect(“修改字段名”, _variant_t(修改后的字段值));
  m_pRecordset->Fields->GetItem(“修改字段名”)->Value = _variant_t(修改后的字段值);
最后,同样的需要调用Update让更新生效。
5. 删除记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void AdoMysqlHelper::mySQL_delete(CString uid){
_ConnectionPtr m_pConnection;
m_pConnection = this->mySQL_connect();

/* 创建_CommandPtr智能指针实例 */
_CommandPtr m_pCommand;
m_pCommand.CreateInstance("ADODB.Command");

// 设置命令的相关参数
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND; //定义为无参数
m_pCommand->ActiveConnection = m_pConnection; //非常关键的一句,将建立的连接赋值给它
m_pCommand->CommandText = "delete FROM users where UID=\'"+ ((_bstr_t)uid) +"\'"; //命令字串
try{
m_pCommand->Execute(&vNULL, &vNULL, adCmdText); //执行删除命令
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}
this->mySQL_close(m_pConnection);
}

  上述方式通过_CommandPtr命令智能指针执行删除指定uid记录的SQL语句实现删除功能。首先需要获取连接智能指针以及命令智能指针;接着,对命令智能指针进行相关参数的设置,包括m_pCommand->ActiveConnection将指定连接与命令相关联,然后通过m_pCommand->CommandText设置我们要执行的SQL语句,最后通过m_pCommand->Execute执行SQL语句,完成删除功能。
  也可以通过下面的方法,直接调用m_pRecordset->Delete方法,按照上述的方式通过uid进行查询获取数据集,然后删除当前行(adAffectCurrent)或者删除整个查询得到的数据集(adAffectAll)进行数据的删除,但是这种方式的问题是对于按照中文进行查询获取的数据集,似乎没办法进行删除。所以,这里推荐使用上面的第一种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_RecordsetPtr m_pRecordset;
// 创建一个实例的智能指针
m_pRecordset.CreateInstance(__uuidof(Recordset));
// 打开数据表
m_pRecordset->Open(_variant_t("SELECT * FROM users where UID=\'"+uid+"\'"), // Source
_variant_t((IDispatch*)m_pConnection, true),
adOpenStatic, // 游标类型
adLockOptimistic, // 表示数据库的锁定类型
adCmdText); // Source的类型
// 判断能否删除?
if(!(m_pRecordset->Supports(adDelete))){
return;
}
try
{
//m_pRecordset->Delete(adAffectAll);
m_pRecordset->Delete(adAffectCurrent);
AfxMessageBox("删除成功!");
}
catch(_com_error *e)
{
AfxMessageBox(e->ErrorMessage());
}

最后,小结一下。

  到这里,我们基本上讲完我们MFC项目中如何通过ADO访问MySQL数据库,进而实现我们需要的功能。我们将对数据库的所有操作封装在一个Helper数据库辅助类中,实现如何获取数据库连接以及如何将连接关闭。以及如何实现对数据库的增、删、改、查操作。其中也涉及了如何引入ADO动态链接库,以及与之相关的连接(_ConnectionPtr)、数据集(_RecordsetPtr)、命令(_CommandPtr)三种智能指针的一些操作。按照上述想法,应该可以成功实现对数据库的基本操作,赶紧试一下吧。

  下一讲,将会介绍一下这次项目中使用的几种UI功能实现,包括弹出自定义对话款、满足需求的信息框、列表框等。

文章目录
  1. 1. 第一步,引入ADO库定义文件。
  2. 2. 第二步,声明AdoMysqlHelper辅助类。
  3. 3. 第三步, 实现AdoMysqlHelper辅助类。
  4. 4. 最后,小结一下。