日期:2014-05-16  浏览次数:20472 次

JAVA 执行存储过程(和参数顺序无关,适用oracle数据库)
通常jdbc调用oracle存储过程的输入输出参数都是按顺序,和java端的参数名称没关系,这样当存储过程修改参数时不是非常方便,字段一旦多的话容易错位,这类问题的排查效率也很低,所以想看看有没有办法java端的key值能和sql的参数名称匹配起来。后来发现pl/sql可以用“参数名=>参数值”的方式调用存储过程。java端的DatabaseMetaData 类也有getProcedureColumns可以得到所有的输入输出参数,这样我的想法就可以通过这两点得以实现。以下就是输入和放回都是map的调用存储过程的方法:
/**
	 * 执行存储过程(和参数顺序无关)
	 * @param procName 存储过程名称
	 * @param procParam 参数(key需大写)
	 * @return
	 */
	public static Map<String, Object> executeProcedure(String procName,
			Map<String, Object> procParam) {
		Connection conn = null;
		CallableStatement cs = null;
		PreparedStatement ps = null;
		ResultSet rs=null;
		String sqlCall = "";
		Map<String, List<Integer>> outParaIdxMap = new LinkedHashMap<String, List<Integer>>();
		try {
			conn = getConnection(dbResourceName);
			DatabaseMetaData dbmd = conn.getMetaData();
			sqlCall = "{call " + procName + "(";
			String pckgName = procName.lastIndexOf(".") > 0 ? procName
					.substring(0, procName.lastIndexOf(".")).toUpperCase()
					: null;//有包体时
			String proc = procName.substring(procName.lastIndexOf(".") + 1,
					procName.length()).toUpperCase();
			rs = dbmd.getProcedureColumns(pckgName,"" , proc, null);
			int i = 1;
			
			while (rs.next()) {// 拼装参数
				String columnName = rs.getString("COLUMN_NAME");
				Short columnType = rs.getShort("COLUMN_TYPE");
				int dataType = rs.getInt("DATA_TYPE");
				List<Integer> outParaList=new ArrayList<Integer>();
				if (DatabaseMetaData.procedureColumnIn==columnType){
					sqlCall += columnName + "=>'" + procParam.get(columnName)
					+ "',";
				}
				else if (DatabaseMetaData.procedureColumnOut==columnType) {// 输出参数
					sqlCall += columnName + "=>?,";
					outParaList.add(i);
					outParaList.add(dataType);
					outParaIdxMap.put(columnName,outParaList);
					
					i++;
				}
				
			}
			sqlCall = sqlCall.substring(0, sqlCall.lastIndexOf(",")) + ")}";//去除参数最后一个逗号以及后面的部分
			cs = conn.prepareCall(sqlCall);
			for (String key : outParaIdxMap.keySet()) {
				int idx=outParaIdxMap.get(key).get(0);//顺序
				int dataType=outParaIdxMap.get(key).get(1);//dataType
				cs.registerOutParameter(idx, dataType);
			}
			cs.execute();
			for (String key : outParaIdxMap.keySet()) {//组装输出参数值(保留输入参数值)
				int idx=outParaIdxMap.get(key).get(0);//顺序
				procParam.put(key, cs.getObject(idx));
			}
			conn.commit();
		

		} catch (Exception e) {
			System.err.println("SQLException proc=[" + sqlCall + "] ");
			try {
				if (conn != null)
					conn.rollback();
			} catch (SQLException sqlex) {
				sqlex.printStackTrace();
			}
			e.printStackTrace();

		} finally {
			closeRsPsConn(rs, ps, conn);
			closeCsPsConn(cs, ps, conn);
		}
		return procParam;
	}
/**
	 * 把key装换为大写便于后台处理
	 * @param map
	 * @return
	 */
    public static Map<String, Object> convertUpperCaseMap(Map<String, ?> map) {
		Map<String, Object> returnMap=new LinkedHashMap<String, Object>();
		for (String key : map.keySet()) {
			returnMap.put(key.toUpperCase(), map.get(key));
		}
		return returnMap;
	}

开发中遇到的问题:
1.本来CallableStatement 是提供setString(ParameterName,value)这样的方法的,但是看了ibm网站上的资料发现不支持oracle,是infomix的特性,所以无法用,只能麻烦点用index的方式注册输出参数
2.另外所有的参数包括存储过程名称、包名都要大写,否则不认得
3.使用getProcedureColumns(pckgName,"" , proc, null);时注意 第一个参数catalog对应oracle的包名,第二个参数应该是schema名称,我一开始以为是用户名,但不对,只能写空,但不要写null,否则会得到这个实例里面所有这个存储过程的参数,当实例存在多个用户的使用会有问题。

我的异常网推荐解决方案:oracle存储过程,http://www.aiyiweb.com/oracle-develop/177537.html