SQL Server 存儲過程調用封裝

使用Spring的JdbcTemplate簡單實現(xiàn)
支持入?yún)? 支持游標返回
調用方式

import xxx.RoutingDataSource;
import xxx.BaseService;
import xxx.DbStoreProcUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * xxx
 *
 * @author zhangbs
 * @version 2021-01-08
 */
@Service
@Transactional(readOnly = true)
public class UpdateGoodsPricingService extends BaseService {
    @Autowired
    private RoutingDataSource routingDataSource;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * xxxxx
     */
    @Transactional(readOnly = false)
    public void execute() {
        // 設置jdbcTemplate的數(shù)據(jù)源(單體模式單一數(shù)據(jù)源不需要設置jdbcTemplate的數(shù)據(jù)源, 使用默認數(shù)據(jù)源即可)
        jdbcTemplate.setDataSource(routingDataSource.getDataSource("xxx"));

        // 調用存儲過程xxxxxx
        DbStoreProcUtils.execute(jdbcTemplate, "xxxxxx");
    }
}
import xxx.ListUtils;
import xxx.MapUtils;
import org.springframework.jdbc.core.JdbcTemplate;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;

/**
 * EIP調用存儲過程工具類
 *
 * @author zhangbs
 * @version 2020-05-11
 */
public class DbStoreProcUtils {

    /**
     * 存儲過程返回類型 cursor游標
     */
    private static final String RESULT_TYPE_CURSOR = "cursor";
    /**
     * 返回類型 output 以存儲過程方法聲明的參數(shù)進行返回,eg: @p_out_amt decimal(18,2) OutPut
     */
    private static final String RESULT_TYPE_OUT_PUT = "output";


    /**
     * 調用存儲過程,無返回值 無入?yún)?     *
     * @param jdbcTemplate jdbcTemplate對象
     * @param procName     存儲過程名稱
     */
    public static void execute(JdbcTemplate jdbcTemplate, String procName) {
        execute(jdbcTemplate, procName, null, null, null);
    }

    /**
     * 調用存儲過程,有入?yún)?無返回值
     *
     * @param jdbcTemplate jdbcTemplate對象
     * @param procName     存儲過程名
     * @param params       入?yún)?     */
    public static void execute(JdbcTemplate jdbcTemplate, String procName, Map<String, Object> params) {
        execute(jdbcTemplate, procName, params, null, null);
    }


    /**
     * 調用存儲過程,無入?yún)?有游標返回值
     *
     * @param jdbcTemplate jdbcTemplate對象
     * @param procName     存儲過程名
     * @return 返回結果
     */
    public static Object queryForList(JdbcTemplate jdbcTemplate, String procName) {
        return execute(jdbcTemplate, procName, null, RESULT_TYPE_CURSOR, null);
    }

    /**
     * 調用存儲過程,有入?yún)?有游標返回值
     *
     * @param jdbcTemplate jdbcTemplate對象
     * @param procName     存儲過程名
     * @param params       入?yún)?     * @return 返回結果
     */
    public static Object queryForList(JdbcTemplate jdbcTemplate, String procName, Map<String, Object> params) {
        return execute(jdbcTemplate, procName, params, RESULT_TYPE_CURSOR, null);
    }

    /**
     * 調用存儲過程
     * 支持無參,有參,無返回值,有返回值等的調用
     *
     * @param jdbcTemplate jdbcTemplate對象(需要在調用處實例化,并修改其DataSource數(shù)據(jù)源)
     *                     eg: jdbcTemplate.setDataSource(routingDataSource.getDataSource("zy_data"));
     * @param procName     存儲過程名
     * @param params       入?yún)?     * @param resultType   存儲過程返回類型(調用的存過無返回值時,傳null)
     * @param outArgInfo   存儲過程返回值的參數(shù)類型(調用的存過無返回值時傳null,此類型非返回游標里的字段類型!!)
     *                     eg: Map<String, Object> outArgInfo = MapUtils.newHashMap();
     *                     outArgInfo.put("p_out_amt", Types.DECIMAL);
     */
    private static Object execute(JdbcTemplate jdbcTemplate, String procName, Map<String, Object> params,
                                  String resultType, Map<String, Object> outArgInfo) {
        // 檢查輸出參數(shù)的合法性
        checkOutArgs(outArgInfo);

        // 構建存儲過程調用SQL語句
        String execSql = buildCallProcSQL(procName, params, outArgInfo);
        Object result = jdbcTemplate.execute((Connection con) -> {
            CallableStatement cs = con.prepareCall(execSql);
            // 設置入?yún)⒌膮?shù)值
            if(MapUtils.isNotEmpty(params)){
                for (String inArgName : params.keySet()) {
                    cs.setObject(inArgName, params.get(inArgName));
                }
            }

            // 注冊輸出參數(shù)
            if (MapUtils.isNotEmpty(outArgInfo)) {
                // 注冊輸出參數(shù)
                for (String outArgName : outArgInfo.keySet()) {
                    cs.registerOutParameter(outArgName, (Integer) outArgInfo.get(outArgName));
                }
            }
            return cs;
        }, (CallableStatement cs) -> {
            // 如果是游標返回
            if (RESULT_TYPE_CURSOR.equals(resultType)) {
                return convertResultSetToList(cs.executeQuery());
            } else { //如果是output返回
                cs.execute();
                if (RESULT_TYPE_OUT_PUT.equals(resultType)) {
                    return buildOutParams(cs, outArgInfo);
                } else {
                    return null;
                }
            }
        });

        return result;
//        // 執(zhí)行存儲過程
//        return jdbcTemplate.execute(execSql,
//                (CallableStatementCallback<Object>) cs -> {
//                    // 設置入?yún)⒌膮?shù)值
//                    for (String inArgName : params.keySet()) {
//                        cs.setObject(inArgName, params.get(inArgName));
//                    }
//
//                    // 注冊輸出參數(shù)
//                    if (MapUtils.isNotEmpty(outArgInfo)) {
//                        // 注冊輸出參數(shù)
//                        for (String outArgName : outArgInfo.keySet()) {
//                            cs.registerOutParameter(outArgName, (Integer) outArgInfo.get(outArgName));
//                        }
//                    }
//
//                    // 判斷是否有返回值
//                    if (StringUtils.isNotEmpty(resultType)) {
//                        // 如果是游標返回
//                        if (RESULT_TYPE_CURSOR.equals(resultType)) {
//                            return queryForList(cs);
//                        } else if (RESULT_TYPE_OUT_PUT.equals(resultType)) { //如果是output返回
//                            return buildOutParams(cs, outArgInfo);
//                        }
//                    } else {
//                        cs.execute();
//                    }
//                    return null;
//                });
    }

    /**
     * 構建調用存儲過程SQL語句
     *
     * @param procName   存儲過程名
     * @param params     入?yún)⒘斜?     * @param outArgInfo 出參列表
     * @return 存儲過程調用SQL語句
     */
    private static String buildCallProcSQL(String procName, Map<String, Object> params, Map<String, Object> outArgInfo) {
        int paramsSize;
        if (MapUtils.isEmpty(params)) {
            paramsSize = 0;
        } else {
            paramsSize = params.size();
        }
        // 拼接存儲過程參數(shù)占位符
        int placeholderLen = paramsSize + (outArgInfo != null ? outArgInfo.size() : 0);
        String procPlaceHolder = genProcPlaceHolder(placeholderLen);

        // 拼接要執(zhí)行的存儲過程
        return String.format("exec %s %s", procName, procPlaceHolder);
    }

    /**
     * 檢查輸出參數(shù)的合法性
     *
     * @param outArgInfo 輸出參數(shù)
     */
    private static void checkOutArgs(Map<String, Object> outArgInfo) {
        // 出參校驗
        if (MapUtils.isEmpty(outArgInfo)) {
            return;
        }

        // 校驗輸出參數(shù)類型必須為 SQLType
        Collection<Object> values = outArgInfo.values();
        values.forEach(p -> {
            if (!(p instanceof Integer) || !(isIncludeTypes(Integer.parseInt(p.toString())))) {
                throw new RuntimeException("類型代碼必須在【java.sql.Types】類中已定義");
            }
        });
    }


    /**
     * 調用存儲過程后的參數(shù)返回
     *
     * @param cs         CallableStatement對象
     * @param outArgInfo 出參map
     * @return 存過執(zhí)行返回的數(shù)據(jù)
     */
    private static Map<String, Object> buildOutParams(CallableStatement cs, Map<String, Object> outArgInfo) throws SQLException {
        Map<String, Object> resultMap = MapUtils.newHashMap();
        cs.execute();
        for (String key : outArgInfo.keySet()) {
            // 根據(jù)key獲取值
            Object object;
            object = cs.getObject(key);
            // 結果放入并返回
            resultMap.put(key, object);
        }
        return resultMap;
    }

    /**
     * 游標數(shù)據(jù)返回
     * 注:此時在使用cs.execute(),后調用cs.getResultSet()會出現(xiàn)部分存儲過程返回的游標取不到的情況,具體原因未知
     * 改為使用 cs.executeQuery()方法,存過里的更新也能正常執(zhí)行,結果集也能拿到
     *
     * @param cs CallableStatement對象
     * @return 游標數(shù)據(jù)集返回:參數(shù)形式List<Map<String, Object>>
     * @throws SQLException 異常
     */
    private static List queryForList(CallableStatement cs) throws SQLException {
        // 執(zhí)行存儲過程,獲得結果集
        ResultSet rs = cs.executeQuery();
        if (null != rs) {
            // 組裝返回結果集
            return convertResultSetToList(rs);
        }
        return null;
    }


    /**
     * 按指定個數(shù)生成存儲過程占位符
     *
     * @param argCount 參數(shù)個數(shù)
     * @return 占位符字符串, eg: ?,?,?,...
     */
    private static String genProcPlaceHolder(int argCount) {
        List<String> placeHolderList = ListUtils.newArrayList();
        for (int i = 0; i < argCount; i++) {
            placeHolderList.add("?");
        }
        return String.join(",", placeHolderList);
    }

    /**
     * 組裝返回數(shù)據(jù)
     *
     * @param rs ResultSet對象
     * @return 集合數(shù)據(jù)
     * @throws SQLException 異常
     */
    private static List<Map<String, Object>> convertResultSetToList(ResultSet rs) throws SQLException {
        if (rs == null) {
            return null;
        }
        // 封裝到 List
        List<Map<String, Object>> resultList = ListUtils.newArrayList();
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        while (rs.next()) {
            // 轉換每行的返回值到Map中
            Map<String, Object> rowMap = MapUtils.newHashMap();
            for (int i = 1; i <= columnCount; i++) {
                String columnName = metaData.getColumnName(i);
                rowMap.put(columnName, rs.getString(columnName));
            }
            resultList.add(rowMap);
        }
        // 關閉
        rs.close();
        return resultList;
    }

    /**
     * 檢查傳入類型代碼是否合法
     *
     * @param key 類型代碼
     * @return 如果合法則返回 true,否則返回 false
     */
    private static boolean isIncludeTypes(int key) {
        List<Integer> typeCodeList = ListUtils.newArrayList();
        Field[] declaredFields = Types.class.getDeclaredFields();
        // 判斷Types中定義的類型是否包含傳入的參數(shù)類型
        for (Field declaredField : declaredFields) {
            try {
                typeCodeList.add(declaredField.getInt(Types.class));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("類型檢查失敗!");
            }
        }
        return typeCodeList.contains(key);
    }

}

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幕袱,一起剝皮案震驚了整個濱河市蜓肆,隨后出現(xiàn)的幾起案子斟珊,更是在濱河造成了極大的恐慌与倡,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件静秆,死亡現(xiàn)場離奇詭異查库,居然都是意外死亡屠缭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門相赁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來相寇,“玉大人,你說我怎么就攤上這事钮科』缴溃” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵绵脯,是天一觀的道長佳励。 經(jīng)常有香客問我休里,道長,這世上最難降的妖魔是什么赃承? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任妙黍,我火速辦了婚禮,結果婚禮上瞧剖,老公的妹妹穿的比我還像新娘拭嫁。我一直安慰自己,他們只是感情好抓于,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布做粤。 她就那樣靜靜地躺著,像睡著了一般捉撮。 火紅的嫁衣襯著肌膚如雪驮宴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天呕缭,我揣著相機與錄音堵泽,去河邊找鬼。 笑死恢总,一個胖子當著我的面吹牛迎罗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播片仿,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼纹安,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砂豌?” 一聲冷哼從身側響起厢岂,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阳距,沒想到半個月后塔粒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡筐摘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年卒茬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咖熟。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡圃酵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出馍管,到底是詐尸還是另有隱情郭赐,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布确沸,位于F島的核電站捌锭,受9級特大地震影響躬存,放射性物質發(fā)生泄漏。R本人自食惡果不足惜舀锨,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一岭洲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坎匿,春花似錦盾剩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至承桥,卻和暖如春驻粟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凶异。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工蜀撑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剩彬。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓酷麦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喉恋。 傳聞我的和親對象是個殘疾皇子沃饶,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容