16. 存储过程和存储函数

文章目录

1.存储过程和存储函数

1.指存储在数据库中供所有用户程序调用的子程序叫存储过程、存储函数。
2.存储过程和存储函数的相同点:完成特定功能的程序。
3.存储过程和存储函数的区别:是否用return语句返回值。

2.创建和使用存储过程

2.1 语法:

语法:

CREATE [OR REPLACE] PROCEDURE 过程名(参数列表)
AS 
PLSQL子程序体

解释:

1. CREATE 创建
2. OR REPLACE 替换,存储过程/函数不支持修改,只能创建替换。
3. PROCEDURE 关键字
4. 过程名(参数列表) 存储过程/函数名称,可带参数,也可不带
5. AS 声明部分,比如创建变量

2.2 第一个存储过程,打印hello world

案例:

CREATE OR REPLACE PROCEDURE sayhelloworld 
AS
	-- 说明部分
BEGIN
	DBMS_OUTPUT.PUT_LINE('hello world');
END;

2.3 调用语法

调用存储过程

1.直接调用 exec sayhelloworld()
2.通过其他存储过程调用,如 
    CREATE OR REPLACE PROCEDURE usesayhelloworld
    AS
    BEGIN
    	sayhelloworld();
    END;

2.4 带参数的存储过程

例: 给指定员工涨100块钱工资,并打印涨前和涨后的工资。

--创建一个带参数的存储过程
CREATE OR REPLACE PROCEDURE raisemoney(userid in NUMBER) 
AS
-- 定义一个变量保存涨前的薪水
    raisemoney YSTEXT.MONEY%type; -- mon是变量 YSTEXT.money是表名和字段名 
BEGIN
    -- 得到员工涨前的薪水,into raisemoney表示赋值
    SELECT money into raisemoney FROM YSTEXT WHERE ID = userid;
    -- 给该员工涨100工资
    UPDATE YSTEXT SET MONEY = MONEY + 100 WHERE ID = userid;
    -- 需不需要commit?
    -- 注意,一般不在存储过程或者存储函数中,commit和rollback。一般交由调用者处理。
    -- 打印
    DBMS_OUTPUT.PUT_LINE('涨前:'||raisemoney||'  涨后:'||(raisemoney +100));
END;

1.Navicat直接执行

2.其他的存储过程调用执行

CREATE OR REPLACE PROCEDURE "USERAISEMONEY" AS
BEGIN
    RAISEMONEY(1);
    RAISEMONEY(1);
    commit;
END;

2.5 调试存储过程


在调试中可能会出现问题

此会话需要 DEBUG CONNECT SESSION 和 DEBUG ANY PROCEDURE 用户权限

出现该问题是因为权限不足

操作步骤:

1.打开cmd窗口
2.以管理员身份登录
    sqlplus / as sysdba
3.查看本用户权限
    USER 为 "SYS"
4.授予调试用户DEBUG CONNECT SESSION 和 DEBUG ANY PROCEDURE权限
    grant DEBUG CONNECT SESSION , DEBUG ANY PROCEDURE to YS;

3.创建和使用存储函数

3.1 存储函数定义

1.函数为一命名的存储函数,可带参数,并返回一计算值。
2.函数和过程的结构类似,但是必须返回一个return子句,用于返回函数值。

3.2 存储函数语法:

语法:

CREATE [OR REPLACE] FUNCTION 函数名(参数列表)
return 函数值类型
AS 
PLSQL子程序体

3.3 存储函数案例:

案例 : 查询某个员工的年收入

CREATE OR REPLACE FUNCTION queryusermoney(eno IN NUMBER) 
return number --定义返回的类型为number
AS
    --定义变量保存员工的薪水和奖金
    pmoney YSTEXT.MONEY%TYPE;--薪水
    pbonus YSTEXT.BONUS%TYPE;--奖金
BEGIN
	--得到该员工的薪水和奖金
	--into关键字赋值
	SELECT MONEY,BONUS INTO pmoney,pbonus FROM YSTEXT WHERE id = eno;
    --直接返回年收入
	return pmoney*12 + pbonus;
END;

表结构和数据

结果1
查询ID为1的员工,结果为30800,正确

结果2
查询ID为3的员工,结果为空

奖金字段为空,代表为0,所以正确写法

    CREATE OR REPLACE FUNCTION queryusermoney(eno IN NUMBER) 
return number --定义返回的类型为number
AS
    --定义变量保存员工的薪水和奖金
    pmoney YSTEXT.MONEY%TYPE;--薪水
    pbonus YSTEXT.BONUS%TYPE;--奖金
BEGIN
	--得到该员工的薪水和奖金
	--into关键字赋值
	SELECT MONEY,BONUS INTO pmoney,pbonus FROM YSTEXT WHERE id = eno;
    --直接返回年收入
	return pmoney*12 + nvl(pbonus,0);
END;

结果3

4.in和out参数

4.1 介绍

1.存储过程和存储函数都可以有out参数。
2.存储过程和存储函数都可以有多个out参数。
3.存储过程可以通过out参数来实现返回值。

4.2 使用原则

那么存储过程和存储函数都能返回一个值,什么时候使用存储函数/存储过程?

原则:

如果只有一个返回值,用存储函数;否则,使用存储过程。

4.3 案例

查询员工的名称,薪水,职位

CREATE OR REPLACE PROCEDURE QUERYUSERINFO(eno IN NUMBER, pname OUT VARCHAR2, pmoney OUT NUMBER, pjob OUT VARCHAR2)
AS
BEGIN
	--查询员工的名称,薪水,职位
	SELECT NAME,MONEY,JOB INTO pname,pmoney,pjob FROM YSTEXT WHERE id = eno;
END;

表结构和数据

结果

5.在应用程序中访问存储过程

代码实现

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>dbtest</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>dbtest</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc14</artifactId>
      <version>9.0.2.0.0</version>
      <type>pom</type>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.oracle.database.jdbc</groupId>
      <artifactId>ojdbc8</artifactId>
      <version>19.3.0.0</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

2.JDBCUtils 工具类,创建连接,释放连接。

package storage.utils;

import java.sql.*;

public class JDBCUtils {
    private static String driver = "oracle.jdbc.OracleDriver";
    private static String url = "jdbc:oracle:thin:@localhost:1521:orcl";
    private static String user = "YS";
    private static String password = "123456";

    //注册数据库驱动
    static {
        try {
            //1.java方式注册驱动
            Class.forName(driver);
            //2.使用jdbc自带方式注册驱动
            //DriverManager.registerDriver(driver);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    //获取数据库连接
    public static Connection getConnection(){
        try{
            return DriverManager.getConnection(url,user,password);
        }catch (SQLException e){
            e.printStackTrace();
        }
        return null;
    }

    //释放数据库资源
    public static void release(Connection conn, Statement st, ResultSet rs){
        if(rs != null){
            try{
                rs.close();
            }catch(SQLException e){
                e.printStackTrace();
            }finally {
                rs = null;
            }
        }

        if(st != null){
            try{
                st.close();
            }catch(SQLException e){
                e.printStackTrace();
            }finally {
                st = null;
            }
        }

        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
              conn = null;
            }
        }
    }


}

3.RunProcedure 单元测试

package storage.run;

import oracle.jdbc.OracleTypes;
import org.junit.jupiter.api.Test;
import storage.utils.JDBCUtils;

import java.sql.CallableStatement;
import java.sql.Connection;

public class RunProcedure {

    @Test
    public void runProcedure(){
        //调用一个已有的存储过程,如:
//        CREATE OR REPLACE PROCEDURE QUERYUSERINFO(eno IN NUMBER,  --查询员工的名称,薪水,职位
//                pname OUT VARCHAR2,
//                pmoney OUT NUMBER,
//                pjob OUT VARCHAR2)
//        AS
//                BEGIN
//        --查询员工的名称,薪水,职位
//        SELECT NAME,MONEY,JOB INTO pname,pmoney,pjob FROM YSTEXT WHERE id = eno;
//        -- DBMS_OUTPUT.PUT_LINE('Navicat for Oracle');
//        END;

        //存储过程写法,四个问号表示四个参数
        String sql = "{call QUERYUSERINFO(?,?,?,?)}";
        Connection conn = null;
        CallableStatement call = null;

        try {
            //1.获取数据库连接
            conn = JDBCUtils.getConnection();

            //2.传入sql,并通过连接获取一个statement
            call = conn.prepareCall(sql);

            //3.对于输入参数 in,需要赋值
            call.setInt(1,1);
            //对于输出参数 out,需要申明,申明是一个输出参数.第二个参数为输出参数类型
            call.registerOutParameter(2, OracleTypes.VARCHAR);
            call.registerOutParameter(3, OracleTypes.NUMBER);
            call.registerOutParameter(4, OracleTypes.VARCHAR);

            //4.执行调用
            call.execute();

            //5.取出结果
            String name = call.getString(2);
            double money = call.getDouble(3);
            String job = call.getString(4);

            System.out.println(name + "\t" + money + "\t" + job);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, null);
        }
    }
}

4.执行结果

数据库数据

运行结果

6.在应用程序中访问存储函数

pom.xml和JDBCUtils工具类和存储过程一致

1.存储函数代码

package storage.run;
/**
 * 执行存储函数
 */

import oracle.jdbc.OracleTypes;
import org.junit.jupiter.api.Test;
import storage.utils.JDBCUtils;

import java.sql.CallableStatement;
import java.sql.Connection;

public class RunFunction {

    @Test
    public void runFunction(){
        //调用一个已有的存储函数,如:
//        CREATE OR REPLACE FUNCTION QUERYUSERMONEY(eno IN NUMBER) --查询某个员工的年收入
//        return number --定义返回的类型为number
//                AS
//        --定义变量保存员工的薪水和奖金
//        pmoney YSTEXT.MONEY%TYPE;--薪水
//        pbonus YSTEXT.BONUS%TYPE;--奖金
//        BEGIN
//                --得到该员工的薪水和奖金
//                --into关键字赋值
//        SELECT MONEY,BONUS INTO pmoney,pbonus FROM YSTEXT WHERE id = eno;
//
//        --直接返回年收入
//        return pmoney*12 + nvl(pbonus,0);
//        END;

        //存储函数写法,最前面问号表示返回值
        String sql = "{?=call QUERYUSERMONEY(?)}";
        Connection conn = null;
        CallableStatement call = null;

        try {
            //1.获取数据库连接
            conn = JDBCUtils.getConnection();

            //2.传入sql,并通过连接获取一个statement
            call = conn.prepareCall(sql);

            //3.对于返回值,需要申明类型
            call.registerOutParameter(1,OracleTypes.NUMBER);
            //给传入参数赋值
            call.setInt(2, 1);

            //4.执行调用
            call.execute();

            //5.取出结果
            double yearMoney = call.getDouble(1);

            System.out.println("该员工年收入为" + "\t" + yearMoney);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, null);
        }
    }
}

2.执行结果

数据库数据

运行结果

7.如何在out函数中使用光标,即out返回集合

7.1 使用光标,申明包结构

包结构包括包头和包体,包同样为数据库的对象,地位和表、视图、序列,存储过程,存储函数一样。
1.包头:只负责申明。
2.包体:只负责实现,包体要实现包头定义的所有存储过程和存储函数。

7.2 包头

语法

--PACKAGE定义包头的关键字
CREATE OR REPLACE PACKAGE MYPACKAGE 
AS
	-- 定义一个光标参数usrcursor,ref cursor是引用光标的意思
	type usrcursor is ref cursor;
	-- 调用存储过程
	PROCEDURE QUERYUSERLIST(dno IN NUMBER, usrList OUT usrcursor);
	
END MYPACKAGE;

快速创建包体

7.3 包体

语法

-- PACKAGE BODY定义包体的关键字
CREATE OR REPLACE 
PACKAGE BODY MYPACKAGE AS
    --存储过程
    PROCEDURE QUERYUSERLIST(dno IN NUMBER, usrList OUT usrcursor) AS
    BEGIN
        --打开光标
        open usrList for SELECT * FROM YSTEXT WHERE id = dno;
    END QUERYUSERLIST;

END MYPACKAGE;

执行结果

8.代码中使用游标

代码

package storage.run;

import oracle.jdbc.OracleCallableStatement;
import oracle.jdbc.OracleTypes;
import oracle.sql.NUMBER;
import org.junit.jupiter.api.Test;
import storage.utils.JDBCUtils;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;

public class RunCursor {

    @Test
    public void runCursor(){
        //调用一个已有的包结构,如:

//      CREATE OR REPLACE
//      PACKAGE MYPACKAGE AS --PACKAGE定义包的关键字
//          -- 定义一个光标参数usrcursor,ref cursor是引用光标的意思
//          type usrcursor is ref cursor;
//          -- 调用存储过程
//          PROCEDURE QUERYUSERLIST(dno IN NUMBER, usrList OUT usrcursor);
//      END MYPACKAGE;

        //存储函数写法,最前面问号表示返回值
        String sql = "{call MYPACKAGE.QUERYUSERLIST(?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        ResultSet rs = null;

        try {
            //1.获取数据库连接
            conn = JDBCUtils.getConnection();

            //2.传入sql,并通过连接获取一个statement
            call = conn.prepareCall(sql);

            //3.给传入参数赋值
            call.setInt(1, 1);
            //对于返回值,需要申明类型.这里为游标类型
            call.registerOutParameter(2, OracleTypes.CURSOR);

            //4.执行调用
            call.execute();

            //5.取出结果,这里使用CallableStatement接口的实现类
            rs = ((OracleCallableStatement)call).getCursor(2);

            //6.打印结果
            System.out.println("ID" + "\t" + "NAME" + "\t" + "AGE" + "\t" + "MONEY" + "\t" + "DATE" + "\t" + "BONUS" +"\t" + "JOB");
            while(rs.next()){
                int id = rs.getInt("ID");
                String name = rs.getString("NAME");
                int age = rs.getInt("AGE");
                double money = rs.getDouble("MONEY");
                String date = rs.getString("DATE");
                double bonus = rs.getDouble("BONUS");
                String job = rs.getString("JOB");
                System.out.println(id + "\t" + name + "\t" + age + "\t" + money + "\t" + date + "\t" + bonus +"\t" + job);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, null);
        }
    }
}

运行结果

相关推荐
云和恩墨21 分钟前
云计算、AI与国产化浪潮下DBA职业之路风云变幻,如何谋破局启新途?
数据库·人工智能·云计算·dba
明月看潮生1 小时前
青少年编程与数学 02-007 PostgreSQL数据库应用 11课题、视图的操作
数据库·青少年编程·postgresql·编程与数学
阿猿收手吧!1 小时前
【Redis】Redis入门以及什么是分布式系统{Redis引入+分布式系统介绍}
数据库·redis·缓存
奈葵1 小时前
Spring Boot/MVC
java·数据库·spring boot
leegong231111 小时前
Oracle、PostgreSQL该学哪一个?
数据库·postgresql·oracle
中东大鹅1 小时前
MongoDB基本操作
数据库·分布式·mongodb·hbase
夜光小兔纸2 小时前
Oracle 普通用户连接hang住处理方法
运维·数据库·oracle
兩尛3 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库
web2u3 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
Elastic 中国社区官方博客4 小时前
使用 Elasticsearch 导航检索增强生成图表
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索