注册
存储程序对象与 JDBC/ODBC 连接配置
专栏/技术分享/ 文章详情 /

存储程序对象与 JDBC/ODBC 连接配置

chinuppp 2026/10/10 193 0 0
摘要

一、背景

在达梦数据库的日常开发中,存储过程、函数、触发器、包、视图这些“存储程序对象”是绕不开的核心技能,而 JDBC 和 ODBC 则是应用程序连接数据库的两种主要方式。本文把这两块内容整理成一份完整的开发实战笔记,包含每个对象的概念、使用场景和动手实验,以及 JDBC/ODBC 的连接配置与代码验证。

二、过程、函数、触发器、包与视图的基本操作

存储过程(Procedure)
存储过程是一组预先编译好、存放在数据库中的 SQL 语句集合。它通过 CALL 或 EXECUTE 显式调用,可以有输入参数、输出参数,但不强制要求返回值。
使用场景:适合封装复杂的业务逻辑,比如批量数据清洗、定期报表生成、多表关联写入等。一次编写、多次调用,减少网络传输,提高执行效率。

函数(Function)
函数与存储过程类似,也是一段存储在数据库中的代码,但它必须返回一个值。函数可以在 SELECT 语句中直接调用,像使用内置函数一样使用。
使用场景:适合封装计算逻辑,比如根据身份证号计算年龄、根据订单金额计算折扣、格式化字符串等。凡是需要在 SQL 中直接使用计算结果的地方,都适合用函数。

触发器(Trigger)
触发器是一种特殊的存储程序,它不能手动调用,而是绑定在表或视图上,由特定的数据操作(如 INSERT、UPDATE、DELETE)自动激发。
使用场景:适合做自动化的联动操作,比如插入订单后自动扣减库存、修改数据后自动记录审计日志、删除数据前做数据校验等。它把“该做的事”绑定在数据变化上,避免遗漏。

包(Package)
包是用于打包管理相关存储过程、函数、变量、游标等对象的容器,由“包头”和“包体”两部分组成:包头声明对外接口,包体实现具体逻辑。
使用场景:适合把一组相关的功能组织在一起,比如“员工管理包”里放涨薪、调岗、查信息等过程。包能提高代码的模块化程度,也方便统一授权和管理。

视图(View)

​ 视图是一种虚拟表,它本身不存储数据,只保存一条查询语句的定义。当你查询视图时,数据库会实时执行视图背后的那条 SELECT 语句,把结果返回给你。所以视图可以理解为“给一条复杂查询起个名字,以后就当表来用”。

使用场景:把多表关联、聚合计算的复杂 SQL 封装成视图,上层应用直接 SELECT * FROM 视图名 即可,不用每次都写一长串 JOIN。把业务上常用的统计逻辑(如“有效订单”“活跃用户”)定义成视图,所有报表和应用都从同一个视图取数,避免口径不一致。

1.存储过程实战

  1. 准备测试表:

    CREATE TABLE test_emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(30), salary DECIMAL(10,2), dept_no INT );

​ 建一张员工测试表,包含工号、姓名、薪资、部门号四个字段,后面所有存储过程都基于这张表操作。

  1. 创建无参数存储过程:

    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;

image.png

查询测试表,确认数据已经插入成功。

  1. 创建带 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;

image.png

  1. 删除调用过程:

    DROP PROCEDURE p_insert_emp;

​ 用 DROP PROCEDURE 删除不再需要的存储过程。
image.png

2.函数实战

  1. 写一个不带参数、固定返回一个字符串的函数。

    CREATE OR REPLACE FUNCTION f_hello RETURN VARCHAR(30) AS BEGIN RETURN 'hello_dm'; END; /

创建一个无参函数,调用时固定返回 ‘hello_dm’。函数用 RETURN 声明返回类型,函数体里必须有一条 RETURN 语句把值返回。

image.png

在 SELECT 中直接调用函数,DUAL 是达梦提供的一张单行虚拟表,专门用来测试表达式或函数。

  1. 创建带 IN 参数的函数
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 参数的函数,传入工号后查询员工姓名并返回。如果查不到数据,就在异常处理里返回 '查无此人',避免函数报错中断。
image.png
综合表查询:把函数用在 SELECT 列表里,对每一行都调用一次,这是函数最典型的用法

SELECT emp_id, f_get_emp_name(emp_id) AS emp_name FROM test_emp;

image.png

  1. 创建带计算逻辑的函数
    目标:传入薪资,返回对应的个税(简化计算:超过 5000 的部分按 10% 计算)
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;

结合表查询,对每个员工的薪资都算一次税,返回结果作为一列。
image.png
删除函数:

DROP FUNCTION f_get_emp_name;

3. 触发器

  1. 创建一个日志表,用来记录员工表的操作痕迹,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) );
  1. 创建 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);

    验证:

image.png

  1. 创建 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 来引用即将被删除的那行数据。

验证:
image.png
删除触发器

DROP TRIGGER trg_emp_insert;

注意::NEW 和 :OLD

​ :NEW 代表新数据,:OLD 代表旧数据

4. 包

  1. 创建包头:声明一个包,里面包含前面写过的一个过程和一个函数。

    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; /

image.png

删除包时,包头和包体一起删除:

DROP PACKAGE pkg_emp;

5. 视图

  1. 把员工表里薪资大于 8000 的员工单独做成一个视图。
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;

image.png

  1. 创建多表关联视图

    先建一张部门表:

    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;

    查询视图,返回员工和对应部门名称。

image.png

三、 JDBC 与 ODBC 接口连接配置

1. JDBC 接口连接配置

JDBC(Java Database Connectivity)是 Java 程序访问关系型数据库的标准接口。Java 应用通过 JDBC API 调用达梦提供的 JDBC 驱动,完成连接建立、SQL 执行和结果读取。

普通项目可通过此方式进行添加驱动,点击文件–> 项目结构–> 库,添加 DmJdbc 驱动。

image.png

测试连接实例:

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(); } } }

image.png
查一下 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); }

image.png

2. ODBC 接口连接配置

ODBC 提供访问不同类型的数据库的途径。结构化查询语言 SQL 是一种用来访问数据库的语言。通过使用 ODBC,应用程序能够使用相同的源代码和各种各样的数据库交互。这使得开发者不需要以特殊的数据库管理系统 DBMS 为目标,或者了解不同支撑背景的数据库的详细细节,就能够开发和发布客户/服务器应用程序。

打开 ODBC 管理器:按 Win + R,输入 odbcad32 回车

image.png

**添加 DSN:**在“系统 DSN”标签页点击“添加”,选择 DM8 ODBC DRIVER
image.png

填写连接信息:在弹出的窗口里填入达梦数据库的 IP、端口(默认 5236)、用户名(SYSDBA)和密码,并测试成功:
image.png
image.png
使用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; }

image.png

测试连接成功!

我们再来测试,是否能查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); }

image.png

在 VS2022 中编写 C++ 程序,通过 ODBC API 连接达梦数据源。调用 SQLConnect 连接 DSN,用 SQLExecDirect 执行 SELECT 查询,通过 SQLFetch 和 SQLGetData 遍历结果集,成功输出 test_emp 表的员工数据。验证了 ODBC 驱动配置正确,C++ 程序可以正常访问达梦数据库。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服