用于调用存储过程的 Spring JDBC 模板

Posted

技术标签:

【中文标题】用于调用存储过程的 Spring JDBC 模板【英文标题】:Spring JDBC Template for calling Stored Procedures 【发布时间】:2012-03-10 19:37:32 【问题描述】:

使用现代(大约 2012 年)Spring JDBC 模板调用存储过程的正确方法是什么?

比如说,我有一个存储过程,它同时声明了INOUT 参数,如下所示:

mypkg.doSomething(
    id OUT int,
    name IN String,
    date IN Date
)

我遇到过基于CallableStatementCreator 的方法,我们必须显式注册INOUT 参数。考虑JdbcTemplate 类中的以下方法:

public Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)

当然,我知道我可以这样使用它:

List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();

declaredParameters.add(new SqlOutParameter("id", Types.INTEGER));
declaredParameters.add(new SqlParameter("name", Types.VARCHAR));
declaredParameters.add(new SqlParameter("date", Types.DATE));

this.jdbcTemplate.call(new CallableStatementCreator() 

    @Override
    CallableStatement createCallableStatement(Connection con) throws SQLException 
        CallableStatement stmnt = con.createCall("mypkg.doSomething(?, ?, ?)");

        stmnt.registerOutParameter("id", Types.INTEGER);
        stmnt.setString("name", "<name>");
        stmnt.setDate("date", <date>);

        return stmnt;
    
, declaredParameters);

当我已经在我的csc 实现中注册它们时,declaredParameters 的目的是什么?换句话说,当spring可以简单地在内部执行con.prepareCall(sql)时,为什么我需要传入csc?基本上,我不能传入其中一个而不是两个吗?

或者,有没有比我目前遇到的更好的方法来调用存储过程(使用 Spring JDBC 模板)?

注意:您可能会发现许多题目看似相似但与本题不同的题目。

【问题讨论】:

我可以看到这个问题现在很流行,距离被问到这个问题已经2年多了。如果有人认为在 Spring 4 结束后有更好的方法来调用存储过程,请发布答案或提出修改建议。 【参考方案1】:

在 Spring 中有多种调用存储过程的方法。

如果您使用CallableStatementCreator 声明参数,您将使用Java 的标准接口CallableStatement,即注册出参数并单独设置它们。使用SqlParameter 抽象将使您的代码更干净。

我建议您查看SimpleJdbcCall。可以这样使用:

SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
    .withSchemaName(schema)
    .withCatalogName(package)
    .withProcedureName(procedure)();
...
jdbcCall.addDeclaredParameter(new SqlParameter(paramName, OracleTypes.NUMBER));
...
jdbcCall.execute(callParams);

对于简单的程序,您可以使用jdbcTemplateupdate方法:

jdbcTemplate.update("call SOME_PROC (?, ?)", param1, param2);

【讨论】:

SimpleJdbcCall 看起来真的很酷。我将对此进行测试,并让您知道它在我的情况下的表现。 更新方法对我不起作用,即使我的语句在数据库中执行得很好,我也收到了错误的 SQL 语法异常。 SimpleJdbcCall 效果很好 我正在尝试以与您所说的相同的方式使用 jdbcTemplate.update(),但出现错误提示 "cannot invoke update on null object" @EpicPandaForce 我有 NamedParameterJdbcTemplate,而不是 JdbcTemplate 不知道是不是Oracle特有的语法,但是Oracle的语法是:jdbcTemplate.update(" call my_schema.my_pkg.SOME_PROC (?, ?) ", param1, param2);(注意大括号)。【参考方案2】:

以下是从java调用存储过程的方法

1.使用 CallableStatement:

 connection = jdbcTemplate.getDataSource().getConnection();
  CallableStatement callableStatement = connection.prepareCall("call STORED_PROCEDURE_NAME(?, ?, ?)");
  callableStatement.setString(1, "FirstName");
  callableStatement.setString(2, " LastName");
  callableStatement.registerOutParameter(3, Types.VARCHAR);
  callableStatement.executeUpdate();

这里我们对外管理资源关闭

2。使用 CallableStatementCreator

 List paramList = new ArrayList();
    paramList.add(new SqlParameter(Types.VARCHAR));
    paramList.add(new SqlParameter(Types.VARCHAR));
    paramList.add(new SqlOutParameter("msg", Types.VARCHAR));

    Map<String, Object> resultMap = jdbcTemplate.call(new CallableStatementCreator() 

    @Override
    public CallableStatement createCallableStatement(Connection connection)
    throws SQLException 

    CallableStatement callableStatement = connection.prepareCall("call STORED_PROCEDURE_NAME(?, ?, ?)");
    callableStatement.setString(1, "FirstName");
            callableStatement.setString(2, " LastName");
            callableStatement.registerOutParameter(3, Types.VARCHAR);
    return callableStatement;

    
    , paramList);

3。使用 SimpleJdbcCall:

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)

.withProcedureName("STORED_PROCEDURE_NAME");

Map<String, Object> inParamMap = new HashMap<String, Object>();
inParamMap.put("firstName", "FirstNameValue");
inParamMap.put("lastName", "LastNameValue");
SqlParameterSource in = new MapSqlParameterSource(inParamMap);


Map<String, Object> simpleJdbcCallResult = simpleJdbcCall.execute(in);
System.out.println(simpleJdbcCallResult);

4.使用 org.springframework.jdbc.object 的 StoredProcedure 类

The Code:
First Create subclass of StoredProcedure: MyStoredProcedure

class MyStoredProcedure extends StoredProcedure 

public MyStoredProcedure(JdbcTemplate jdbcTemplate, String name) 

super(jdbcTemplate, name);
setFunction(false);





Use MyStoredProcedure to call database stored procedure:


//Pass jdbcTemlate and name of the stored Procedure.
MyStoredProcedure myStoredProcedure = new MyStoredProcedure(jdbcTemplate, "PROC_TEST");

//Sql parameter mapping
SqlParameter fNameParam = new SqlParameter("fName", Types.VARCHAR);
SqlParameter lNameParam = new SqlParameter("lName", Types.VARCHAR);
SqlOutParameter msgParam = new SqlOutParameter("msg", Types.VARCHAR);
SqlParameter[] paramArray = fNameParam, lNameParam, msgParam;


myStoredProcedure.setParameters(paramArray);
myStoredProcedure.compile();


//Call stored procedure
Map storedProcResult = myStoredProcedure.execute("FirstNameValue", " LastNameValue");

Reference

【讨论】:

如果我有一个多行返回程序会是什么样子?会是 Map 对象的列表吗? @Kent - 你是对的。对于多行,它将是 List>。 纯 JDBC(选项 1。)似乎是这种情况下最简单的选项 -> +1【参考方案3】:

我通常更喜欢扩展基于 Spring 的 StoredProcedure 类来执行存储过程。

    你需要创建你的类构造函数并且需要在其中调用StoredProcedure类构造函数。这个超类构造函数接受 DataSource 和过程名称。

    示例代码:

    public class ProcedureExecutor extends StoredProcedure 
          public ProcedureExecutor(DataSource ds, String funcNameorSPName) 
            super(ds, funcNameorSPName);
            declareParameter(new SqlOutParameter("v_Return", Types.VARCHAR, null, new SqlReturnType() 
                    public Object getTypeValue(CallableStatement cs,
                         int paramIndex, int sqlType, String typeName) throws SQLException 
                    final String str = cs.getString(paramIndex);
                    return str;
                           
            ));    
            declareParameter(new SqlParameter("your parameter",
                    Types.VARCHAR));
            //set below param true if you want to call database function 
            setFunction(true);
            compile();
            
    

    如下重写存储过程调用的执行方法

    public Map<String, Object> execute(String someParams) 
                 final Map<String, Object> inParams = new HashMap<String, Object>(8);
                 inParams.put("my param", "some value");
                 Map outMap = execute(inParams);
                 System.out.println("outMap:" + outMap);
                 return outMap;
             
    

希望对你有所帮助。

【讨论】:

【参考方案4】:

另一种调用存储过程的方法是:

sql="execute Procedure_Name ?";
Object search[]=Id;
List<ClientInvestigateDTO> client=jdbcTemplateObject.query(sql,search,new 
   ClientInvestigateMapper());

在此示例中,“ClientInvestigateDTO”是 POJO 类,“ClientInvestigateMapper”是映射器类。“client”存储您调用存储过程时获得的所有结果。

【讨论】:

以上是关于用于调用存储过程的 Spring JDBC 模板的主要内容,如果未能解决你的问题,请参考以下文章

Hana 参数化存储过程问题简单 jdbc 模板

Java Spring JDBC 调用存储过程(Stored Procedure) 输入(In)输出(Out)参数

在某些参数类型是用户定义的情况下,如何使用 JDBC/Spring 调用 Oracle 存储过程?

spring中如何调用存储过程

JDBC存储过程调用

Java Spring JDBC Oracle存储过程返回null [重复]