在达梦数据库的日常开发中,存储过程、函数、触发器、包、视图这些“存储程序对象”是绕不开的核心技能,而 JDBC 和 ODBC 则是应用程序连接数据库的两种主要方式。本文把这两块内容整理成一份完整的开发实战笔记,包含每个对象的概念、使用场景和动手实验,以及 JDBC/ODBC 的连接配置与代码验证。
存储过程(Procedure)
存储过程是一组预先编译好、存放在数据库中的 SQL 语句集合。它通过 CALL 或 EXECUTE 显式调用,可以有输入参数、输出参数,但不强制要求返回值。
使用场景:适合封装复杂的业务逻辑,比如批量数据清洗、定期报表生成、多表关联写入等。一次编写、多次调用,减少网络传输,提高执行效率。
函数(Function)
函数与存储过程类似,也是一段存储在数据库中的代码,但它必须返回一个值。函数可以在 SELECT 语句中直接调用,像使用内置函数一样使用。
使用场景:适合封装计算逻辑,比如根据身份证号计算年龄、根据订单金额计算折扣、格式化字符串等。凡是需要在 SQL 中直接使用计算结果的地方,都适合用函数。
触发器(Trigger)
触发器是一种特殊的存储程序,它不能手动调用,而是绑定在表或视图上,由特定的数据操作(如 INSERT、UPDATE、DELETE)自动激发。
使用场景:适合做自动化的联动操作,比如插入订单后自动扣减库存、修改数据后自动记录审计日志、删除数据前做数据校验等。它把“该做的事”绑定在数据变化上,避免遗漏。
包(Package)
包是用于打包管理相关存储过程、函数、变量、游标等对象的容器,由“包头”和“包体”两部分组成:包头声明对外接口,包体实现具体逻辑。
使用场景:适合把一组相关的功能组织在一起,比如“员工管理包”里放涨薪、调岗、查信息等过程。包能提高代码的模块化程度,也方便统一授权和管理。
视图(View)
视图是一种虚拟表,它本身不存储数据,只保存一条查询语句的定义。当你查询视图时,数据库会实时执行视图背后的那条 SELECT 语句,把结果返回给你。所以视图可以理解为“给一条复杂查询起个名字,以后就当表来用”。
使用场景:把多表关联、聚合计算的复杂 SQL 封装成视图,上层应用直接 SELECT * FROM 视图名 即可,不用每次都写一长串 JOIN。把业务上常用的统计逻辑(如“有效订单”“活跃用户”)定义成视图,所有报表和应用都从同一个视图取数,避免口径不一致。
准备测试表:
CREATE TABLE test_emp (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(30),
salary DECIMAL(10,2),
dept_no INT
);
建一张员工测试表,包含工号、姓名、薪资、部门号四个字段,后面所有存储过程都基于这张表操作。
创建无参数存储过程:
CREATE OR REPLACE PROCEDURE p_insert_emp AS
BEGIN
INSERT INTO test_emp VALUES (1, '张三', 8000.00, 10);
PRINT '插入成功';
END;
/
创建一个不带参数的存储过程,执行时固定向表中插入一条“张三”的数据,并用 PRINT 输出提示。结尾的 / 表示这段程序定义结束。
调用:用 CALL 调用这个存储过程,执行后表中会多出一条记录。
CALL p_insert_emp();
验证:
SELECT * FROM test_emp;
查询测试表,确认数据已经插入成功。
创建带 IN 参数的存储过程:创建一个带四个 IN 参数的存储过程,调用时把工号、姓名、薪资、部门号传进去,过程负责插入数据。IN 参数表示“只传入、不返回”。
CREATE OR REPLACE PROCEDURE p_insert_emp_param (
p_id IN INT,
p_name IN VARCHAR(30),
p_salary IN DECIMAL(10,2),
p_dept IN INT
) AS
BEGIN
INSERT INTO test_emp VALUES (p_id, p_name, p_salary, p_dept);
PRINT '插入成功:' || p_name;
END;
/
调用:
CALL p_insert_emp_param(2, '李四', 9500.00, 20);
验证:
SELECT * FROM test_emp;
删除调用过程:
DROP PROCEDURE p_insert_emp;
用 DROP PROCEDURE 删除不再需要的存储过程。
写一个不带参数、固定返回一个字符串的函数。
CREATE OR REPLACE FUNCTION f_hello
RETURN VARCHAR(30)
AS
BEGIN
RETURN 'hello_dm';
END;
/
创建一个无参函数,调用时固定返回 ‘hello_dm’。函数用 RETURN 声明返回类型,函数体里必须有一条 RETURN 语句把值返回。
在 SELECT 中直接调用函数,DUAL 是达梦提供的一张单行虚拟表,专门用来测试表达式或函数。
CREATE OR REPLACE FUNCTION f_get_emp_name (
p_id IN INT
) RETURN VARCHAR(30)
AS
v_name VARCHAR(30);
BEGIN
SELECT emp_name INTO v_name
FROM test_emp
WHERE emp_id = p_id;
RETURN v_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN '查无此人';
END;
/
创建一个带 IN 参数的函数,传入工号后查询员工姓名并返回。如果查不到数据,就在异常处理里返回 '查无此人',避免函数报错中断。
综合表查询:把函数用在 SELECT 列表里,对每一行都调用一次,这是函数最典型的用法
SELECT emp_id, f_get_emp_name(emp_id) AS emp_name FROM test_emp;
CREATE OR REPLACE FUNCTION f_calc_tax (
p_salary IN DECIMAL(10,2)
) RETURN DECIMAL(10,2)
AS
v_tax DECIMAL(10,2);
BEGIN
IF p_salary > 5000 THEN
v_tax := (p_salary - 5000) * 0.1;
ELSE
v_tax := 0;
END IF;
RETURN v_tax;
END;
/
SELECT emp_name, salary, f_calc_tax(salary) AS tax FROM test_emp;
结合表查询,对每个员工的薪资都算一次税,返回结果作为一列。
删除函数:
DROP FUNCTION f_get_emp_name;
log_id 用 IDENTITY 自增,后面触发器每次执行都会往这张表里插一条日志。CREATE TABLE test_emp_log (
log_id INT IDENTITY(1,1) PRIMARY KEY,
emp_id INT,
action_type VARCHAR(20),
action_time TIMESTAMP,
remark VARCHAR(100)
);
创建 INSERT 触发器,向 test_emp 表插入数据后,自动往日志表里记一条日志。
创建一个 AFTER INSERT 触发器,绑定在 test_emp 表上。FOR EACH ROW 表示每插入一行就触发一次,:NEW.emp_id 和 :NEW.emp_name 代表刚插入的那行数据里的字段值。
INSERT INTO test_emp VALUES (4, '赵六', 12000.00, 40);
验证:
创建 DELETE 触发器,目标:删除员工数据前,先把被删的数据记录到日志表。
CREATE OR REPLACE TRIGGER trg_emp_delete
BEFORE DELETE ON test_emp
FOR EACH ROW
BEGIN
INSERT INTO test_emp_log (emp_id, action_type, action_time, remark)
VALUES (:OLD.emp_id, 'DELETE', SYSDATE, '删除员工:' || :OLD.emp_name);
END;
/
创建一个 BEFORE DELETE 触发器,在删除操作执行**之前(BEFORE)**触发。因为删除后数据就没了,所以要用 :OLD 来引用即将被删除的那行数据。
验证:
删除触发器
DROP TRIGGER trg_emp_insert;
注意::NEW 和 :OLD
:NEW 代表新数据,:OLD 代表旧数据
创建包头:声明一个包,里面包含前面写过的一个过程和一个函数。
CREATE OR REPLACE PACKAGE pkg_emp AS
-- 声明一个过程:根据工号查员工信息
PROCEDURE p_get_emp (
p_id IN INT,
p_name OUT VARCHAR(30),
p_salary OUT DECIMAL(10,2)
);
-- 声明一个函数:根据薪资算个税
FUNCTION f_calc_tax (
p_salary IN DECIMAL(10,2)
) RETURN DECIMAL(10,2);
END pkg_emp;
/
包头只写“目录”:声明包里有哪几个过程和函数,以及它们的参数和返回类型。不写具体实现代码。
创建包体:
CREATE OR REPLACE PACKAGE BODY pkg_emp AS
PROCEDURE p_get_emp (
p_id IN INT,
p_name OUT VARCHAR(30),
p_salary OUT DECIMAL(10,2)
) AS
BEGIN
SELECT emp_name, salary INTO p_name, p_salary
FROM test_emp
WHERE emp_id = p_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_name := '查无此人';
p_salary := 0;
END;
FUNCTION f_calc_tax (
p_salary IN DECIMAL(10,2)
) RETURN DECIMAL(10,2) AS
v_tax DECIMAL(10,2);
BEGIN
IF p_salary > 5000 THEN
v_tax := (p_salary - 5000) * 0.1;
ELSE
v_tax := 0;
END IF;
RETURN v_tax;
END;
END pkg_emp;
/
删除包时,包头和包体一起删除:
DROP PACKAGE pkg_emp;
CREATE OR REPLACE VIEW v_high_salary AS
SELECT emp_id, emp_name, salary, dept_no
FROM test_emp
WHERE salary > 8000;
创建视图 v_high_salary,它不存数据,只保存了这条 SELECT 语句。以后查询这个视图,数据库会实时执行这条查询。
调用:
SELECT * FROM v_high_salary;
创建多表关联视图
先建一张部门表:
CREATE TABLE test_dept (
dept_no INT PRIMARY KEY,
dept_name VARCHAR(30)
);
插入两条部门数据:
INSERT INTO test_dept VALUES (10, '研发部');
INSERT INTO test_dept VALUES (20, '测试部');
创建关联视图:
CREATE OR REPLACE VIEW v_emp_dept AS
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name
FROM test_emp e
JOIN test_dept d ON e.dept_no = d.dept_no;
视图把两张表关联起来,对外只暴露需要的字段。上层应用直接查视图,不用每次都写 JOIN。
调用:
SELECT * FROM v_emp_dept;
查询视图,返回员工和对应部门名称。
JDBC(Java Database Connectivity)是 Java 程序访问关系型数据库的标准接口。Java 应用通过 JDBC API 调用达梦提供的 JDBC 驱动,完成连接建立、SQL 执行和结果读取。
普通项目可通过此方式进行添加驱动,点击文件–> 项目结构–> 库,添加 DmJdbc 驱动。
测试连接实例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class test {
// 定义连接
static Connection *con* = null;
// 定义 DM JDBC 驱动串
static String *cname* = "dm.jdbc.driver.DmDriver";
// 定义 DM URL 连接串
static String *url* = "jdbc:dm://localhost:5236";
// 定义连接用户名
static String *userid* = "SYSDBA";
// 定义连接用户口令
static String *pwd* = "Dameng123";
public static void main(String[] args) {
try {
Class.*forName*(*cname*);
*con* = DriverManager.*getConnection*(*url*, *userid*, *pwd*);
*con*.setAutoCommit(true);
System.*out*.println("[SUCCESS]conn database");
} catch (Exception e) {
System.*out*.println("[FAIL]conn database:" + e.getMessage());
}
}
public void disConn(Connection con) throws SQLException {
if (con != null) {
con.close();
}
}
}
查一下 test_emp 表的数据
// 3. 执行查询
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT emp_id, emp_name, salary, dept_no FROM test_emp"
);
System.out.println("---- 查询结果 ----");
while (rs.next()) {
int id = rs.getInt("emp_id");
String name = rs.getString("emp_name");
double salary = rs.getDouble("salary");
int dept = rs.getInt("dept_no");
System.out.println(id + " | " + name + " | " + salary + " | " + dept);
}
ODBC 提供访问不同类型的数据库的途径。结构化查询语言 SQL 是一种用来访问数据库的语言。通过使用 ODBC,应用程序能够使用相同的源代码和各种各样的数据库交互。这使得开发者不需要以特殊的数据库管理系统 DBMS 为目标,或者了解不同支撑背景的数据库的详细细节,就能够开发和发布客户/服务器应用程序。
打开 ODBC 管理器:按 Win + R,输入 odbcad32 回车
**添加 DSN:**在“系统 DSN”标签页点击“添加”,选择 DM8 ODBC DRIVER
填写连接信息:在弹出的窗口里填入达梦数据库的 IP、端口(默认 5236)、用户名(SYSDBA)和密码,并测试成功:
使用Visual Studio 2022来测试ODBC:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
/* 检测返回代码是否为成功标志,当为成功标志返回 TRUE,否则返回 FALSE */
#define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO)
/* 检测返回代码是否为失败标志,当为失败标志返回 TRUE,否则返回 FALSE */
#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))
HENV henv;/* 环境句柄 */
HDBC hdbc;/* 连接句柄 */
HSTMT hsmt;/* 语句句柄 */
SQLRETURN sret; /* 返回代码 */
int main(void)
{
/* 申请一个环境句柄 */
SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
/* 设置环境句柄的 ODBC 版本 */
SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,
SQL_IS_INTEGER);
/* 申请一个连接句柄 */
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
sret = SQLConnectA(hdbc, (SQLCHAR*)"DM", SQL_NTS,
(SQLCHAR*)"SYSDBA", SQL_NTS,
(SQLCHAR*)"Dameng123", SQL_NTS);
if (RC_NOTSUCCESSFUL(sret)) {
/* 连接数据源失败! */
printf("odbc: fail to connect to server!\n");
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
exit(0);
}
printf("odbc: connect to server success!\n");
/* 断开与数据源之间的连接 */
SQLDisconnect(hdbc);
/* 释放连接句柄 */
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
/* 释放环境句柄 */
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
测试连接成功!
我们再来测试,是否能查test_emp 表:
// 5. 执行查询
ret = SQLExecDirect(hstmt,
(SQLCHAR*)"SELECT emp_id, emp_name, salary, dept_no FROM test_emp",
SQL_NTS);
if (!SQL_SUCCEEDED(ret)) {
printf("odbc: query failed!\n");
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return -1;
}
// 6. 遍历结果集
printf("---- 查询结果 ----\n");
SQLCHAR empId[32], empName[64], salary[32], deptNo[32];
SQLLEN ind;
while ((ret = SQLFetch(hstmt)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
SQLGetData(hstmt, 1, SQL_C_CHAR, empId, sizeof(empId), &ind);
SQLGetData(hstmt, 2, SQL_C_CHAR, empName, sizeof(empName), &ind);
SQLGetData(hstmt, 3, SQL_C_CHAR, salary, sizeof(salary), &ind);
SQLGetData(hstmt, 4, SQL_C_CHAR, deptNo, sizeof(deptNo), &ind);
printf("%s | %s | %s | %s\n", empId, empName, salary, deptNo);
}
在 VS2022 中编写 C++ 程序,通过 ODBC API 连接达梦数据源。调用 SQLConnect 连接 DSN,用 SQLExecDirect 执行 SELECT 查询,通过 SQLFetch 和 SQLGetData 遍历结果集,成功输出 test_emp 表的员工数据。验证了 ODBC 驱动配置正确,C++ 程序可以正常访问达梦数据库。
文章
阅读量
获赞
