Oracle 19c Concepts(08):Server-Side Programming: PL/SQL and Java

服务器端编程介绍

在 SQL 之类的非过程化语言中,指定的是要操作的数据集, 而不是要执行什么操作或该操作应如何执行。

在过程化语言程序中, 大多数语句的执行取决于其前面或后面的语句及控制结构,如循环或条件分支, 而这在 SQL 中是不可用的。为演示过程化语言与非过程化语言之间的区别,假设如下的 SQL 语句查询 employees 表:
SELECT employee_id, department_id, last_name, salary FROM employees;

前面的语句只是请求数据,但并不会在数据上应用逻辑。但是,假设您希望应用程序能够确定在数据集中的每个员工是否应该基于工资和部门绩效来提高待遇。 加薪的必要条件是, 在过去五年中, 加薪没有超过三次以上的雇员。如果要加薪, 应用程序必须调整工资并发送电子邮件给经理 ;否则,该应用程序必须更新一份报告。

问题是, 需要条件逻辑和程序流控制的过程化数据库应用程序, 如何才能使用 SQL。基本的开发方法如下所示:

  • 使用客户端编程, 将 SQL 语句嵌入到由过程化语言如 C、 C++、 或 Java 等编写的应用程序中

    可以将 SQL 语句置入到源代码中,并在编译之前将其提交给预编译器或 Java 翻译器。或者,您可以消除预编译步骤,而使用如 Java 数据库连接 (JDBC) 或 Oracle 调用接口 (OCI) 这样的 API , 使应用程序能够与数据库进行交互。

  • 使用服务器端编程,开发驻留在数据库中的数据逻辑

    应用程序可以显式地调用以 PL/SQL (发音为 P L sequel) 或 Java 编写的存储子程序(过程和函数)。您还可以创建一个触发器,它是一个存储在数据库中的命名程序单元, 在响应指定的事件时调用。

本章介绍了第二种方法。服务器端编程的主要好处, 是内置于数据库的功能可以在任意位置部署。 由数据库(而不是由应用程序) 确定在给定的操作系统上执行任务的最佳方式。此外, 通过在服务器上集中应用程序处理, 子程序增加可伸缩性, 使客户端能够重用代码。 由于子程序调用快速而高效,仅仅一个调用就可以启动一个计算密集型的存储子程序, 减少了网络流量。

在 Oracle 数据库中可以使用以下语言来存储数据逻辑:

  • PL/SQL

    PL/SQL 是 Oracle 数据库针对 SQL 的过程化扩展。 PL/SQL 与数据库集成, 支持所有 Oracle SQL 语句、 函数、 和数据类型。 由数据库 API 编写的应用程序, 可以调用 PL/SQL 存储子程序,或发送 PL/SQL 代码块到数据库以被执行。

  • Java

    Oracle 数据库还对开发、 存储、 和部署 Java 应用程序提供支持。Java 存储子程序在数据库中运行, 并独立于在中间层上运行的程序。 Java 存储子程序使用与 PL/SQL 类似的运行模型来与 SQL 接口。

See Also:

PL/SQL 概述

PL/SQL 提供了一种服务器端的, 存储的过程化语言,它易于使用并与 SQL无缝集成, 而且健壮、可移植、 且安全。您可以使用被称为 PL/SQL 程序单元的过程化模式对象,来访问和操作数据库数据。

PL/SQL 程序单位一般分为如下几类:

  • PL/SQL 子程序,它存储在数据库中,可以从应用程序按名称调用。当你创建一个子程序时,数据库解析该子程序,并将其已解析的表示形式存储在数据库中。您可以将一个子程序声明为一个过程或函数。

  • PL/SQL 匿名块,它出现在您的应用程序中, 没有名字,也不存储在数据库中。在很多的应用程序中 , PL/SQL 块可以出现在任何 SQL 语句可以出现的地方。

Oracle SQL Developer 中嵌入了 PL/SQL 编译器和解释器, 给与开发人员一个在客户端和服务器上一致和有效的开发模式。此外, PL/SQL 存储过程可从多种数据库客户端调用,比如 Pro*C、 JDBC、 ODBC 、 或 OCI, 以及Oracle Reports 和 Oracle Forms 等。

另见:

PL/SQL 子程序

PL/SQL 子程序是一个命名的 PL/SQL 块, 允许调用方提供参数(可以是输入参数、 输出参数、 或输入输出参数值) 。

子程序解决某一具体问题, 或执行相关的任务,并作为模块化的、 可维护的数据库应用程序的构造块。子程序是一个过程或函数。过程和函数基本相同, 区别在于函数始终向调用者返回单个值, 而过程不返回任何值。在本章中的过程一词, 可能代表过程或函数。

另见:

PL/SQL 子程序的优势

服务器端编程相对于客户端编程有很多的好处。 

优势包括:

  • 提高性能

    • 相比发出一个个 SQL 语句,或将整个 PL/SQL 块的文本发送到数据库中, 应用程序必须通过网络发送的信息的量是很小的,因为信息只发送一次,并随后在使用时才调用它。

    • 已编译形式的过程在数据库中是随时可用的, 所以在执行时不需要编译。

    • 如果该过程已在 SGA 的共享池中,则数据库不需要从磁盘检索它, 就可以立即开始执行。

  • 内存分配

    因为存储过程利用了数据库的共享内存功能,由对多个用户执行的同一过程,数据库只须将该过程加载一次到内存中。 在多个用户间共享代码,使应用程序对数据库内存的要求大幅减少。

  • 提高生产率

    存储过程增加了开发生产率。通过围绕一组通用的过程来设计应用程序,您可以避免冗余编码。 例如,您可以编写过程,以处理 employees 表中的行。任何应用程序可以调用这些过程,而无需重写 SQL 语句。如果数据管理方法改变了,则只须修改过程, 而不必修改使用过程的应用程序。

    存储过程也许是实现代码重用的最好方式。 因为连接到数据库的任何客户端应用程序,无论它是用何种语言编写的, 都可以调用存储过程, 存储过程提供了在所有环境中最大的代码重用。

  • 完整性

    存储过程能改进您的应用程序完整性和一致性。通过围绕一组公用的过程开发应用程序,您可以减少编码错误的可能性。

    例如,您可以测试一个子程序, 来保证它将返回一个准确的结果, 验证后, 可以在任意数量的应用程序中重用它, 而无需重新测试。如果该过程所引用的数据结构改变了,则只须重新编译该过程。调用该过程的应用程序不需要修改。

  • 定义者权限程序的安全性

    存储过程有助于强制执行数据安全。 定义者权限过程按其所有者的权限来执行,而不是按当前用户的权限。因此,您可以通过让用户只通过按定义者权限运行的过程和函数来访问数据,以限制用户执行的数据库操作。

    例如,您可以只授予用户访问更新表的过程的权限, 而不授予其访问表本身的权限。当用户调用该过程时,它按其所有者的权限运行。只有权运行该过程(但对其基础表不具有查询、 更新、 或删除权限)的用户可以调用该过程,但不能以除此之外的任何其他方式操作表数据。

  • 继承权限与调用者权限过程的模式上下文

    调用者权限过程以当前用户的权限在当前用户的模式中执行。换句话说, 调用者权限过程不依赖于特定用户或模式。 调用者权限过程调用使应用程序开发人员更易于集中处理应用程序逻辑, 但是其基础数据可能会分散到多个用户模式。

    例如, 一个 hr_manager 用户可以运行更新过程来更新在 hr.employees 表中的薪水,而一个 hr_clerk 用户运行相同的过程却只能更新地址数据。

另见:

PL/SQL 子程序的创建

独立存储的子程序是 在模式级别使用 CREATE PROCEDURE 或 CREATE FUNCTION 语句创建的一个子程序。在一个包中定义的子程序称为包子程序, 并被认为是包的一部分。 

数据库将子程序作为模式对象存储在数据字典中。每个子程序有一个规范,包括一些参数说明和一个程序体。

例 8-1 PL/SQL 存储过程

这个示例显示了创建独立 PL/SQL 过程 hire_employees 的语句的一部分。该过程向 employees 表中插入一行。
CREATE PROCEDURE hire_employees 
  (p_last_name VARCHAR2, p_job_id VARCHAR2, p_manager_id NUMBER, 
   p_hire_date DATE, p_salary NUMBER, p_commission_pct NUMBER, 
   p_department_id NUMBER)
IS 
BEGIN
.
.
.
  INSERT INTO employees (employee_id, last_name, job_id, manager_id, hire_date, 
    salary, commission_pct, department_id)
  VALUES (emp_sequence.NEXTVAL, p_last_name, p_job_id, p_manager_id, 
          p_hire_date, p_salary, p_commission_pct, p_department_id);
.
.
.
END;

另见:

PL/SQL 子程序的执行

用户可以以多种方式交互式地执行子程序。

选项是:

  • 使用 Oracle 工具, 如 SQL * Plus 或 SQL Developer

  • 在一个数据库应用程序(如 Oracle Forms 或预编译器应用程序) 代码中显式调用它。 

  • 在另一个过程或触发器的代码中显式调用它

下图显示了调用 hire_employees 的不同数据库应用程序。

另外,有权限的用户可以使用 Oracle 企业管理器或 SQL*Plus 使用如下的语句来运行 hire_employees 过程:

EXECUTE hire_employees ('TSMITH', 'CLERK', 1037, SYSDATE, 500, NULL, 20);

前面的语句在 employees 表中为 TSMITH 插入一条新记录。

存储过程依赖于在其主体中引用的对象。数据库自动跟踪和管理这些依赖。例如, hire_employees 过程引用了 employees 表, 如果您更改了此表的定义且影响到该过程,则该过程必须重新编译以验证它仍然工作正常。通常,数据库自动管理这种依赖关系。

另见:

PL/SQL 包
PL/SQL 包是一组相关子程序, 与其使用的游标和变量一起, 作为一个单元存储在数据库中。打包的子程序可以由应用程序或用户显式调用。       

Oracle 数据库包括许多原厂商提供的软件包,扩展了数据库的功能,并提供对 SQL 功能的 PL/SQL 访问。例如, UTL_HTTP 包使你能够从 PL/SQL 和 SQL 访问发出 HTTP 调用,以访问在因特网上的数据,或调用 Oracle Web Server 模块。 你可以在创建应用程序时使用这些原厂商提供的软件包,或当创建您自己的存储过程时将其作为思维来源。

PL/SQL 包的优势

PL/SQL包为应用程序开发人员提供了许多优势。

优势包括:

  • 封装

    包使您能够封装或分组存储过程、 变量、 数据类型等等, 并存储为命名单元。封装为开发过程提供了更好地组织形式,也提供了更大的灵活性。您可以创建规范,只引用公共过程而无需实际创建包体。封装简化了权限管理。 对包进行授权, 使其结构元素可以由被授权者访问。

  • 数据安全

    定义包的方法使您能够指定哪些变量、 游标过程是公共的或私有的。公有的意味着包的用户可以直接访问它。私有的意味着它对于包的用户够是隐藏的。

    例如,一个包可能包含 10 个过程。您可以定义包,使其只有三个过程是公共的,且可由包的用户执行。 其余的过程是私有的, 且只可由包中的过程访问。不要将公有或私有的包变量与对 PUBLIC 的授权相混淆。

  • 更好的性能

    当包中的某个过程第一次被调用时, 整个包被加载到内存中。 这种加载在一个操作中完成,不同于加载多个独立过程需要分别加载。 当调用到包中的相关过程时,只需直接运行内存中的已编译代码, 而无需额外磁盘 I/O。

    包体可以被替换并重新编译,而不会影响其规范。 这样,(总是通过该规范)引用包的结构元素的模式对象不需要被重新编译,除非包规范也被替换了。通过使用包,可以将不必要的重新编译减小到最少,结果对数据库的总体性能影响就比较小。

PL/SQL 包的创建

分两个部分创建包: 包规范和包体。包规范声明包的所有公有结构, 而包体定义包的所有(公有的和私有的) 结构。

如下示例显示了用于创建 employees_management 包规范的语句的一部分, 封装了用于管理雇员的几个子程序。包的每个部分用不同的语句创建。

CREATE PACKAGE employees_management AS 
FUNCTION hire_employees (last_name VARCHAR2, job_id VARCHAR2, manager_id NUMBER, 
salary NUMBER, commission_pct NUMBER, department_id NUMBER) RETURN NUMBER; 
PROCEDURE fire_employees(employee_id NUMBER); 
PROCEDURE salary_raise(employee_id NUMBER, salary_incr NUMBER); 
.
.
.
no_sal EXCEPTION;
END employees_management;

此规范声明了函数 hire_employees、 过程 fire_employees 和 salary_raise、 以及异常 no_sal。所有这些公有程序对象, 对有权访问该包的用户都是可用的。

CREATE PACKAGE BODY 命令定义在规范中声明的对象。包体必须在和包相同的模式中创建。在创建包之后,您就可以开发应用程序,并在其中调用这些公共过程或函数,或抛出包中的任何公共例外。

另见:

Oracle Database PL/SQL Language Reference了解 CREATE PACKAGE 命令

PL/SQL 包子程序的执行

您可以从数据库触发器、 存储子程序、 3GL 应用程序、 和 Oracle 工具引用包内容。

下图显示了数据库应用程序调用 employees_management 包中的过程和函数。

数据库应用程序在必要时显式调用包内的过程。 在取得对 employees_management 包的授权后,用户可以显式运行其中的任何过程。例如, SQL * Plus 可以发出以下语句来运行 hire_employees 包过程:

EXECUTE employees_management.hire_employees ('TSMITH', 'CLERK', 1037, SYSDATE, 500, NULL, 20);

另见:

PL/SQL 匿名块

匿名块是一个未命名的、 非持久化的 PL/SQL 单元。

匿名块的典型用途包括:

  • 启动调用子程序和包结构元素

  • 隔离例外处理

  • 通过在其他 PL/SQL 块中嵌套代码来管理控制

匿名块没有存储子程序的代码重用优势。表 8-1 总结了这两类程序单元的之间的差异。

表 8-1 匿名块和子程序的差异

该 PL/SQL 单元是否... 匿名块 子程序

指定了一个名字?

每次使用时都要重新编译?

存储在数据库中?

可由其它应用程序调用?

能返回绑定变量值?

能返回函数值?

能接受参数?

匿名块包括一个可选的声明性部分、 一个可执行部分、 和一个或多个可选的异常处理程序。下面的示例匿名块将一个雇员的姓氏选出并存入到一个变量,并输出该姓氏:

DECLARE
  v_lname VARCHAR2(25);
BEGIN
  SELECT last_name 
    INTO v_lname
  FROM employees
  WHERE employee_id = 101;
  DBMS_OUTPUT.PUT_LINE('Employee last name is '||v_lname);
END;

Oracle 数据库编译 PL/SQL 块,并将其放在 SGA 的共享池中,但它不会在数据库中存储其源代码或已编译版本, 以便在当前运行实例之外能重复使
用。与触发器不同的是,一个匿名块在它每次加载到内存中都要被重新编译。共享 SQL 允许匿名 PL/SQL 块可以在共享池中被共享和重用,直到它们被从共享池中冲掉。

另见:

Oracle Database Development Guide了解 PL/SQL 匿名块的更多信息

PL/SQL 语言结构

PL/SQL 块可以包括各种不同的 PL/SQL 语言结构。

这些结构包括:

  • 变量和常量

    您可以在过程、 函数、 或包内声明这些结构。 在你需要在 SQL 或 PL/SQL 语句中捕获或提供一个值时, 您可以使用变量或常量。

  • 游标

    您可以在过程、 函数、 或包中显式声明一个游标,以便对 Oracle 数据库数据进行面向记录的处理。 PL/SQL 引擎也可以隐式声明游标。

  • 例外

    PL/SQL 允许您显式处理例外,即在 PL/SQL 代码的处理过程中抛出的内部的或用户定义的错误。

PL/SQL 可以运行动态 SQL 语句, 其完整的文本直到运行时才知道。动态SQL 语句被存储为字符串,可以是手工输入的或在运行时由其程序生成。这种技术使您能够创建通用过程。例如, 您可以创建一个过程,用于操作一个直到运行时才知道其名称的表。

另见:

PL/SQL 集合和记录

许多编程技术都使用集合类型, 如数组、 袋、 列表、 嵌套表、 集、 和树等。 

为在数据库应用程序中支持这些技术, PL/SQL 提供了 TABLE 和 VARRAY 数据类型,使您能够声明关联数组、 嵌套表、 和变长数组。

集合

集合是一组有序的、 具有相同类型的元素。

每个元素都有一个唯一的下标,以确定其在该集合中的位置。若要创建一个集合,你得首先定义一个集合类型,然后声明一个该类型的变量。

集合的工作原理与第三代编程语言中的数组类似。此外,集合可以作为参数传递。所以,你可以使用他们在数据库中的表、 客户端应用程序、 和存储子程序等之间,移入或移出数据列。

记录

记录是一种复合变量, 可以存储不同类型的的数据值, 类似于 C、 C++、或 Java 中的结构类型。记录可用于容纳来自多个表行,或多个表行中的某些列的数据。

假设有某个雇员的相关数据,如名字、 工资、和雇用日期。这些项的类型不同,但逻辑上相关。 记录为每个项都包含了一个字段, 让你可以视其为一个逻辑单元。

您可以使用 %ROWTYPE 属性来声明一条记录,以表示一个表行或从游标读取的行。 对用户定义的记录,您可以声明自己的域。

另见:

Oracle Database PL/SQL Language Reference学习如何使用PL/SQL记录

PL/SQL 如何运行

PL/SQL 支持本地执行和解释执行。

对解释执行, PL/SQL 源代码被编译为所谓的字节表示形式,来实现为 Oracle 数据库一部分的一个可移植虚拟机运行。 

本机执行为计算密集型程序单元提供了最佳性能,在这种情况下,PL/SQL 程序源代码被直接编译为给定平台的对象代码。该对象代码被链接到 Oracle 数据库中。

PL/SQL 引擎定义、 编译、 和运行 PL/SQL 程序单元。该引擎是包括数据库的许多 Oracle 产品中的一个特殊的组件。虽然许多 Oracle 产品都有 PL/SQL 组件,但本节专门介绍可以存储在数据库中,并使用 Oracle 数据库 PL/SQL 引擎处理的程序单元。每个Oracle工具的文档都描述了其PL/SQL功能。

下图说明了包含在 Oracle 数据库中的 PL/SQL 引擎。

程序单元存储在数据库中。当应用程序调用存储过程时,数据库会将已编译的程序单元加载到系统全局区(SGA) 的共享池中。PL/SQL 执行器和 SQL 语句执行器共同处理过程中的语句。

您可以从另一个 PL/SQL 块调用存储过程, 这可以是一个匿名块或另一个存储过程。 例如,您可以从 Oracle Forms 调用存储过程。

在 Oracle 数据库上执行的 PL/SQL 过程,可以调用一个由 C 编程语言编写的并存储在一个共享库中的外部过程或函数。 C 例程在一个不同于数据库的单独的地址空间中运行。

另见:

Oracle 数据库中的 Java 概述

Java 已成为面向对象的编程语言的选择。

Java 包括以下特性:

  • Java 虚拟机 (JVM), 提供了平台独立性的基础

  • 自动化的存储管理技术,例如垃圾回收

  • 从 C 语言借鉴的强类型语言语法

数据库为 Java 程序提供了一个动态的数据处理引擎,支持复杂的查询和数据的多个视图。客户端请求被装配为数据查询,以备立即处理。查询结果是动态生成的。

Java 和 Oracle 数据库的结合, 可帮助您创建基于组件的、 以网络为中心的应用程序,并可以轻松地随业务需求的变化而变化。 此外, 您可以将应用程序和数据存储, 从桌面移到智能网络和以网络为中心的服务器上。更重要的是,您可以从任何客户端设备来访问这些应用程序和数据存储。

下图显示了传统的两层客户端/服务器配置, 客户端使用与调用 PL/SQL子程序相同的方式, 来调用 Java 存储过程。

另见:

Oracle Database 2 Day + Java Developer's Guide关于在 Oracle 数据库中使用Java 的简介

Java 虚拟机 (JVM) 的概述

JVM 是一个运行已编译 Java 代码的虚拟处理器。 

Java 源代码被编译为称为字节码的、 独立于平台的低级计算机指令。 Java 字节码通过 JVM 解释为平台依赖的操作。

Oracle JVM 概述

Oracle JVM 是一个完整的、 符合 Java2 标准的环境,用于运行纯 Java 应用程序。

Oracle JVM与 JLS 和 JVM 规范兼容。它支持 Java 的标准二进制格式和API。此外, Oracle 数据库遵从标准的 Java 语言语义, 包括在运行时动态加载类。

下图说明了 Oracle Java 应用程序如何驻留在 Oracle JVM 中的 Java 核心类库的基础上。 因为 Oracle Java 支持系统位于数据库内, 所以 JVM 是与数据库的库文件交互, 而不是直接与操作系统交互的。

与其他的 Java 环境不同, Oracle JVM 是内嵌于数据库中的。 Oracle JVM 和典型的客户端 JVM 之间存在一些重要的差异。例如, 在一个标准的 Java 环境中, 您通过在命令行上向解释器发出下面的命令来运行 Java 应用程序, 其中 classname 是你希望 JVM 首先解释的类名:

java classname

前面的命令导致应用程序在您的操作系统上的某个进程内运行。但是,如果您不使用命令行接口,则你必须加载应用程序到数据库、 发布该接口、 然后在数据库数据字典中运行这个应用程序。

另见:

《Oracle Database Java Developer’s Guide关于 Oracle JVM 和典型的客户端 JVM 之间的其他差异的描述

Oracle JVM 的主要组件

Oracle JVM 通过共享其内存堆, 并直接访问其关联数据,与数据库内核运行在同一个进程空间。这种设计优化了内存使用并提高了吞吐量。

Oracle JVM 提供了一个 Java 对象的运行时环境。它完全支持 Java 数据结构、 方法调度、 异常处理、 和语言级线程。它还支持所有核心 Java 类库,包括 java.lang、 java.io、 java.net、 java.math、 和 java.util。

下图显示 Oracle JVM 的主要组件。

Oracle JVM 在数据库模式中嵌入标准 Java 命名空间。此特性使 Java 程序能够访问整个企业范围内的数据库和应用程序服务器中的 Java 对象。

另外, Oracle JVM 与可扩展的、 共享的数据库内存体系结构紧密集成。Java 程序有效地使用调用、 会话、 和对象生存期, 而无需用户干预。 这样, Oracle JVM 和中间层 Java 业务对象, 即使处于长会话状态,仍然具有可扩展性。

另见:

Oracle Database Java Developer’s Guide关于 Oracle JVM 的主要部件的描述

Java 编程环境

Oracle 为企业应用程序开发人员提供了一个端到端的 Java 解决方案,用于创建、 部署、 并管理 Java 应用程序。

该解决方案包含客户端和服务器端的编程接口、 支持 Java 开发的工具、 和与 Oracle 数据库集成的 Java 虚拟机。所有这些产品都与 Java 标准兼容。

Java 编程环境还包括以下额外功能:

  •  Java 存储过程类似于 Java,经常和 PL/SQL 配合使用。 Java 存储过程与 PL/SQL 紧密集成。您可以从 PL/SQL 包调用 Java 存储过程,或从 Java 存储过程调用 PL/SQL 存储过程。

  • 用于访问 SQL 数据的 SQLJ 和 JDBC 编程接口。

  • 用于辅助开发、 加载、 和管理 Java 类的工具和脚本。

Java 存储过程

Java 存储过程是一个发布到 SQL、 并存储在数据库中的 Java 方法。 

与 PL/SQL 子程序类似, Java 存储过程可以从 SQL * Plus 之类的产品直接调用,或从一个触发器间接调用。您可以从任何 Oracle Net 客户端访问它——如 OCI、 预编译器、 或 JDBC 等。

若要发布 Java 方法,你需要编写调用规范,以映射 Java 方法名称、 参数类型、 及其与 SQL 对应的返回类型。当被客户端应用程序调用时, Java 存储过程可以接受参数、 引用 Java 类、 并返回 Java 结果值。

应用程序通过引用调用规范的名称来调用 Java 方法。运行时系统在 Oracle 数据字典中查找调用规范的定义,并运行相应的 Java 方法。

此外, 您可以使用 Java 来开发功能强大的独立于 PL/SQL 的程序。 Oracle 数据库提供了一个与 Java 编程语言和 JVM 完全兼容的实现。

另见:

Oracle Database Java Developer’s Guide说明了如何在 Java 中编写存储过程、 如何从 PL/SQL 访问它们、 以及如何从 Java 访问 PL/SQL 功能

Java 和 PL/SQL 集成

您可以从 Java 调用现有 PL/SQL 程序, 或从 PL/SQL 调用 Java 程序。此解决方案能保护和利用您的 PL/SQL 和 Java 代码。

Oracle 数据库提供了两种不同的方法从 Java、 JDBC、 和 SQLJ 访问 SQL 数据。JDBC在客户机和服务器上都可用,而SQLJ仅在客户机上可用。
JDBC 驱动程序

JDBC 是一种数据库访问协议,使您能够连接到数据库,并对数据库运行 SQL 语句和查询。

核心 Java 类库仅提供一个 JDBC API, java.sql。但是,JDBC 被设计为允许供应商针对特定数据库提供所需的特殊的驱动程序。Oracle 提供了下表中所示的不同 JDBC 驱动程序。

表 8-2 JDBC 驱动程序

驱动程序 描述

JDBC Thin 驱动程序

你可以使用 JDBC Thin 驱动程序来编写纯 Java 应用程序和访问 Oracle SQL 数据的小程序。 JDBC 瘦驱动程序特别适合基于 Web 的应用程序和小程序,因为您可以从 Web 页动态地下载它,与下载任何其他 Java 小程序类似。

JDBC OCI 驱动程序

JDBC OCI 驱动程序访问特定于 Oracle 的本地代码(即: 非 Java 的代码) 和客户端或中间层上的库,相比 JDBC 瘦驱动程序, 提供了性能的提升,其代价是尺寸明显变大,且需要客户端安装。

JDBC 服务器端内部驱动程序

当 Java 代码在服务器上运行时, Oracle 数据库使用服务器端内部驱动程序。它允许 Java 应用程序在服务器上的 Oracle JVM 中运行,以访问在本地定义的数据,即与 JDBC 在同一系统上,且在同一进程中的数据。 由于具有使用底层的 Oracle RDBMS 库的能力,而没有 Java 代码和 SQL 数据之间网络连接的开销,其性能大大提高。通过在服务器上支持相同的 Java-SQL 接口, Oracle 数据库不需要你重新编码代码来部署它。

触发器概述

数据库触发器是一个已编译的存储程序单元,使用 PL/SQL 或 Java 编写,Oracle数据库在某些情况下会自动调用(“触发”)。

每当发生下列操作之一时,触发器就会触发:

  1. 由任何用户在一个特定的表或视图上,发出 DML 语句

    DML 语句修改模式对象中的数据。例如, 插入和删除行即是 DML 操作。

  2. 由任何用户或特定用户发出 DDL 语句

    DDL 语句定义模式对象。例如, 创建表并添加一列即是 DDL 操作。

  3. 数据库事件

    用户登录或注销、 错误、 和数据库启动或关闭, 是触发器可以调用的事件。

触发器是模式对象, 类似于子程序; 但其调用方法不同。子程序由用户、应用程序、 或触发器显式运行。 而触发器是在触发的事件发生时由数据库隐式调用的。

触发器的优势

正确使用触发器使您能够生成和部署更健壮、 更能有效使用数据库的应用程序。

您可以使用触发器:

  • 自动生成派生列中的值

  • 防止无效事务

  • 提供审计和事件日志记录

  • 记录有关访问表的信息

您可以使用触发器强制执行对所有客户端应用程序都适用的底层业务规则。例如, 几个应用程序都可能要访问 employees 表。如果此表上的某个触发器可以确保插入数据的格式,则不需要在每个客户端中重复这种业务逻辑。因为该触发器不能被应用程序绕过, 触发器中的业务逻辑会被自动应用。

您可以使用触发器或完整性约束, 来定义和执行任何类型的完整性规则。但是, 当不能使用一个完整性约束来定义时, Oracle 强烈建议您仅使用触发器来强制执行这种复杂的业务规则。

过度使用的触发器, 可能导致复杂的相互依赖关系, 在一个大的应用程序中可能很难维护。例如, 当调用触发器时,在其触发器操作中的 SQL 语句可能会触发其他触发器,导致级联触发, 且产生意想不到的结果。

另见:

触发器类型

触发器可以根据其调用方式和他们所执行的操作类型进行分类。

Oracle 数据库支持以下类型的触发器:

  • 行触发器

    行触发器在表每次受到触发语句影响时触发。例如。 如果一个语句更新多个行, 则行触发器为受 UPDATE 影响的每一行触发一次。如果触发语句未影响任何行,则行触发器不会运行。如果触发器操作中的代码依赖于触发语句提供的数据或受影响的行,则行触发器是有用的。

  • 语句触发器

    语句触发器针对触发语句只触发一次, 而无论受触发语句影响的行数。例如, 如果一个语句从表中删除 100 行, 语句级 DELETE 触发器仅触发一次。 如果触发器操作中的代码并不依赖触发语句所提供的数据或受影响的行,则语句触发器是有用的。

  • INSTEAD OF 触发器

    数据库会触发 INSTEAD OF 触发器,而不是执行触发语句。这些触发器可用于透明地修改不能直接通过 DML 语句修改的视图。

  • 事件触发器

    您可以使用触发器将有关数据库事件的信息发布到订阅服务器。事件触发器可分为以下几类:

    • 系统事件触发器可以由诸如数据库实例启动和关闭或错误消息之类的事件引起。

    • 用户事件触发器会因为与用户登录和注销、 DDL 语句、 和DML 语句等相关的事件而激发。

触发时间

您可以定义触发时间 —— 触发器操作是要在触发语句之前运行,还是之后运行。 

简单触发器是一个在表上的触发器, 使您能够仅为以下时间点之一指定操作:

  • 触发语句之前

  • 在受触发语句影响的每行之前

  • 在受触发语句影响的每行之后

  • 触发语句之后

对语句和行触发器, BEFORE 触发器可以增强安全性,并在对数据库进行更改之前启用业务规则。 而 AFTER 触发器则对日志记录操作很理想。

复杂触发器可以在多个时间点触发。 复杂触发器提供一种编程方法,使你可以在多个不同的时间点, 对共享的公共数据实施操作。

另见:

Oracle Database PL/SQL Language Reference了解复杂触发器

创建触发器

使用 CREATE TRIGGER 语句创建或替换数据库触发器。 

PL/SQL 触发器具有以下的一般语法形式:

CREATE TRIGGER trigger_name
  triggering_statement
  [trigger_restriction]
BEGIN
  triggered_action
END;

PL/SQL 触发器具有以下基本组件:

  • 触发器名称

    在相同的模式中,触发器的名称必须是唯一的。 例如,名称可能是 part_reorder_trigger。

  • 触发事件或语句

    触发事件或语句是导致触发器被调用的 SQL 语句、 数据库事件、 或用户事件。例如, 用户更新一个表。

  • 触发器限制

    触发器限制指定一个布尔表达式,其值必须为 true , 触发器才会触发。 例如,某触发器仅当可用部件的数量不足订购量时才触发。

  • 触发的操作

    触发的操作是包含 SQL 语句和代码的过程,当发出一个触发语句、且触发器限制的计算结果为 true 时将被运行。例如, 用户向待办订单表中插入行。

另见:

例: CREATE TRIGGER 语句

这个示例创建一个触发器,当在一个行项表上执行INSERT、UPDATE或DELETE语句时触发该触发器。

假设您使用如下语句创建 orders 表和 lineitems 表,orders 表为每个唯一订单包含一行,而 lineitems 表为所订单中的每个项目包含一行。

CREATE TABLE orders
( order_id NUMBER PRIMARY KEY,
  /* other attributes */
  line_items_count NUMBER DEFAULT 0 );

CREATE TABLE lineitems
( order_id REFERENCES orders,
  seq_no   NUMBER,
  /* other attributes */
  CONSTRAINT lineitems PRIMARY KEY(order_id,seq_no) );

如下语句显示一个简单触发器, 自动更新 orders 表的订单中的项目数:

CREATE OR REPLACE TRIGGER lineitems_trigger
  AFTER INSERT OR UPDATE OR DELETE ON lineitems 
  FOR EACH ROW
BEGIN
  IF (INSERTING OR UPDATING)
  THEN
    UPDATE orders SET line_items_count = NVL(line_items_count,0)+1
    WHERE order_id = :new.order_id;
  END IF;
  IF (DELETING OR UPDATING)
  THEN
    UPDATE orders SET line_items_count = NVL(line_items_count,0)-1
    WHERE order_id = :old.order_id;
  END IF;
END;
/

在lineitems_trigger中 ,触发语句是一个在 lineitems 表上的 INSERT、UPDATE、 或 DELETE 操作。没有触发限制存在。触发器为更改的每一行调用一次。触发器对正在被触发语句影响的当前行, 具有访问其旧列值和新列值的权限。正在修改的表的每一列存在两个相关名字: 旧值 (:old)和新值 (:new)。如果在 lineitems 表中插入或更新了某订单行, 则在此操作后触发器会计算该订单中的项目数,并在 orders 表中更新计数值。

例: 调用行级触发器

在此场景中,客户发起两个订单,并从订单中添加和删除行项目。

该场景基于Example: CREATE trigger语句中创建的触发器。

表 8-3 行级触发器的场景

SQL 语句触发SQL语句描述
SQL> INSERT INTO orders
  (order_id) VALUES (78);
 
1 row created.
 

客户创建 ID 为 78 的订单。此时客户在该订单中没有订单项目。

因为未对 lineitems 表执行任何操作, 所以触发器不会调用。

SQL> INSERT INTO orders
  (order_id) VALUES (92);
 
1 row created. 
 

客户创建另一个 ID 为 92 的订单。 此时客户在该订单中没有订单项目。

因为未对 lineitems 表执行任何操作, 所以触发器不会调用。

SQL> INSERT INTO lineitems 
 (order_id, seq_no)
 VALUES (78,1);
 
1 row created. 
UPDATE orders 
  SET line_items_count = 
    NVL(NULL,0)+1
  WHERE order_id = 78;

客户将某订单项添加到订单 78。

INSERT 操作调用触发器。 被触发的语句将订单 78 的项目计数从 0 增加到 1。

SQL> INSERT INTO lineitems 
  (order_id, seq_no) 
  VALUES (78,2);
 
1 row created. 
UPDATE orders 
  SET line_items_count = 
    NVL(1,0)+1
  WHERE order_id = 78;

客户将另一个项目添加到订单 78。

INSERT 操作调用触发器。 被触发的语句将订单 78 的项目计数从 1 增加到 2。

SQL> SELECT * FROM orders;
 
 ORDER_ID LINE_ITEMS_COUNT
--------- ----------------
      78                2
      92                0
 

客户查询两个订单的状态。订单 78 包含两个项目。 而订单 92 不包含任何项目。

SQL> SELECT * FROM lineitems;
 
  ORDER_ID     SEQ_NO
---------- ----------
        78          1
        78          2
 

客户查询订单行项目的状态。每个项目都由订单 ID 和序列号唯一标识。

SQL> UPDATE lineitems 
  SET order_id = 92;
 
2 rows updated.
UPDATE orders 
  SET line_items_count = 
    NVL(NULL,0)+1
  WHERE order_id = 92;
 
UPDATE orders 
  SET line_items_count = 
    NVL(2,0)-1
  WHERE order_id = 78;
UPDATE orders 
  SET line_items_count = 
    NVL(1,0)+1
  WHERE order_id = 92;
 
UPDATE orders 
  SET line_items_count = 
    NVL(1,0)-1
  WHERE order_id = 78;

客户将订单 78 中的项目移到订单 92 中。

UPDATE 语句更改了 lineitems 表中的 2 行, 这将为每个行调用一次触发器。               

每次调用触发器时,在触发器中的两个 IF 条件都符合。第一个条件增加订单 92 的计数,而第二个条件减少订单 78 的计数。因此, 总共运行四个 UPDATE 语句。

SQL> SELECT * FROM orders;
 
 ORDER_ID LINE_ITEMS_COUNT
--------- ----------------
       78                0
       92                2
 

客户查询两个订单的状态。净影响是订单 92 的行项目计数已从 0 增至 2,而订单 78 的计数从 2 下降为 0。

SQL> SELECT * FROM lineitems;
 
  ORDER_ID     SEQ_NO
---------- ----------
        92          1
        92          2
 

客户查询订单行项目的状态。唯一标识每个项目的订单 ID 和序列号。

SQL> DELETE FROM lineitems;
 
2 rows deleted.
UPDATE orders 
  SET line_items_count = 
    NVL(2,0)-1
  WHERE order_id = 92;
 
UPDATE orders 
  SET line_items_count = 
    NVL(1,0)-1
  WHERE order_id = 92;

客户现在从所有订单中删除所有行项目。

DELETE 语句更改 lineitems 的表中的 2 行, 每个行会调用一次触发器。对于每个触发器调用,在触发器中只有一个 IF 条件得到满足。每次该条件从订单 92 将计数减 1。因此, 总共运行两个 UPDATE 语句。

SQL> SELECT * FROM orders;
 
 ORDER_ID LINE_ITEMS_COUNT
--------- ----------------
       78                0
       92                0
 
SQL> SELECT * FROM lineitems;
 
no rows selected
 

客户查询两个订单的状态。 订单都不包含任何行项目。

客户还查询订单行项目的状态。 也没有任何项目。

执行触发器

Oracle 数据库在内部使用与执行子程序相同的步骤来执行触发器。

唯一的细微区别在于,如果用户有权运行触发语句,则他或她有权触发该触发器。除了这个区别之外, 数据库使用与存储子程序相同的方式来验证和运行触发器。

另见:

Oracle Database PL/SQL Language Reference了解触发器执行的更多信息

触发器存储

与 PL/SQL 存储过程类似, Oracle 数据库将触发器以已编译形式存储在数据库模式中。

当 CREATE TRIGGER 语句提交后, 其编译后的 PL/SQL 代码被存储在数据库中,从共享池中移除该 PL/SQL 触发器源代码。

下图显示了一个数据库应用程序,其中的一个 SQL 语句隐式调用了 PL/SQL 触发器。触发器与其相关联的表是分开存储的

Java 触发器以与 PL/SQL 触发器相同的方式被存储在数据库中。但是, Java触发器使用 CALL 语句引用单独编译的 Java 代码。因此,创建一个 Java 触发器涉及到创建 Java 代码, 并创建引用此 Java 代码的触发器。

另见:

Oracle Database PL/SQL Language Reference了解编译和存储触发器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值