跳到主要内容

12-条码打印&存储过程

提示

🐱‍👓任务需求:通过存储过程控制条码生成,包含流水号,一次性会要求生成多个。

1.存储过程


存储过程(Stored Procedure)是数据库中预先编写并存储的一组可重复使用的 SQL 语句集合,它具有类似编程语言的结构(如变量、条件判断、循环、异常处理等),可以通过一个名称被调用执行。

特点
预编译存储过程在创建时就被数据库引擎编译并优化,执行时无需重新解析,提高效率。
存储于数据库它作为数据库对象保存在数据库服务器中,不依赖客户端程序。
可参数化支持输入参数(IN)、输出参数(OUT)和输入/输出参数(INOUT),实现灵活交互。
可复用多个应用程序或用户可通过调用同一个存储过程完成相同逻辑,避免代码重复。
封装逻辑将复杂的业务逻辑(如多表联查、事务控制)封装在数据库层,简化应用层开发。

Qwen生成示例:

CREATE OR REPLACE PROCEDURE AdjustSalary(
p_emp_id IN NUMBER, -- 输入:员工ID
p_ratio IN NUMBER, -- 输入:调整比例(如 0.1 表示加10%)
p_result OUT VARCHAR2 -- 输出:操作结果信息
)
IS
v_current_salary NUMBER; -- 临时变量:当前工资
v_new_salary NUMBER; -- 新工资
emp_not_found EXCEPTION; -- 自定义异常
BEGIN
-- 查询当前工资
SELECT salary
INTO v_current_salary
FROM employees
WHERE emp_id = p_emp_id;

-- 计算新工资
v_new_salary := v_current_salary * (1 + p_ratio);

-- 更新工资
UPDATE employees
SET salary = v_new_salary
WHERE emp_id = p_emp_id;

-- 提交事务(可选,取决于是否由外部控制)
COMMIT;

-- 设置成功结果
p_result := '成功!员工 ' || p_emp_id || ' 的工资已从 ' ||
TO_CHAR(v_current_salary, '99999.99') || ' 调整为 ' ||
TO_CHAR(v_new_salary, '99999.99');

EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 员工不存在
ROLLBACK; -- 回滚事务
p_result := '错误:员工 ID ' || p_emp_id || ' 不存在!';

WHEN OTHERS THEN
-- 其他异常(如数值溢出等)
ROLLBACK;
p_result := '系统错误:' || SQLERRM;

END AdjustSalary;
/

2.Oracle存储过程语法规则速查

创建/替换存储过程

CREATE [OR REPLACE] PROCEDURE 过程名 (
参数1 [IN|OUT|IN OUT] 数据类型, -- 输入/输出参数
...
) IS
-- 变量声明区
BEGIN
-- 执行逻辑
EXCEPTION
-- 异常处理
END [过程名];

参数模式

模式说明示例
IN只读输入参数 (默认)i_site IN NVARCHAR2
OUT只写输出参数o_result OUT STR_ARRAY
IN OUT双向参数io_data IN OUT NUMBER

变量声明

变量名 数据类型 [NOT NULL] [:= 默认值];
-- 示例:
v_num_seq NUMBER; -- 数字
v_date_time CHAR(32); -- 定长字符串
v_db_today NVARCHAR2(10) := ''; -- 变长字符串带默认值

流程控制 条件判断 (IF)

IF 条件 THEN
...
ELSIF 条件 THEN -- 注意是ELSIF不是ELSEIF
...
ELSE
...
END IF;

循环 (FOR/WHILE)

-- 固定次数循环
FOR idx IN 1..i_qty LOOP
...
END LOOP;

-- WHILE循环
WHILE v_count < 10 LOOP
...
END LOOP;

异常处理

EXCEPTION
WHEN 异常类型 THEN -- 如NO_DATA_FOUND
...
WHEN OTHERS THEN -- 捕获所有异常
ROLLBACK; -- 回滚事务
o_mess := SQLERRM; -- 获取错误信息

事务控制

  • COMMIT:提交事务(通常在过程末尾)
  • ROLLBACK:回滚事务(异常处理中使用)
  • 注意:存储过程内不自动提交,需显式控制

动态SQL与游标 显式游标使用

DECLARE
CURSOR cur_name IS SELECT ...;
BEGIN
OPEN cur_name;
FETCH cur_name INTO 变量;
CLOSE cur_name;
END;

集合类型操作

o_result := STR_ARRAY();  -- 初始化集合
o_result.EXTEND; -- 扩展空间
o_result(o_result.LAST) :=; -- 追加元素
  • 集合需要自己定义
  • 此处就是自定义了STR_ARRAY
  • create type STR_ARRAY as table of VARCHAR2(512)

关键函数

函数用途示例
NVL(值, 默认值)空值转换NVL(i_qty, 0)
TO_CHAR(日期, 格式)日期转字符串TO_CHAR(SYSDATE, 'YYYYMMDD')
TO_NUMBER(字符串)字符串转数字TO_NUMBER(i_item_num)
TRIM(字符串)去除两端空格TRIM(i_cus_version)

锁机制

SELECT ... FOR UPDATE;  -- 加行级锁(你代码中用于序列安全)

MERGE语句(UPSERT)

MERGE INTO 目标表 USING 源数据 
ON (关联条件)
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT (...) VALUES (...);

3.存储过程SQL

CREATE OR REPLACE PROCEDURE p_generate_num_print(
i_site IN NVARCHAR2,
i_item IN NVARCHAR2,
i_num_type IN NVARCHAR2,
i_cus_num IN NVARCHAR2, -- ZKUNNR
i_product_code IN NVARCHAR2, -- CUSTOMER_MODEL
i_cus_version IN NVARCHAR2, -- ZCUSTOM_MATER_REV
i_item_num IN NVARCHAR2, -- ZPLDX
i_qty IN NUMBER, -- ★新增:要生成的条码条数
o_result OUT STR_ARRAY, -- ★替换:条码集合
o_status OUT NVARCHAR2,
o_mess OUT NVARCHAR2)
IS
v_num_seq NUMBER;
v_now_seq NUMBER;
v_limit_seq NUMBER;
v_date_time CHAR(32);
v_week_day NUMBER;
v_current_today NVARCHAR2(10); -- ★新增:当前today值
v_db_today NVARCHAR2(10); -- ★新增:数据库中的today值
BEGIN
-- 初始化集合
o_result := STR_ARRAY();

-- 基本校验
IF NVL(i_qty, 0) <= 0 THEN
o_status := 'N';
o_mess := '生成数量 I_QTY 必须大于0';
RETURN;
END IF;

IF i_cus_version IS NULL OR TRIM(i_cus_version) = '' THEN
o_status := 'N';
o_mess := '参数 ZCUSTOM_MATER_REV 不能为空';
RETURN;
END IF;

IF i_product_code IS NULL OR TRIM(i_product_code) = '' THEN
o_status := 'N';
o_mess := '参数 CUSTOMER_MODEL 不能为空';
RETURN;
END IF;

IF i_cus_num IS NULL OR TRIM(i_cus_num) = '' THEN
o_status := 'N';
o_mess := '参数 ZKUNNR 不能为空';
RETURN;
END IF;

IF i_item_num IS NULL OR TRIM(i_item_num) = '' THEN
o_status := 'N';
o_mess := '参数 ZPLDX 不能为空';
RETURN;
END IF;

-- 日期/周次
v_date_time := TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS');
v_week_day := TO_NUMBER(TO_CHAR(SYSDATE, 'D'));
IF v_week_day = 1 THEN
v_week_day := 7;
ELSE
v_week_day := v_week_day - 1;
END IF;

-- ★新增:计算当前today值
v_current_today := TO_CHAR(SYSDATE,'YYIW') || TO_CHAR(v_week_day);

-- 拼板数量
v_limit_seq := NVL(TO_NUMBER(i_item_num), 0);
IF v_limit_seq <= 0 THEN
o_status := 'N';
o_mess := '拼板数量 ZPLDX 必须大于0,当前值:' || i_item_num;
RETURN;
END IF;

-- 主循环:生成 i_qty 条条码
FOR idx IN 1 .. i_qty LOOP
BEGIN
-- ★修改:获取当前序号和today值
SELECT TO_NUMBER(suffix), num_seq, today
INTO v_now_seq, v_num_seq, v_db_today
FROM mesext_number
WHERE site = i_site
AND num_type = i_num_type
AND num_kind = i_item
FOR UPDATE;

EXCEPTION
WHEN NO_DATA_FOUND THEN
v_now_seq := 0;
v_num_seq := 1;
v_db_today := '';
END;

-- ★新增:检查是否是新的一天,如果是则重置
IF v_db_today != v_current_today THEN
-- 新的一天,重置序号
v_now_seq := 1;
v_num_seq := 1;
ELSE
-- 同一天,按原有逻辑计算下一个值
IF v_now_seq = v_limit_seq THEN
v_now_seq := 1;
v_num_seq := v_num_seq + 1;
ELSE
v_now_seq := v_now_seq + 1;
END IF;
END IF;

-- 更新/插入(today值使用当前计算的值)
MERGE INTO mesext_number m
USING (SELECT i_site AS site,
i_num_type AS num_type,
i_item AS num_kind,
v_limit_seq AS prefix,
v_now_seq AS suffix,
v_date_time AS modity_dt,
v_num_seq AS num_seq,
v_current_today AS today -- ★修改:使用当前today值
FROM dual) s
ON (m.site = s.site AND m.num_type = s.num_type AND m.num_kind = s.num_kind)
WHEN MATCHED THEN
UPDATE SET m.prefix = s.prefix,
m.suffix = s.suffix,
m.num_seq = s.num_seq,
m.today = s.today, -- ★新增:更新today
m.modity_dt = s.modity_dt
WHEN NOT MATCHED THEN
INSERT (site,num_type,num_kind,prefix,suffix,today,num_seq,create_dt,modity_dt)
VALUES (s.site,s.num_type,s.num_kind,s.prefix,s.suffix,
s.today,s.num_seq,s.modity_dt,s.modity_dt);

-- 拼装条码(规则不变)
o_result.EXTEND;
o_result(o_result.LAST) :=
i_cus_num || i_product_code || ';' ||
i_cus_version || ';' ||
v_num_seq || '-' || v_now_seq || ';' ||
v_now_seq || ';' ||
v_current_today; -- ★修改:使用当前today值

END LOOP;

o_status := 'Y';
o_mess := '成功生成 ' || i_qty || ' 条条码';
COMMIT;

EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
o_status := 'N';
o_mess := SQLERRM;
o_result := NULL;
END p_generate_num_print;

4.整体操作架构

前端页面 (jsp) 
↓ 发送AJAX请求
后端服务 (ejb.jsp)
↓ 调用外部服务
条码生成服务 (ip)
↓ 返回条码
打印服务 (PrintService)

  • 界面jsp和服务ejb.jsp分离
  • 通过ajax发送,调用ejb的后端方法
  • ser = printUtil.getSerial()页面初始化获取本地打印机服务
  • 用户触发打印,发送请求调用
  • 存储过程涉及自定义集合,封装到java客户端处理 REP_CONF 将存储过程的执行,通过REP_CODE存储和调用。如:
p_generate_num_print(IN:I_SITE, IN:I_ITEM, IN:I_NUM_TYPE, IN:I_CUS_NUM, IN:I_PRODUCT_CODE, IN:I_CUS_VERSION, IN:I_ITEM_NUM, IN:I_QTY, OUT:O_RESULT:STR_ARRAY, OUT:O_STATUS, OUT:O_MESS)

http调用 mes内部没有针对集合类型返回的处理,需要放在mom客户端处理这段逻辑。通过http方式调用mom接口

public List<String> httpGetPrintSfcList(
String serverIpPort,String site,String item,String numType,String cusNum,
String productCode,String cusVersion,String itemNum,String qty) throws Exception {
    // 构建 URL
    StringBuilder url = new StringBuilder();
    url.append("http://").append(serverIpPort).append("/topband-mom/tb/mes/generateMultiSfc?");
    url.append("site=").append(site);
    url.append("&item=").append(item);
    url.append("&numType=").append(numType);
    url.append("&cusNum=").append(cusNum);
    url.append("&productCode=").append(productCode);
    url.append("&cusVersion=").append(cusVersion);
    url.append("&itemNum=").append(itemNum);
    url.append("&qty=").append(qty);
   
    HttpClient client = new HttpClient();
    // 设置超时
    client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
    client.getHttpConnectionManager().getParams().setSoTimeout(10000);
    GetMethod method = new GetMethod(url.toString());

    try {
        int statusCode = client.executeMethod(method);
        if (statusCode == HttpStatus.SC_OK) {
            String responseBody = method.getResponseBodyAsString();
            JSONObject result = JSONObject.fromObject(responseBody);
            String code = result.getString("code");
            if (!"0".equals(code)) {
                String msg = result.getString("message");
                throw new RuntimeException("调用Mom接口出错: " + msg);
            }
            // 提取 data 字段   一个 JSON 数组
            JSONArray dataArray = result.getJSONArray("data");
            List<String> sfcList = new ArrayList<String>();
            for (int i = 0; i < dataArray.size(); i++) {
                sfcList.add(dataArray.getString(i));
            }
            return sfcList;
        } else {
            throw new RuntimeException("HTTP请求失败: " + method.getStatusLine());
        }
    } finally {
        method.releaseConnection();
    }
}

5.java存储过程处理

baseFunction传参查询调用。查询REP_CODE,获取存储过程调用代码,传参调用。

String sql = "select * from table where rep_code =:code";  
List<Map<String, Object>> sqlResult = baseFunctionService.getSqlResult(sql);
if(CollectionUtil.isEmpty(sqlResult)){
return OperationResult.buildFailureResult("未配置RepCode");
}
String procedureSignature = sqlResult.get(0).get("PRE_HANDLE").toString();
Map<String, Object> inParams = new HashMap<>();
inParams.put("I_SITE", site);
.........
// 传参
// 调用执行....

存储过程调用处理 from Qwen

public Object callMyProcedureV2(String procedureSignature, Map<String, ?> inParams) {  
// 用于存储所有输出参数的值
final Map<String, Object> outValues = new HashMap<>();

// 1. 解析过程签名
String procedureName = procedureSignature.substring(0, procedureSignature.contains("(") ? procedureSignature.indexOf("(") : procedureSignature.length());
String paramStr = procedureSignature.contains("(") ? procedureSignature.substring(procedureSignature.indexOf("(") + 1, procedureSignature.length() - 1) : "";
String[] paramDefinitions = StringUtils.isNotBlank(paramStr) ? paramStr.split(",") : new String[0];

// 2. 构造 JDBC 调用字符串, e.g., "{call p_get_names(?,?,?)}"
String jdbcCallString = "{call " + procedureName + "(" + String.join(",", Collections.nCopies(paramDefinitions.length, "?")) + ")}";

// 3. 使用 Hibernate Session 安全地执行原生 JDBC 操作
Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> {
try (CallableStatement cs = connection.prepareCall(jdbcCallString)) {

// 4. 注册所有参数,并为 IN 参数赋值
for (int i = 0; i < paramDefinitions.length; i++) {
String def = paramDefinitions[i].trim();
String[] parts = def.split(":");
String mode = parts[0].trim().toUpperCase();
String paramName = parts[1].trim();
int paramIndex = i + 1;

if ("IN".equals(mode)) {
// 注意:你的旧代码是 I_ + fieldName,这里我们直接用 fieldName 匹配 inParams 的 key Object value = inParams.get(paramName);
if (value == null) {
// 如果 map 中没有这个 key,或者 key 对应的值是 null,我们都提供一个默认值
value = StringUtils.EMPTY;
}
cs.setObject(paramIndex, value);
} else if ("OUT".equals(mode)) {
if (parts.length == 3) {
// 这是一个自定义类型,如 "OUT:O_RESULT:STR_ARRAY" String customTypeName = parts[2].trim().toUpperCase();
cs.registerOutParameter(paramIndex, OracleTypes.ARRAY, customTypeName);
} else {
// 这是一个标准类型,我们默认为 VARCHAR cs.registerOutParameter(paramIndex, java.sql.Types.VARCHAR);
}
} }
// 5. 执行存储过程
cs.execute();

// 6. 获取所有 OUT 参数的值
for (int i = 0; i < paramDefinitions.length; i++) {
String def = paramDefinitions[i].trim();
String[] parts = def.split(":");
String mode = parts[0].trim().toUpperCase();

if ("OUT".equals(mode)) {
String paramName = parts[1].trim();
int paramIndex = i + 1;

if (parts.length == 3) {
// 获取自定义 ARRAY 类型的结果
Array oracleArray = cs.getArray(paramIndex);
if (oracleArray != null) {
// 将 java.sql.Array 转换为 Java List Object[] arrayData = (Object[]) oracleArray.getArray();
List<String> resultList = Arrays.stream(arrayData)
.map(String::valueOf)
.collect(Collectors.toList());
outValues.put(paramName, resultList);
oracleArray.free(); // 释放数据库资源
} else {
outValues.put(paramName, Collections.emptyList());
}
} else {
// 获取标准 VARCHAR 类型的结果
outValues.put(paramName, cs.getString(paramIndex));
}
} } } catch (SQLException e) {
throw new RuntimeException("执行存储过程时发生数据库错误", e);
}
});

// 7. 检查状态并返回结果
Preconditions.checkArgument("Y".equals(String.valueOf(outValues.get("O_STATUS"))), String.valueOf(outValues.get("O_MESSAGE")));

return outValues.get("O_RESULT");
}