一、概述

如果想要执行存储过程,我们应该使用 CallableStatement 接口。

CallableStatement接口继承自PreparedStatement接口。所以CallableStatement接口包含有Statement 接口和PreparedStatement 接口定义的全部方法,但是并不是所有的方法我们都要使用,主要使用的方法有这样几个:

CallableStatement 常用方法:

返回类型 方法签名 说明
boolean execute() 执行 SQL 语句,如果第一个结果是 ResultSet 对 象,则返回 true;如果第一个结果是更新计数或者没 有结果,则返回 false
void registerOutParameter(int parameterIndex,int sqlType) 按顺序位置parameterIndex 将OUT 参数注册为 JDBC 类型sqlType,sqlType 为Types 类中的常量
Type getType(int parameterIndex) 根据参数的序号获取指定的 JDBC 参数的值。第一 个参数是 1,第二个参数是 2,依此类推

我们可以使用execute()方法来执行存储过程。CallableStatement 为所有的数据库提供了一种统一的标准形式调用存储过程。所以,你将会看到我们使用execute()调用存储过程的语法与在Oracle 中会所有不同。

为了获得存储过程或函数的返回值,我们需要使用 registerOutParameter()方法将返回的参数注册为JDBC 的类型。 registerOutParameter()方法的第一个参数是参数的序号,第一个为1,第二个为2,以此类推。第二个参数需要一个int 值,用来标记JDBC 的类型,我们可以使用java.sql.Types 类中的常量来设置这个参数。比如VARCHAR、DOUBLE 等类型。如果类型不够用,也可以从具体数据库的驱动中寻找合适的类型常量。如果存储过程或函数有返回值,这个方法是必须要调用的,否则无法得到返回值,甚至会发生异常。

CallableStatement 接口中定义了很多get 方法,用于获取存储过程返回的值,根据值的类型不同,你可以使用不同get 方法,比如getInt()、getString()、getDouble()等等。
我们看一下使用CallableStatement 接口执行存储过程和函数的语法格式。

存储过程:**{call <procedure-name>[(<arg1>,<arg2>, ...)]}**
函数:**{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}**

如果要调用存储过程,则使用第一种语法,就是开头不带问号的语法,call 后面是过程名,
如果没有参数,可以省略小括号。

如果要调用函数,则使用第二种语法,开头带有一个问号加等号,实际上这个问号就是一个占位符,这个问号总是调用函数的第一个占位符。其它部分与过程的语法相同

二、CallableStatement 执行存储过程

2.1、建立基类

  1. package com.pb.emp.dao;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import com.pb.emp.untily.ConfigManager;
  8. public class BaseDao {
  9. protected Connection conn;
  10. protected PreparedStatement ps;
  11. protected ResultSet rs;
  12. //建立连接
  13. public boolean getConnection(){
  14. String driver=ConfigManager.getInstance().getString("jdbc.driver_class");
  15. String url=ConfigManager.getInstance().getString("jdbc.connection.url");
  16. String username=ConfigManager.getInstance().getString("jdbc.connection.username");
  17. String password=ConfigManager.getInstance().getString("jdbc.connection.password");
  18. try {
  19. Class.forName(driver);
  20. conn=DriverManager.getConnection(url,username, password);
  21. } catch (ClassNotFoundException e) {
  22. // TODO Auto-generated catch block
  23. e.printStackTrace();
  24. return false;
  25. } catch (SQLException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. return false;
  29. }
  30. return true;
  31. }
  32. //增加,修改,删除
  33. public int executeUpdate(String sql, Object[] params){
  34. getConnection();
  35. int updateRow=0;
  36. try {
  37. ps=conn.prepareStatement(sql);
  38. //填充占位符
  39. for(int i=0;i<params.length;i++){
  40. ps.setObject(i+1, params[i]);
  41. }
  42. updateRow = ps.executeUpdate();
  43. } catch (SQLException e) {
  44. // TODO Auto-generated catch block
  45. e.printStackTrace();
  46. }
  47. return updateRow;
  48. }
  49. //
  50. //查询
  51. public ResultSet executeSQL(String sql, Object[] params){
  52. getConnection();
  53. try {
  54. ps=conn.prepareStatement(sql);
  55. //填充占位符
  56. for(int i=0;i<params.length;i++){
  57. ps.setObject(i+1, params[i]);
  58. }
  59. rs = ps.executeQuery();
  60. } catch (SQLException e) {
  61. // TODO Auto-generated catch block
  62. e.printStackTrace();
  63. }
  64. return rs;
  65. }
  66. // 关闭资源
  67. public boolean closeResource() {
  68. if(rs!=null){
  69. try {
  70. rs.close();
  71. } catch (SQLException e) {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. return false;
  75. }
  76. }
  77. if(ps!=null){
  78. try {
  79. ps.close();
  80. } catch (SQLException e) {
  81. // TODO Auto-generated catch block
  82. e.printStackTrace();
  83. return false;
  84. }
  85. }
  86. if(conn!=null){
  87. try {
  88. conn.close();
  89. } catch (SQLException e) {
  90. // TODO Auto-generated catch block
  91. e.printStackTrace();
  92. return false;
  93. }
  94. }
  95. return true;
  96. }
  97. }

2.2、执行不带参但是有返回值的存储过程

新建类来继承上面的类也可以继承,下面建立存储过程

  1. --查询emp表记录数
  2. CREATE OR REPLACE PROCEDURE getEmpCount(v_count OUT NUMBER)
  3. AS
  4. BEGIN
  5. SELECT COUNT(*) INTO v_count FROM emp;
  6. END;

调用

  1. //执行不带参但是有返回值的存储过程获取emp表总记录数
  2. public int getTotalCountProc(){
  3. //定义一个变量来接收结果
  4. int totalCount=0;
  5. //声明CallableStatement对象
  6. CallableStatement proc=null;
  7. String sql="{call getEmpCount(?)}";
  8. try {
  9. //建立连接
  10. getConnection();
  11. //CallableStatement对象
  12. proc=conn.prepareCall(sql);
  13. //将数据库对象数据类型注册为java中的类型
  14. proc.registerOutParameter(1, Types.INTEGER);
  15. //执行
  16. proc.execute();
  17. //接收返回值
  18. totalCount=proc.getInt(1);
  19. } catch (SQLException e) {
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. }
  23. return totalCount;
  24. }

2.3、执行带参带返回值的存储过程

  1. --根据部门编号和姓名查询人数
  2. CREATE OR REPLACE PROCEDURE getEmpCount(v_deptno NUMBER, v_ename VARCHAR2,v_count OUT NUMBER)
  3. AS
  4. BEGIN
  5. SELECT COUNT(*) INTO v_count FROM emp
  6. WHERE ename LIKE '%'||v_ename||'%' AND deptno=v_deptno;
  7. END;
  1. //执行带参带返回值的存储过程
  2. public int getTotalCountProc1(int deptno,String ename){
  3. //定义一个变量来接收结果
  4. int totalCount=0;
  5. //声明CallableStatement对象
  6. CallableStatement proc=null;
  7. String sql="{call getEmpCount(?,?,?)}";
  8. //建立连接
  9. getConnection();
  10. //CallableStatement对象
  11. try {
  12. proc=conn.prepareCall(sql);
  13. //设置占位符
  14. //Object [] params={deptno,ename};
  15. //只设置输入参数即可
  16. proc.setInt(1, deptno);
  17. proc.setString(2, ename);
  18. //proc.setInt(3, totalCount);
  19. //将数据库对象数据类型注册为java中的类型,将输出参数转换
  20. proc.registerOutParameter(3, Types.INTEGER);
  21. //执行
  22. proc.execute();
  23. //获取结果
  24. totalCount=proc.getInt(3);
  25. } catch (SQLException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }finally{
  29. this.closeResource();
  30. if(proc!=null){
  31. try {
  32. proc.close();
  33. } catch (SQLException e) {
  34. // TODO Auto-generated catch block
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. return totalCount;
  40. }

2.4、执行返回值为游标的存储过程

  1. --查询员工所有信息
  2. CREATE OR REPLACE PROCEDURE emp_cur(emp_cur OUT SYS_REFCURSOR)
  3. AS
  4. BEGIN
  5. OPEN emp_cur FOR SELECT * FROM emp;
  6. END;
  1. //执行返回值为游标的存储过程 游标名emp_cur
  2. public List<Emp> getempProc1(){
  3. List<Emp> emplist=new ArrayList<Emp>();
  4. String sql="{call emp_cur(?) }";
  5. //声明CallableStatement对象
  6. CallableStatement proc=null;
  7. //建立连接
  8. getConnection();
  9. try {
  10. //执行
  11. proc=conn.prepareCall(sql);
  12. //注册类型为数据库游标类型
  13. proc.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
  14. //接收结果集
  15. proc.execute();
  16. //获取结果第一个对象
  17. rs=(ResultSet) proc.getObject(1);
  18. while(rs.next()){
  19. int empno=rs.getInt("empno");
  20. String ename=rs.getString("ename");
  21. String job=rs.getString("job");
  22. int mgr=rs.getInt("mgr");
  23. Date hiredate=rs.getDate("hiredate");
  24. double sal=rs.getDouble("sal");
  25. double comm=rs.getDouble("comm");
  26. int deptno=rs.getInt("deptno");
  27. //声明Emp对象
  28. Emp emp=new Emp();
  29. //将得到的值添加到对象中
  30. emp.setEmpno(empno);
  31. emp.setEname(ename);
  32. emp.setJob(job);
  33. emp.setMgr(mgr);
  34. emp.setHiredate(hiredate);
  35. emp.setSal(sal);
  36. emp.setComm(comm);
  37. emp.setDeptno(deptno);
  38. //将对象添加到集合
  39. emplist.add(emp);
  40. }
  41. } catch (SQLException e) {
  42. // TODO Auto-generated catch block
  43. e.printStackTrace();
  44. }finally{
  45. this.closeResource();
  46. if(proc!=null){
  47. try {
  48. proc.close();
  49. } catch (SQLException e) {
  50. // TODO Auto-generated catch block
  51. e.printStackTrace();
  52. }
  53. }
  54. }
  55. return emplist;
  56. }

以上看出,需要将输出的参数,和结果注册,输入的参数不要注册,

但输入参数需要设置占位符

三、使用JdbcTemplate执行存储过程

使用CallableStatementCallback

这里使用接口

<T> T execute(String callString, CallableStatementCallback action)
Execute a JDBC data access operation, implemented as callback action working on a JDBC CallableStatement.

将2.3的例子用jdbcTemplate实现

  1. //执行带参带返回值的存储过程
  2. public Object getTotalCountProc1(int deptno,String ename){
  3. //定义一个变量来接收结果
  4. int totalCount=0;
  5. String sql="{call getEmpCount(?,?,?)}";
  6. //建立连接
  7. return jdbcTemplate.execute(sql,(CallableStatement proc)->{//声明CallableStatement对象
  8. //设置占位符
  9. //Object [] params={deptno,ename};
  10. //只设置输入参数即可
  11. proc.setInt(1, deptno);
  12. proc.setString(2, ename);
  13. //proc.setInt(3, totalCount);
  14. //将数据库对象数据类型注册为java中的类型,将输出参数转换
  15. proc.registerOutParameter(3, Types.INTEGER);
  16. //执行
  17. boolean retVal = proc.execute();
  18. int updateCount = proc.getUpdateCount();
  19. return proc.getObject(3);
  20. });
  21. }

一个通用的函数:

  1. public Object callProcedure(final String callStr, final Object[] inValues,
  2. final int[] outTypes) {
  3. return jdbcTemplate.execute(callStr, (CallableStatement cs)->{
  4. int i = 0;
  5. if (inValues != null) {
  6. //入参设置
  7. for (int index = 0; index < inValues.length; index++) {
  8. i++;
  9. Object inValue = inValues[index];
  10. if (inValue instanceof StringBuffer
  11. || inValue instanceof StringWriter) {
  12. cs.setString(i, inValue.toString());
  13. } else if ((inValue instanceof java.util.Date)
  14. && !(inValue instanceof java.sql.Date
  15. || inValue instanceof java.sql.Time || inValue instanceof java.sql.Timestamp)) {
  16. cs.setTimestamp(i, new java.sql.Timestamp(
  17. ((java.util.Date) inValue)
  18. .getTime()));
  19. } else if (inValue instanceof Calendar) {
  20. Calendar cal = (Calendar) inValue;
  21. cs.setTimestamp(i, new java.sql.Timestamp(
  22. cal.getTime().getTime()));
  23. } else {
  24. // Fall back to generic setObject call
  25. // without SQL type specified.
  26. cs.setObject(i, inValue);
  27. }
  28. }
  29. }
  30. //出参设置
  31. if (outTypes != null) {
  32. for (int index = 0; index < outTypes.length; index++) {
  33. i++;
  34. cs.registerOutParameter(i, outTypes[index]);
  35. }
  36. }
  37. boolean retVal = cs.execute();
  38. int updateCount = cs.getUpdateCount();
  39. if (retVal || updateCount != -1) {
  40. // Map returnedResults = new HashMap();
  41. // returnedResults.putAll(extractReturnedResultSets(cs,
  42. // declaredParameters, updateCount));
  43. }
  44. if (outTypes == null || outTypes.length <= 0) {
  45. return null;
  46. } else if (outTypes.length == 1) {
  47. return cs.getObject(i);
  48. } else {
  49. List results = new ArrayList();
  50. // 依次返回结果
  51. for (int index = 0; index < outTypes.length; index++) {
  52. results.add(cs.getObject(inValues.length
  53. + index + 1));
  54. }
  55. return results;
  56. }
  57. });
  58. }

使用接口CallableStatementCreator

注意使用如下接口:

T execute
(CallableStatementCreator
csc, CallableStatementCallback
action)
Execute a JDBC data access operation, implemented as callback action working on a JDBC CallableStatement.

是在执行SQL之前已经创建好了[CallableStatementCreator](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/CallableStatementCreator.html),所以在Spring创建Bean的时候,无法改变指定的SQL的参数。下列情况只能调用一次,填入一次(jobType,step),再调用就会报错。

  1. private static final String INTEREST_JOB_SQL = "{call INTEREST_JOB(?,?)}"
  2. jdbcTemplate.execute((Connection con)->{
  3. CallableStatement cs = con.prepareCall(INTEREST_JOB_SQL);
  4. cs.setInt(1,jobType);
  5. cs.setInt(2,step);
  6. return cs;
  7. }, (CallableStatementCallback<Boolean>) PreparedStatement::execute);

若想每次传入不同参数只能使用

  1. String callInterestJob = String.format("{call INTEREST_JOB(%s,%s)}",jobType,step);
  2. jdbcTemplate.execute(callInterestJob);


四、执行函数

3.1 、函数功能为根据雇员id 返回姓名

  1. CREATE OR REPLACE FUNCTION getename(v_empno NUMBER)
  2. RETURN VARCHAR2
  3. AS
  4. v_ename VARCHAR2(20);
  5. BEGIN
  6. SELECT ename INTO v_ename FROM emp WHERE empno=v_empno;
  7. RETURN v_ename;
  8. END;
  1. public void getenamefun(int empno){
  2. //sql
  3. String ename="";
  4. String sql="{?=call getename(?)}";
  5. CallableStatement fun=null;
  6. getConnection();
  7. try {
  8. fun=conn.prepareCall(sql);
  9. fun.setInt(2, empno);
  10. fun.registerOutParameter(1, Types.VARCHAR);
  11. fun.execute();
  12. ename=fun.getString(1);
  13. System.out.println(ename);
  14. } catch (SQLException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. }

其它的方法与过程一样,只是多了个返回值类型。