Oracle 19c入门学习教程,从入门到精通,VC++ + Oracle 实现汽配管理系统(21)

VC++ + Oracle 实现汽配管理系统

内容整理的 完整技术指南,涵盖开发环境搭建、VC++ 与 Oracle 集成、核心语法知识点、自定义控件设计、数据库操作类封装及综合性案例。所有代码均附详细注释,适用于 Visual C++ 6.0 / Visual Studio(建议 VS2019/2022)+ Oracle 19c 开发场景。


一、开发环境安装与配置

1. 安装 Visual Studio(推荐 VS2019 或 VS2022)

2. 安装 Oracle 数据库(Oracle 19c XE)

  • 下载地址:Oracle Database XE

  • 安装后创建用户:

    sql 复制代码
    CREATE USER auto_parts IDENTIFIED BY ap_password;
    GRANT CONNECT, RESOURCE, DBA TO auto_parts;

3. 安装 Oracle Instant Client(用于 ODBC/OLE DB 连接)

  • 下载地址:Oracle Instant Client

    • 下载 instantclient-basic-windows.x64.zip
    • 解压到 C:\oracle\instantclient_19_20
  • 配置系统环境变量:

    bash 复制代码
    PATH += C:\oracle\instantclient_19_20
    TNS_ADMIN = C:\oracle\instantclient_19_20

4. 配置 ODBC 数据源(DSN)

  1. 打开 控制面板 → 管理工具 → ODBC 数据源(64位)
  2. 在"系统 DSN"中点击"添加"
  3. 选择 Oracle in instantclient_19_20
  4. 填写:
    • Data Source Name: AutoPartsDB
    • TNS Service Name: XE(或你的服务名)
    • User ID: auto_parts
  5. 测试连接成功即可。

✅ 此 DSN 将在 VC++ 中通过 ADO 使用。


二、核心语法知识点与代码示例

知识点 1:VC++ 中使用 ADO 连接 Oracle(RxADO 类设计)

背景

ADO(ActiveX Data Objects)是 Windows 平台访问数据库的标准 COM 接口。通过 #import 引入 msado15.dll。

步骤 1:引入 ADO 库(在 stdafx.h 中)
cpp 复制代码
// stdafx.h
#pragma once

// 其他 MFC 头文件...

// 引入 ADO 库(智能指针)
#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
    no_namespace rename("EOF", "EndOfFile")

⚠️ 路径可能因系统而异,可搜索 msado15.dll

步骤 2:设计通用数据库操作类 CRxADO
cpp 复制代码
// RxADO.h
#pragma once
#include <atlbase.h> // 用于 CComBSTR

class CRxADO {
private:
    _ConnectionPtr m_pConnection;  // 连接对象
    _RecordsetPtr m_pRecordset;    // 结果集对象

public:
    CRxADO();
    ~CRxADO();

    // 初始化连接
    BOOL Connect(LPCTSTR lpszDSN, LPCTSTR lpszUser, LPCTSTR lpszPwd);
    
    // 执行非查询 SQL(INSERT/UPDATE/DELETE)
    BOOL ExecuteSQL(LPCTSTR lpszSQL);

    // 执行查询,返回记录集
    _RecordsetPtr GetRecordset(LPCTSTR lpszSQL);

    // 关闭连接
    void Close();

    // 判断记录集是否为空
    BOOL IsRecordsetEmpty(_RecordsetPtr pRs);
};
cpp 复制代码
// RxADO.cpp
#include "stdafx.h"
#include "RxADO.h"

CRxADO::CRxADO() {
    CoInitialize(NULL); // 初始化 COM
    m_pConnection = NULL;
    m_pRecordset = NULL;
}

CRxADO::~CRxADO() {
    Close();
    CoUninitialize(); // 释放 COM
}

BOOL CRxADO::Connect(LPCTSTR lpszDSN, LPCTSTR lpszUser, LPCTSTR lpszPwd) {
    try {
        m_pConnection.CreateInstance(__uuidof(Connection));
        CString strConn;
        strConn.Format(_T("Provider=OraOLEDB.Oracle;Data Source=%s;User ID=%s;Password=%s;"),
                       lpszDSN, lpszUser, lpszPwd);
        m_pConnection->Open((LPCTSTR)strConn, "", "", adConnectUnspecified);
        return TRUE;
    }
    catch (_com_error &e) {
        AfxMessageBox(e.ErrorMessage());
        return FALSE;
    }
}

_RecordsetPtr CRxADO::GetRecordset(LPCTSTR lpszSQL) {
    try {
        m_pRecordset.CreateInstance(__uuidof(Recordset));
        m_pRecordset->Open(lpszSQL, m_pConnection.GetInterfacePtr(),
                          adOpenStatic, adLockReadOnly, adCmdText);
        return m_pRecordset;
    }
    catch (_com_error &e) {
        AfxMessageBox(e.ErrorMessage());
        return NULL;
    }
}

BOOL CRxADO::ExecuteSQL(LPCTSTR lpszSQL) {
    try {
        m_pConnection->Execute(lpszSQL, NULL, adExecuteNoRecords);
        return TRUE;
    }
    catch (_com_error &e) {
        AfxMessageBox(e.ErrorMessage());
        return FALSE;
    }
}

void CRxADO::Close() {
    if (m_pRecordset && m_pRecordset->State == adStateOpen)
        m_pRecordset->Close();
    if (m_pConnection && m_pConnection->State == adStateOpen)
        m_pConnection->Close();
}

BOOL CRxADO::IsRecordsetEmpty(_RecordsetPtr pRs) {
    return (pRs == NULL || pRs->EndOfFile);
}

✅ 使用 OraOLEDB.Oracle 提供程序(需安装 Oracle Client)。


知识点 2:自定义按钮类 CBaseButton

目的

实现圆角、背景色、鼠标悬停效果的按钮。

cpp 复制代码
// BaseButton.h
#pragma once

class CBaseButton : public CButton {
public:
    CBaseButton();
    virtual ~CBaseButton();

protected:
    DECLARE_MESSAGE_MAP()

    afx_msg void OnPaint();
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnMouseLeave();

private:
    BOOL m_bHover; // 是否鼠标悬停
    CBrush m_brNormal, m_brHover;
};
cpp 复制代码
// BaseButton.cpp
#include "stdafx.h"
#include "BaseButton.h"

CBaseButton::CBaseButton() : m_bHover(FALSE) {
    m_brNormal.CreateSolidBrush(RGB(240, 240, 240)); // 默认灰色
    m_brHover.CreateSolidBrush(RGB(255, 220, 180));  // 悬停橙色
}

CBaseButton::~CBaseButton() {}

BEGIN_MESSAGE_MAP(CBaseButton, CButton)
    ON_WM_PAINT()
    ON_WM_ERASEBKGND()
    ON_WM_MOUSEMOVE()
    ON_WM_MOUSELEAVE()
END_MESSAGE_MAP()

void CBaseButton::OnPaint() {
    CPaintDC dc(this);
    CRect rect;
    GetClientRect(&rect);

    // 绘制圆角矩形背景
    CBrush* pOldBrush = dc.SelectObject(m_bHover ? &m_brHover : &m_brNormal);
    dc.RoundRect(rect, CPoint(10, 10)); // 圆角半径 10
    dc.SelectObject(pOldBrush);

    // 绘制文字
    CString strText;
    GetWindowText(strText);
    dc.SetBkMode(TRANSPARENT);
    dc.DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

BOOL CBaseButton::OnEraseBkgnd(CDC* pDC) {
    return TRUE; // 防止闪烁
}

void CBaseButton::OnMouseMove(UINT nFlags, CPoint point) {
    if (!m_bHover) {
        m_bHover = TRUE;
        Invalidate(); // 重绘
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.dwFlags = TME_LEAVE;
        tme.hwndTrack = m_hWnd;
        TrackMouseEvent(&tme);
    }
    CButton::OnMouseMove(nFlags, point);
}

void CBaseButton::OnMouseLeave() {
    m_bHover = FALSE;
    Invalidate();
    CButton::OnMouseLeave();
}

✅ 在对话框资源中,将普通 Button 控件的类型改为 "Owner Draw",并绑定到 CBaseButton


知识点 3:扩展组合框 CBaseComboBox

功能:支持自动完成、下拉项高亮
cpp 复制代码
// BaseComboBox.h
class CBaseComboBox : public CComboBox {
    DECLARE_DYNAMIC(CBaseComboBox)

public:
    CBaseComboBox();
    virtual ~CBaseComboBox();

protected:
    DECLARE_MESSAGE_MAP()
    afx_msg void OnCbnEditupdate();
};
cpp 复制代码
// BaseComboBox.cpp
IMPLEMENT_DYNAMIC(CBaseComboBox, CComboBox)

CBaseComboBox::CBaseComboBox() {}

CBaseComboBox::~CBaseComboBox() {}

BEGIN_MESSAGE_MAP(CBaseComboBox, CComboBox)
    ON_CONTROL_REFLECT(CBN_EDITUPDATE, &CBaseComboBox::OnCbnEditupdate)
END_MESSAGE_MAP()

void CBaseComboBox::OnCbnEditupdate() {
    // 可在此实现输入时自动筛选(简化版)
    CString strInput;
    GetWindowText(strInput);
    // 实际项目中可遍历下拉项匹配前缀
}

更高级功能可结合 SetDroppedWidth()SetItemHeight() 等 API。


三、模块级实现示例

示例 1:系统登录模块

cpp 复制代码
// LoginDlg.cpp
void CLoginDlg::OnBnClickedOk() {
    UpdateData(TRUE); // 从控件获取数据

    if (m_strUser.IsEmpty() || m_strPwd.IsEmpty()) {
        AfxMessageBox(_T("用户名或密码不能为空!"));
        return;
    }

    CRxADO db;
    if (!db.Connect(_T("AutoPartsDB"), m_strUser, m_strPwd)) {
        AfxMessageBox(_T("数据库连接失败!"));
        return;
    }

    // 查询用户是否存在(假设用户表 USERS)
    CString strSQL;
    strSQL.Format(_T("SELECT COUNT(*) FROM USERS WHERE USERNAME='%s' AND PASSWORD='%s'"),
                  m_strUser, m_strPwd);

    _RecordsetPtr rs = db.GetRecordset(strSQL);
    if (!db.IsRecordsetEmpty(rs)) {
        long count = rs->Fields->GetItem(_variant_t("COUNT(*)"))->Value.lVal;
        if (count > 0) {
            CDialogEx::OnOK(); // 登录成功
            return;
        }
    }
    AfxMessageBox(_T("用户名或密码错误!"));
}

⚠️ 实际项目应使用参数化查询防 SQL 注入(ADO 支持 _CommandPtr + 参数)。


示例 2:商品信息查询(基础信息模块)

cpp 复制代码
// GoodsQueryDlg.cpp
void CGoodsQueryDlg::LoadGoodsList() {
    m_listCtrl.DeleteAllItems(); // 清空列表

    CRxADO db;
    if (!db.Connect(_T("AutoPartsDB"), _T("auto_parts"), _T("ap_password")))
        return;

    CString strSQL = _T("SELECT GOODS_ID, NAME, BRAND, PRICE FROM GOODS_INFO ORDER BY GOODS_ID");
    _RecordsetPtr rs = db.GetRecordset(strSQL);

    if (db.IsRecordsetEmpty(rs)) return;

    int nIndex = 0;
    while (!rs->EndOfFile) {
        CString id = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("GOODS_ID"))->Value;
        CString name = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("NAME"))->Value;
        CString brand = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("BRAND"))->Value;
        CString price = (LPCTSTR)(_bstr_t)rs->Fields->GetItem(_T("PRICE"))->Value;

        m_listCtrl.InsertItem(nIndex, id);
        m_listCtrl.SetItemText(nIndex, 1, name);
        m_listCtrl.SetItemText(nIndex, 2, brand);
        m_listCtrl.SetItemText(nIndex, 3, price);

        rs->MoveNext();
        nIndex++;
    }
}

✅ 假设使用 CListCtrl 控件,需设置报表视图(Report View)。


四、综合性案例:日常业务处理(入库 + 出库)

场景:商品入库登记

数据库表设计(简化)
sql 复制代码
-- 商品表
CREATE TABLE GOODS_INFO (
    GOODS_ID VARCHAR2(20) PRIMARY KEY,
    NAME VARCHAR2(100),
    BRAND VARCHAR2(50),
    STOCK NUMBER DEFAULT 0
);

-- 入库记录表
CREATE TABLE STOCK_IN (
    RECORD_ID NUMBER GENERATED ALWAYS AS IDENTITY,
    GOODS_ID VARCHAR2(20),
    QUANTITY NUMBER,
    OPERATOR VARCHAR2(50),
    IN_TIME DATE DEFAULT SYSDATE
);
VC++ 实现入库逻辑
cpp 复制代码
void CStockInDlg::OnBnClickedBtnSave() {
    UpdateData(TRUE);

    if (m_strGoodsId.IsEmpty() || m_nQuantity <= 0) {
        AfxMessageBox(_T("请填写完整信息!"));
        return;
    }

    CRxADO db;
    if (!db.Connect(_T("AutoPartsDB"), _T("auto_parts"), _T("ap_password"))) return;

    // 1. 更新库存
    CString sqlUpdate;
    sqlUpdate.Format(_T("UPDATE GOODS_INFO SET STOCK = STOCK + %d WHERE GOODS_ID = '%s'"),
                     m_nQuantity, m_strGoodsId);

    // 2. 插入入库记录
    CString sqlInsert;
    sqlInsert.Format(_T("INSERT INTO STOCK_IN (GOODS_ID, QUANTITY, OPERATOR) VALUES ('%s', %d, '%s')"),
                     m_strGoodsId, m_nQuantity, _T("admin"));

    // 事务处理(简化:顺序执行)
    if (db.ExecuteSQL(sqlUpdate) && db.ExecuteSQL(sqlInsert)) {
        AfxMessageBox(_T("入库成功!"));
        OnCancel(); // 关闭窗口
    } else {
        AfxMessageBox(_T("入库失败!"));
    }
}

🔒 生产环境应使用 ADO 事务

cpp 复制代码
m_pConnection->BeginTrans();
// 执行多个 SQL
m_pConnection->CommitTrans(); // 或 RollbackTrans()

五、小结:关键技术栈

技术点 说明
ADO 编程 通过 _ConnectionPtr, _RecordsetPtr 操作 Oracle
Oracle OLE DB 使用 OraOLEDB.Oracle 提供程序(需安装 Oracle Client)
自定义控件 重绘 CButton、扩展 CComboBox 提升 UI 体验
数据库类封装 CRxADO 实现连接、查询、执行统一管理
MFC 对话框交互 结合 DoDataExchange、控件变量实现数据绑定
业务逻辑分层 登录、查询、业务处理模块解耦

六、附:初始化数据库脚本(Oracle)

sql 复制代码
-- 用户表(登录用)
CREATE TABLE USERS (
    USERNAME VARCHAR2(50) PRIMARY KEY,
    PASSWORD VARCHAR2(50) NOT NULL,
    REAL_NAME VARCHAR2(100)
);

INSERT INTO USERS VALUES ('admin', '123456', '系统管理员');

-- 商品信息表
CREATE TABLE GOODS_INFO (
    GOODS_ID VARCHAR2(20) PRIMARY KEY,
    NAME VARCHAR2(100) NOT NULL,
    BRAND VARCHAR2(50),
    SPEC VARCHAR2(100),      -- 规格
    UNIT VARCHAR2(10),       -- 单位
    PRICE NUMBER(10,2),
    STOCK NUMBER DEFAULT 0
);

-- 日常业务:入库/出库
CREATE TABLE STOCK_RECORD (
    RECORD_ID NUMBER GENERATED ALWAYS AS IDENTITY,
    GOODS_ID VARCHAR2(20),
    TYPE CHAR(1),            -- 'I'=入库, 'O'=出库
    QUANTITY NUMBER,
    OPERATOR VARCHAR2(50),
    RECORD_TIME DATE DEFAULT SYSDATE
);

💡 提示

  • 若使用 Visual Studio 2022,默认字符集为 Unicode,所有字符串使用 _T("") 宏。
  • Oracle 字段名建议大写,避免大小写敏感问题。
  • 生产系统务必使用参数化查询防止 SQL 注入。

如需提供 完整工程结构主窗体菜单设计报表打印模块,可继续扩展。

相关推荐
阿昭L2 小时前
C++异常处理机制反汇编(二):32位下基本类型异常分析
c++·逆向工程
爬山算法2 小时前
Hibernate(80) 如何在数据迁移中使用Hibernate?
java·oracle·hibernate
近津薪荼2 小时前
优选算法——滑动窗口2(数组模拟哈希表)
c++·学习·算法
挖矿大亨2 小时前
C++中const修饰成员函数
开发语言·c++
星火开发设计2 小时前
using 关键字:命名空间的使用与注意事项
开发语言·c++·学习·算法·编程·知识
进击的荆棘2 小时前
C++起始之路——string
开发语言·c++·stl
●VON2 小时前
React Native for OpenHarmony:Pressable —— 构建下一代状态驱动交互的基石
学习·react native·react.js·性能优化·交互·openharmony
孞㐑¥2 小时前
算法—字符串
开发语言·c++·经验分享·笔记·算法
Hill_HUIL2 小时前
学习日志18-不同VLAN间通信(2)-单臂路由
网络·学习·智能路由器