首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 傳 List 參數調用 Oracle 存儲過程實現批量入庫

     編輯:Java 相關知識

業務痛點

MyBatis foreach 批量插入存在事務控制難、主鍵回填難、SQL 長度受限等問題。將邏輯下沉到 Oracle 存儲過程,通過集合類型參數一次性傳入,由數據庫內部循環執行,完美解決上述問題。

實現全流程

1. 數據庫對象類型定義

-- 1. 對象類型 (對應 Java 實體屬性)
CREATE OR REPLACE TYPE unit_menu_obj AS OBJECT(
    unitId VARCHAR2(32),
    menuId VARCHAR2(32)
);

-- 2. 表類型 (對應 List<UnitMenu>)
CREATE OR REPLACE TYPE unit_menu_table AS TABLE OF unit_menu_obj;

2. 存儲過程

CREATE OR REPLACE PROCEDURE save_unit_power(
    list0 IN unit_menu_table,
    result0 OUT INT
) AS
    sql_bind VARCHAR2(200);
BEGIN
    FOR i IN 1 .. list0.COUNT LOOP
        sql_bind := 'INSERT INTO ZD_UNIT_MENU(UNIT_ID, MENU_ID) VALUES(''' 
                    || list0(i).unitId || ''', ''' || list0(i).menuId || ''')';
        EXECUTE IMMEDIATE sql_bind;
    END LOOP;
    COMMIT;
    result0 := 1; -- 成功
EXCEPTION
    WHEN OTHERS THEN
        result0 := -1; -- 失敗
        ROLLBACK;
END save_unit_power;

3. MyBatis Mapper XML 配置

<parameterMap type="java.util.Map" id="_map">
    <parameter property="list0" jdbcType="ARRAY" javaType="java.util.List" 
               mode="IN" typeHandler="com.zd.util.ListHandler"/>
    <parameter property="result0" jdbcType="DECIMAL" javaType="java.lang.Integer" mode="OUT"/>
</parameterMap>

<select id="addUnitPower" statementType="CALLABLE" parameterMap="_map">
    <![CDATA[ CALL save_unit_power(?,?) ]]>
</select>

4. 核心 TypeHandler - List 轉 Oracle ARRAY

package com.zd.util;

import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.zd.model.system.UnitMenu;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class ListHandler extends BaseTypeHandler<List<UnitMenu>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<UnitMenu> list, JdbcType jdbcType) throws SQLException {
        Connection conn = null;
        try {
            // 必須獲取物理連接 (取消包裝)
            conn = ps.getConnection().unwrap(OracleConnection.class);
            // 或 DriverManager.getConnection(...) 但需注意連接池管理
            
            ARRAY array = createOracleArray(conn, list);
            ps.setArray(i, array);
        } finally {
            if (conn != null && !conn.isClosed()) conn.close(); // 若手動獲取需關閉
        }
    }

    private ARRAY createOracleArray(Connection conn, List<UnitMenu> list) throws SQLException {
        // 1. 創建表類型描述符
        ArrayDescriptor tableDesc = ArrayDescriptor.createDescriptor("UNIT_MENU_TABLE", conn);
        // 2. 創建對象類型描述符
        StructDescriptor structDesc = StructDescriptor.createDescriptor("UNIT_MENU_OBJ", conn);
        
        // 3. 構建 STRUCT 數組
        STRUCT[] structs = new STRUCT[list.size()];
        for (int idx = 0; idx < list.size(); idx++) {
            UnitMenu um = list.get(idx);
            Object[] attrs = { um.getUnitId(), um.getMenuId() };
            structs[idx] = new STRUCT(structDesc, conn, attrs);
        }
        // 4. 組裝 ARRAY
        return new ARRAY(tableDesc, conn, structs);
    }

    // 查詢端不需要實現
    @Override public List<UnitMenu> getNullableResult(ResultSet rs, String col) { return null; }
    @Override public List<UnitMenu> getNullableResult(CallableStatement cs, int col) { return null; }
}

5. Service 層調用

@Service
public class UnitMenuService {
    @Autowired private UnitMenuMapper unitMenuMapper;

    public int addUnitPower(String unitId, String menuTreeIds) {
        List<UnitMenu> list = new ArrayList<>();
        for (String menuId : menuTreeIds.split(",")) {
            UnitMenu um = new UnitMenu();
            um.setUnitId(unitId);
            um.setMenuId(menuId);
            list.add(um);
        }
        Map<String, Object> params = new HashMap<>();
        params.put("list0", list);
        params.put("result0", 0);
        unitMenuMapper.addUnitPower(params);
        return (Integer) params.get("result0"); // 1 成功, -1 失敗
    }
}

關鍵依賴與注意事項

  • Oracle 驅動包:ojdbc8.jar。
  • 字符集轉換包:orai18n.jar(STRUCT 構建時需將字符串正確轉為 Oracle 內部編碼,否則中文亂碼)。
  • 連接獲取:ps.getConnection().unwrap(OracleConnection.class) 獲取物理連接,避免連接池包裝類導致 createDescriptor 失效。
  • 事務邊界:存儲過程內部 COMMIT/ROLLBACK 管理事務,外層 Spring 事務建議標註 @Transactional(propagation=NOT_SUPPORTED) 或不加事務,避免嵌套事務衝突。
  • 大數據量:Oracle ARRAY 最大 65535 元素,超過需分批調用。

優勢總結

維度MyBatis foreach存儲過程 + ARRAY
事務控制應用層難以精確控制數據庫原子事務
主鍵回填需 selectKey 複雜配置過程內可 RETURNING INTO
SQL 長度受限於 max_allowed_packet無長度限制
性能多次網絡往返單次往返,服務端批量
維護性SQL 分散在 XML邏輯集中在數據庫

總結

MyBatis 通過自定義 TypeHandler 將 List<POJO> 轉為 Oracle ARRAY of OBJECT,配合 statementType="CALLABLE" 調用存儲過程,實現了高性能、強事務、易維護的批量入庫方案。核心難點在於 STRUCT/ARRAY 構建與物理連接獲取,掌握後可廣泛應用於 Oracle 批量業務場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved