type
status
date
slug
summary
tags
category
icon
password
6.1 数据库服务器编程
6.1.1 数据库服务器编程简介
- 数据库服务器编程是指编写运行在数据库服务器端的应用程序
- 数据库服务程序包括存储过程、用户自定义函数、触发器、事件调度器、事务控制、异常处理、动态SQL和安全性管理等内容。
- 数据库服务器程序有利于实现复杂的数据操作和逻辑,提高数据库应用程序的性能和安全性。
- 数据库服务器编程使用特定的数据库编程语言(如PL/SQL、PL/pgSQL等)编写。
6.1.2. 数据库PL/pgSQL语言
- PL/pgSQL简介:PL/pgSQL(Procedural Language/PostgreSQL)是postgreSQL数据库的面向过程的编程语言。
- PL/pgSQL的主要语法特点
- PL/pgSQL代码由块组成,每个块包含声明、可执行和异常处理部分。
- PL/pgSQL支持多种数据类型,包括PostgreSQL的内置数据类型(如整数、浮点数、字符串等)和用户自定义数据类型。
- PL/pgSQL提供了丰富的控制结构,如条件语句(IF、CASE)、循环语句(FOR、WHILE、LOOP)和异常处理(BEGIN EXCEPTION … END)。
- PL/pgSQL允许开发者在代码中嵌入SQL语句,以便执行查询、插入、更新和删除等操作。
- PL/pgSQL支持创建用户自定义函数和存储过程。
- PL/pgSQL允许开发者创建触发器函数,用于在特定事件发生时自动执行。触发器函数可以用于实现数据完整性约束、审计日志等功能。
6.1.3 PL/pgSQL变量与类型声明
- PL/pgSQL变量声明
- 变量声明语法
如:
所以不要看上面的变量声明语法那么复杂,其实就是变量名+类型+象牙赋值,需要的时候就在变量类型的前后加上CONSTANT和NOT NULL
- PL/pgSQL特殊类型声明
- 声明行类型语法格式
- 声明记录类型语法格式
- 使用已定义变量来定义新变量语法格式
也就是定义行类型变量(就相当于是在定义数据库表中的一条元组的类型,很像一个结构体)
或者是声明一个记录类型变量,又或者是通过一个与定义变量的类型来定义一个新变量的类型
需要注意的是这里表名的后面是一个百分号加上一个ROWTYPE关键字
如:
这里就是将stuinfor变量的类型设置为student表的行类型(也就是一个stuinfor就代表了student表中的一条元组)
可以发现在PL/pgSQL中应该是专门给了记录类型一个关键字RECORD
如:
需要注意的是,这里使用的是%加上一个TYPE关键字。从这里就可以看出%在PL/pgSQL中应该是一个运算符,相当于是取出一个对象的某个属性,具体取出哪个属性是靠%后面提供的关键字实现的。所以在这里我就用这种方法来记这个语法格式了,就是对一个关系表对象而言,他有一个对象的属性就是行类型;而对一个变量而言,就有一个对象属性是他自己的类型
如:
6.1.4 PL/pgSQL控制语句
- IF语句:用于条件判断。根据条件的真假执行相应的代码块
- 所有的IF或者ELSEIF(这里ELSE和IF是连在一起的)都需要有一个THEN对应。
- IF结束之后需要有一个END IF
- 需要注意一下缩进格式
- IF中的布尔表达式使用的逻辑运算符跟SQL语句中是一样的,如AND、OR、NOT等
使用IF语句的例子如下:
需要注意的是:
- CASE语句:用于多条件判断。根据条件的真假执行相应的代码块
- 每一种CASE都需要有一个WHEN关键字
- 最后一个CASE需要使用ELSE关键字
- 每一个CASE中需要像IF一样在布尔表达式的后面加上一个THEN关键字
- 在CASE语句的最后需要有END CASE
- 注意缩进格式
使用CASE语句的例子如下:
需要注意的是:
- LOOP语句:用于无条件循环。循环内部可以使用EXIT或RETURN语句跳出循环。
- 这里的RAISE NOTICE是一个格式化输出关键字,后面提供一个格式化字符串,用%在格式化字符串中代指后面提供的变量
- 在LOOP结束的时候也需要有END LOOP
- 在LOOP中使用EXIT就相当于break的效果
使用LOOP语句的例子如下:
需要注意的点为:
从这里也能看出来一个PL/pgSQL程序的整体过程是什么样的了。首先是通过DECLARE关键字来表明接下来的块是变量声明块;然后通过BEGIN关键字表示进入代码块,最后通过END关键字来表示代码块结束
- WHILE语句:用于条件循环。当条件为真时,执行循环体。
- WHILE循环本质上也是一个LOOP循环,需要在循环的布尔表达式后面跟上一个LOOP关键字。
- 在WHILE循环的末尾也需要有END LOOP
使用WHILE语句的例子如下:
需要注意的点为:
- FOR语句:用于遍历范围或记录集。可以使用整数范围或查询结果作为循环变量。
- FOR循环常用来遍历一个范围,所以在布尔表达式的位置一般提供的是一个临时变量+IN关键字+范围的形式(这种提供布尔表达式的形式和rust很像,甚至是一模一样)。
- FOR循环本质上也是一个LOOP循环,需要循环的布尔表达式后面跟上一个LOOP关键字
- 在FOR循环的末尾需要有END LOOP
使用FOR语句的例子如下:
需要注意的点为:
- 异常处理:用于捕获和处理异常。可在BEGIN…EXCEPTION…END块中定义异常处理
- 异常处理需要在一个额外的BEGIN…END块中使用
- 在可能出现异常的语句后面紧跟着进行异常处理
- 使用EXCEPTION WHEN关键字来捕获特定的异常(这里的when就是一个后置条件判断)
- 因为这里使用到了WHEN关键字,它后面实际上也是一个布尔表达式,所以在布尔表达式之后也需要加上THEN关键字
需要注意的点是:
6.1.5 PL/pgSQL函数编程
- PL/pgSQL函数编程
- 使用PL/pgSQL语言可以编写自定义函数;
- 函数是在数据库服务器上定义的一种特殊类型的程序,用于执行特定功能并返回一个值;
- 用户自定义函数可以在SQL查询中像内置函数一样使用,提高了查询的灵活性和可读性;
- 与C语言的函数一样,具有函数名、输入参数和输出参数
- 数据库函数编程的优点
- 通过PL/pgSQL编写函数存储在数据库服务器,既具有SQL语言简单易用的优点,又具有处理复杂逻辑过程的能力;
- 由于存储数据库服务器,因此调用执行数据库服务器的函数,能消除客户端和服务器之间的额外通信,客户端不需要的中间结果不必由服务器向客户端传送;
- 能够减少冗余代码,提高代码的可重用性。(并不是像ppt里面说的什么减少编译。减少编译的是存储过程)
- 创建数据库函数的语法
- name:要创建的函数名;
- OR REPLACE :覆盖同名的函数;
- argmode:函数参数的模式可以为IN、OUT或INOUT,缺省值是IN(IN表示参数是输入参数)。这个mode是在参数名的前面的
- argname:形式参数的名字。
- RETURNS:返回值
- RETURNS TABLE:返回二维表
这个真得背下来。。。
其中:
由于这个语法格式比较难背,下面给出两个例子(一个不带参数,一个带参数):
需要注意的是:传递给函数参数都可以用$1、$2(表示第一个参数、第二个参数)的标识符来表示。为了增加可读性,可以为参数声明别名,别名和数字标识符均可指向该参数值。在上面的例子中,实际上就是给$1取了一个别名salePrice
- 执行数据库函数(也就是调用数据库函数了)
- 查询窗口执行——语法格式及示例:
- 函数或存储过程中执行——语法格式及示例
这里将查询窗口以及在函数(以及存储过程)中使用函数分开是因为,在查询窗口中就一定是需要函数的返回值,并不是放到一个变量中;而在函数或者存储过程中调用函数可能并不关心函数的返回值,如果关心函数的返回值的话,也是将函数的结果放到一个变量中。所以在查询窗口中使用函数和在函数以及存储过程中使用函数是不同的,实际上也是这样的,因为使用的语句甚至都不一样,一个没有INTO关键字,一个有INTO关键字
疑问,这里的ppt是不是有错?
image-20240519163358955
image-20240519163407008
但是这两种方式还是统一的,统一后的语法格式如下:
其中INTO 变量名不分只会在函数或者存储过程中需要保存函数的返回值时使用
6.1.6 游标
- 游标的概念
- 游标(Cursor)是一种临时的数据库对象;
- 用来存放从数据库表中查询返回的数据记录;
- 提供了从结果集中提取并分别处理每一条记录的机制;
- 游标总是与一条SQL查询语句相关联;
- 游标包括:SQL语言的查询结果,指向特定记录的指针(所以查询的结果应该是一个游标的部分,而不是一个视图。疑问查询结果是一个视图还是一个游标的部分?感觉一个结果集要跟一个游标绑定应该是需要创建游标并且手动将查询语句和游标进行绑定的)
- 使用游标技术的主要原因
- 逐行处理数据: 在某些情况下,需要对查询结果集中的每一行数据进行逐行处理;游标允许开发人员逐行遍历结果集,对每一行数据进行处理。
- 控制数据访问: 游标提供了对查询结果集的灵活访问方式,开发人员可以在需要时随时获取下一行数据,而不是一次性将整个结果集加载到内存中。这种逐行访问的方式可以减少内存消耗,特别是当结果集非常大时。
- 支持复杂逻辑: 游标技术可以支持复杂的逻辑处理,例如在遍历结果集的过程中进行条件判断、数据更新或数据插入等操作。开发人员可以根据业务需求在游标中编写复杂的逻辑。
- 数据导航: 游标提供了对查询结果集的导航功能,开发人员可以在结果集中前进、后退或定位到特定位置,以便对数据进行操作或分析。
- 存储过程和触发器: 游标技术通常用于存储过程和触发器中,以实现对数据的逐行处理和操作。存储过程和触发器可以使用游标来处理复杂的业务逻辑或数据操作。
这个应该只要记一下点就好了,内容不用记
- 游标的使用
- 声明游标
- 声明游标变量格式(常在存储过程中声明)
- 声明并初始化游标格式
- arguments:游标的参数列表
- query:是一个select查询语句。这个查询语句返回的值存储在游标变量中(这个就很像是视图的创建语句。在视图创建的时候也需要提供一个SELECT语句。复习一下视图创建语句的格式:CREATE VIEW 视图名 AS query)
- 打开游标
- 打开未绑定查询语句的游标
- 方法一语句格式
- 方法二语句格式
- 打开已绑定查询语句的游标
- 语句格式
- 从游标中获取数据
- 语句格式
- 关闭游标
- 语句格式
其中refcursor是游标变量的类型,就好像在函数中声明一个记录变量时要使用RECORD关键字一样。
这个时候就不能给游标提供参数列表了,因为就算提供了参数列表,游标也不知道这些参数应该在哪里使用
其中:
需要注意的是这里在查询语句之前有一个FOR关键字(把这个FOR理解为:为query查询创建一个游标)
关于这个arguments,下面给出一个示例:
所以这个arguments相当于是给后面的查询传递参数
如:
需要注意的是这里FOR后面并不是一个字符串,而是一个实实在在的SQL查询
如:
需要注意的是:$1是函数或者存储过程的第一个参数;使用FOR EXECUTE的时候后面提供的就不是一个query了,而是一个查询语句的字符串
需要注意的是这里都没有给游标传递参数,因为在没有绑定查询语句的情况下打开游标并给游标绑定一个查询语句就相当于是将游标的初始化内联在了存储过程或者函数中了,所以这个时候就已经不需要给游标传递参数了
如:
需要注意的是这里需要给游标传递参数是因为游标在之前已经初始化并绑定了一个查询语句,在那个查询语句中使用了游标的参数,所以在这里需要传递参数,并不能像上面未绑定的情况一样直接进行函数参数传递
总结一下就是,已经初始化的带参游标是一个通用的游标,而一个在打开时进行绑定的游标是一个一次性游标
需要注意的是这里的target是一个行变量(也就是具有行类型的变量)。此外游标的属性列必须与 目标列的数量一致,并且类型兼容(也就是游标查询结果的行跟INTO后面的变量类型要兼容)。
如:
是否有通过FETCH成功读取数据,可以使用FOUND关键字实现:
也可以看出WHEN除了可以在CASE语句中使用,也能在一般语句中当做一个后缀的条件判断
关闭游标能够释放游标所占用的内存资源。
格式最简单的一集。但是需要注意的是:当游标被关闭后,如果需要再次读取游标的数据,需要重新使用open打开游标,这时游标重新查询返回新的结果
如:
6.2 数据库存储过程
6.2.1 存储过程的概念
- 存储过程(Stored Procedure)是一种数据库的对象;
- 由一组能完成特定功能的SQL语句集构成;
- 是把经常会被重复使用的SQL语句逻辑块封装起来,经编译后,存储在数据库服务器端;(所以叫存储过程)
- 当被再次调用时,而不需要再次编译;
- 当客户端连接到数据库时,用户通过指定存储过程的名字并给出参数,数据库就可以找到相应的存储过程予以调用。(所以函数调用和存储过程调用很像)
6.2.2 创建存储过程
这一节只需要知道不同的DBMS提供的创建存储过程的语法是不一样的就好了
6.2.3 创建存储过程的语法
- 基本语法:
- procedureName:存储过程名
- OR REPLACE :覆盖同名的存储过程
- IN、OUT或INOUT参数模式。IN为输入参数;OUT为输出参数缺省值是IN
- Pname:形式参数的名字
- dataType:该存储过程参数的数据类型
其中:
实际上就是把创建函数中的FUNCTION关键字替换为了PROCEDURE,并且存储过程是没有返回值的
6.2.4 创建存储过程
唯一一个需要注意的点是,存储过程创建的时候默认都是创建在public这个模式下的,所以在创建存储过程的时候有的存储过程的名字为:
这里实际上就是指定了proName这个存储过程是创建在public这个模式下的
6.2.5 修改存储过程
要知道存储过程是一个数据库对象,所以对存储过程的操作都是数据定义语言那一套
- 修改存储过程的名字
需要注意的是这里的新名字就不在是一个存储过程签名了,这里会直接将参数列表复制给新名字,形成一个存储过程:
- 修改存储过程的所有者(这个不太一样)
使用的是OWNER TO关键字
- 修改存储过程所属模式(这个也不太一样)
使用的是SET SCHEMA关键字
需要注意的是修改存储过程的时候需要提供不是存储过程名,而是存储过程签名,也就是要带上参数列表的,因为这样才能区别一个存储过程
6.2.6 删除存储过程
同样也是数据定义语言那一套,使用的是DROP关键字
- 语法格式
- IF EXISTS:如果指定的存储过程不存在,那么发出提示信息(一定要写这个)
- name :现存的存储过程名称。
- argmode:参数的模式:IN(缺省), OUT, INOUT, VARIADIC。请注意,实际并不注意OUT参数,因为判断存储过程的身份只需要输入参数。
- argname:参数的名字。请注意,实际上并不注意参数的名字,因为判断函数的身份只需要输入参数的数据类型。(所以可以看出存储过程是可以重写的,如果函数名相同,但是参数列表的类型不同,那么就是不同的存储过程)
- argtype:如果有的话,是存储过程参数的类型。
其中:
所以删除一个存储过程的最保险的做法就是将参数列表完整地写出来
如需要删除存储过程 TRANSFER:
6.2.7 存储过程的并发编程
PL/pgSQL语言不提供线程或并行执行功能(也就是这个数据库服务器编程语言并不原生支持并发或并行),但通过使用PostgreSQL的事务和锁来确保在并发环境下数据的一致性和完整性
- FOR UPDATE:用于排他锁定,防止其他事务同时修改或锁定相同的数据。在SELECT语句中使用FOR UPDATE子句来实现。(相当于是在当前的事务中给某个数据库对象加上了一个排他锁)
- FOR SHARE:用于共享锁定,允许多个事务同时读取相同的数据,但阻止其他事务对数据进行更新。在SELECT语句中使用FOR SHARE子句来实现。(相当于是在当前的事务中给某个数据库对象加上了一个共享锁)
- FOR NO KEY UPDATE:这种锁与FOR UPDATE类似,但它不会阻止其他事务对非键列进行更新。在SELECT语句中使用FOR NO KEY UPDATE子句来实现。(相当于是只对主键和外键列加上了排他锁)
- FOR KEY SHARE:这种锁与FOR SHARE类似,允许多个事务同时读取相同的数据,但阻止其他事务对键列进行更新。在SELECT语句中使用FOR KEY SHARE子句来实现。(相当于是只对主键和外键列加上了一个共享锁)
这里说的在SELECT中加上对应的子句实现,实际上就是在SELECT查询的最后加上对应的子句:
image-20240519221726400
6.2.8 存储过程优化原则
- 控制存储过程的复杂度
- 避免在存储过程中过度使用临时表
- 尽量使用参数化查询(也就是保证存储过程的通用性,这样就能使用一定的空间,处理更多的事)
- 合理使用索引来优化存储过程的性能
- 编写存储过程时遵循最佳实践和安全原则
6.3 数据库触发器
6.3.1 触发器的基本概念
- 触发器的概念
- 触发器是特殊类型的存储过程(是因为触发器也是直接存储在数据库服务器上的,不需要重新编译),主要由操作事件(就是只有数据定义语言能触发。如INSERT、UPDATE、DELETE) 触发而被自动执行。(也是一个数据库对象)
- 触发器可以实现比约束更复杂的数据完整性,经常用于加强数据的完整性约束和业务规则。
- 触发器本身是一个特殊的事务单位。
- 触发器的特点
- 与表相关联:必须定义在表或视图上。
- 自动触发:由执行INSERT、DELETE、UPDATE操作时触发
- 不能直接调用,也不能传递或接受参数(但是有返回值,返回的值是一个记录类型的变量,表示着当前操作事件操作的对象)
- 是事务的一部分:触发器和触发语句作为可在触发器内回滚的单个事务。
- 触发器的分类
- 按触发的语句分为: INSERT触发器、DELETE触发器、UPDATE触发器(都是数据操纵语言,查询的时候不会触发触发器)
- 按触发器的执行次数可分为:
- 语句级触发器:由关键字FOR EACH STATEMENT声明,在触发器作用的表上执行一条SQL语句时,该触发器只执行一次,即使是修改了零行数据的SQL,也会导致相应的触发器执行。如果都没有被指定,FOR EACH STATEMENT会是默认值。
- 行级触发器:由关键字FOR EACH ROW标记的触发器,当触发器作用的表的数据发生变化时,每变化一行就会执行一次触发器。例如,假设学生成绩表有DELETE触发器,当在该表执行DELETE语句删除记录时,如果删除了20条记录,则将导致DELETE触发器被执行20 次。
- 按触发的时间分为三类
- BEFORE触发器:在触发事件之前执行触发器。
- AFTER触发器:在触发事件之后执行触发器。
- INSTEAD OF触发器:当触发事件发生后,执行触发器中指定的函数,而不是执行产生触发事件的SQL 语句。在表或视图上,对于INSERT、UPDATE 或 DELETE 三种触发事件,每种最多可以定义一个INSTEAD OF 触发器
INSTEAD OF 触发器的执行逻辑如下:
image-20240520091854487
- 触发器相关的特殊变量
- NEW:数据类型是RECORD。对于行级触发器,它存有INSERT或UPDATE操作产生的新的数据行。对于语句级触发器,它的值是NULL。
- OLD:数据类型是RECORD。对于行级触发器,它存有被UPDATE或DELETE操作修改或删除的旧的数据行。对于语句级触发器,它的值是NULL。
- TG_OP:数据类型是text;是值为INSERT、UPDATE、DELETE 的一个字符串(所以判等的时候需要加上引号),它说明触发器是为哪个操作引发。
也就是说NEW和OLD只对行级触发器有效。这里的记录类型的变量就理解为一个行类型的变量,只不过这个行类型就相当于是使用一个泛型接受的,就使得它能够接收所有关系表的某一个元组
这些变量实际上是在触发器的存储过程中使用的,所以这三个特殊变量在一般的存储过程中也是可以使用的,只不过在一般的存储过程中使用没有意义
6.3.2 PostgreSQL创建触发器的基本语法
- 基本语法格式
- 触发器名:指明所定义的触发器名
- BEFORE | AFTER | INSTEAD OF:指明触发器被触发的时间
- 操作事件:INSERT、UPDATE、DELETE等,可以使用OR将操作事件连接起来
- ON 表名:指明触发器所依附的表
- FOR EACH { ROW | STATEMENT } :指明触发器被触发的次数
- EXECUTE PROCEDURE 函数 ( 参数列表 ) :指明触发时所执行的函数(是函数是因为定义触发器时要求有返回值,而存储过程不能有返回值)
可以发现在定义一个触发器的时候一定要先创建一个触发器函数以便将触发器和对应的函数绑定
其中:
如:
6.3.3 PostgreSQL创建触发器的基本步骤
- 检查数据库中将要创建的触发器所依附的表或视图是否存在,如果不存在,必须首先创建该表或视图。(可能是触发器函数需要查询的表,也可能是触发器函数需要存储数据的表)
- 创建触发器被触发时所要执行的触发器函数,该函数的类型必须是TRIGGER型(存储过程的返回值是TRIGGER),是触发器的执行函数。
- 创建触发器,一般需要指明触发器依附的表,触发器被触发执行的时间,触发器是行级触发器还是语句级触发器,触发器执行需要满足的条件。
疑问触发器调用的到底是函数还是存储过程?ppt上全TM是存储过程,但是涉及到实际的代码就变成函数了,纯sb
6.3.4 触发器的修改
因为触发器也是一个数据库对象,所以对触发器的修改使用的SQL语言也是数据定义语言
- 语句格式
- name:需要修改的现有触发器的名称。
- table_name:该触发器作用的表的名字。
- new_name:现有触发器的新名字。
其中:
这里需要提供table_name跟前面修改存储过程的名称是一样的,因为只有提供了name和table_name才能唯一确定一个触发器(对存储过程而言就是要提供参数列表)。然后新名字会自动绑定原触发器的表
如将score_audit_trigger触发器改名为score_audit_trig:
6.3.5 触发器的删除
也是使用DROP
- 语句格式
- IF EXISTS:如果指定的触发器不存在,那么发出提示而不是抛出错误(跟存储过程是一样的)
- name:要删除的触发器名;
- table_name:触发器定义所依附的表的名称。
其中:
如将上述触发器score_audit_trig删除(没有级联删除的功能,如果要有级联删除的功能的话就要使用CASCADE关键字了):
6.4 应用程序编程访问数据库
6.4.1 JDBC简介
- JDBC(Java DataBase Connectivity,Java数据库连接)技术的简称 ,是一种用于执行SQL语句的Java API。
- 它由一组用Java编程语言编写的类和接口组成。这个API由java.sql.*包中的一些类和接口组成,它为数据库开发人员提供了一个标准的API,使他们能够用纯Java API 来编写数据库应用程序。
- 注意:使用JDBC访问数据库需要相应数据库的JDBC驱动程序。
JDBC就相当于是在数据库DBMS上的一层中间件,使得不仅仅能使用SQL来通过DBMS操作数据库,还能使用Java通过DBMS操作数据库
6.4.2 JDBC工作原理
稍微看看就好了
image-20240520101519114
6.4.3 JDBC程序访问数据库的步骤
image-20240520101628078
这个是在Java程序中的执行流程,下面开始详细介绍:
- 导包:导入java.sql.*
- 加载驱动
- 基本格式
从这里开始就要将java代码放在try里面了
PostgreSQL数据库驱动程序加载语句:
这个的名字要背下来
- 建立连接
- 基本格式
这个方法返回一个connection对象
与PostgreSQL数据库建立连接语句:
- 创建Statement对象
可以用Connection对象的方法createStatement()创建Statement:
- 执行SQL语句
创建了Statement对象 ,就可以向Statement对象发送SQL语句。主要掌握两种执行SQL语句的方法:executeQuery()(针对数据查询语言,返回一个ResultSet结果集对象)、executeUpdate()(针对数据操纵语言,返回值是一个整数,表示受影响的行数)
如:
能发现executeQuery(),executeUpdate()这两个方法都是要接收一个SQL语句字符串的,只不过executeQuery方法执行的是数据查询语言,而executeUpdate执行的是数据操纵语言
- ResultSet保存结果集
下面这段话理解了就,有印象了就好了
ResultSet对象它被称为结果集,它代表符合SQL语句条件的所有行(也就是查询结果),并且它通过一套getXXX方法提供了对这些行中数据的访问
ResultSet里的数据一行一行排列,每行有多个字段,并且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
如:
再强调一遍,只有查询的返回值才是一个结果集
并且可以通过对结果集使用getString(“属性名”)来获取当前行的对应属性的值
- 关闭连接
在不需要ResultSet对象、Statement对象和Connection对象时,应该显式地关闭它们。
如:
6.4.4 嵌入式SQL数据库访问编程
前面使用JDBC实际上就是一个嵌入式SQL的例子
- 嵌入式SQL与宿主语言
- SQL语言具有很强的查询处理能力,而逻辑表达的能力很弱,界面编程能力也很弱。
- 如JAVA,C/C++等高级语言具有很强逻辑表达能力,能实现复杂的处理逻辑,同时具有较强的用户界面实现功能。
- 为了使程序语言同时具有它的优点,在JAVA、C/C++等高级语言中嵌入SQL语句,称高级语言为宿主语言。
- 嵌入式SQL的处理过程
- 由DBMS的预编译器扫描识别处理SQL语句,把SQL语句转换成主语言调用语句(将嵌入式SQL转换为函数调用),以使主语言编译程序能识别它,最后由主语言的编译程序将整个源程序编译成目标码,然后连接(Link)处理生成装载模块。(这一段理解就行了)
- 嵌入式SQL与主语言之间的通信
- 向主语言传递SQL语句的执行状态信息,使主语言能够根据此信息控制程序流程——主要用SQL通信区实现
- 主语言向SQL语句提供参数——主要用主变量实现(如excuteQuery的时候传递的sql字符串变量)
- 将SQL语句查询数据库的结果传回主语言处理——主要用主变量和游标实现(如执行excuteQuery返回的结果集就相当于是一个游标)
- Java语言嵌入式SQL语句的步骤
- 使用Connection对象的下列方法之一创建查询语句对象:
- Connection.createStatement()创建Statement对象,静态SQL语句查询;
- Connection.prepareStatement(String sql)创建PreparedStatement对象,实现动态SQL语句查询;
- Connection.prepareCall(String sql)创建CallableStatement对象来调用数据库存储过程(但是我不太会用。。。)
- 执行查询
- Statement.execute(String sql) 执行各种SQL语句,返回一个boolean类型值,true表示执行的SQL语句具备查询结果,可通过Statement.getResultSet()方法获取;
- Statement.executeUpdate(String sql)执行SQL中的insert/update/delete语句,返回一个int值,表示受影响的记录的数目;
- Statement.executeQuery(String sql)执行SQL中的select语句,返回一个表示查询结果的ResultSet对象。
- 查询返回结果
- ResultSet.next()将游标由当前位置移动到下一行;
- ResultSet.getString(String columnName) 获取指定字段的String类型值;
- ResultSet.getString(int columnIndex) 获取指定索引的String类型值;
- ResuleSet.previous()将游标由当前位置移动到上一行。
- 向SQL语句传递参数:如果Java宿主语言需要向SQL语句传递参数,则使用动态查询prepareStatement对象,preparedStatement预编译SQL语句,并支持批处理,执行查询有类似Statement对象的三种执行方式,且执行方法中没有参数
- preparedStatement.executeUpdate()执行更新;
- preparedStatement对象使用addBatch()向批处理中加入更新语句,
- executeBatch()方法用于成批地执行SQL语句。
实际上就跟上面的JDBC程序访问数据库的步骤差不多,这里就着重介绍一些额外的方法
由于没有使用过preparedStatement对象,所以这里就给出一个例子;
可以发现实际上动态的意思就是传递给prepareStatement方法的sql语句只是一个SQL语句的模板,然后真正需要查询的时候通过setString(index,value)方法(或者setInt,具体是什么根据数据库中属性的类型确定)将SQL模板中第index个位置的?修改为value
6.5 JavaWeb数据库编程
6.5.1 Servlet的概念
- 移植性好,本身是一个Java类,具有跨平台性;
- Java EE平台支持的全部Java API都可用于Servlet;
- 安全性提高,服务器崩溃的可能性减小;
- 多个Servlet可以组织在一起,输出可由组生成,有助于代码复用;
- 可以与服务器中的其它组件交互
6.5.2 Servlet 执行原理
- 客户机将HTTP 请求发送给Web 服务器
- Web 服务器将该请求转发给Servlet
- Servlet 处理该请求
- Servlet 将响应发送给Web 服务器
- Web 服务器将响应转发给客户机
6.5.3 Web 容器
- Web 容器提供了运行servlet 所需的环境;
- 它负责实例化、初始化、调用服务方法并从服务中移除servlet,管理Servlet 的生命周期;
- 实现Servlet API;
- 它充当Web 服务器和Servlet 之间的桥梁;
- 将请求从Web 服务器转发到Servlet;
- 将响应从Servlet 转发到Web 服务器。
6.5.4 Servlet基本工作流程
如下图:
image-20240520114345242
- 客户机将请求发送到服务器;
- Servlet程序是由Web服务器调用,Web服务器收到客户端的Servlet访问请求后,解析客户端的请求;
- 服务器上的Web容器转载并实例化Servlet;
- 调用Servlet实例对象的init()方法;
- 调用Servlet的service()方法并将请求和响应对象作为参数传递进去;
- Servlet 创建一个响应,并将其返回到Web容器。
- Web容器将响应发回客户机。
- 服务器关闭或Servlet空闲时间超过一定限度时,调用destory()方法退出
只需要结合图片来理解就好了
6.5.5 JSP概念
- JSP(Java Server Pages)是基于Java 语言的一种Web服务器端的开发技术。
- 利用这一技术可以建立安全、跨平台、易维护的Web应用程序
- JSP ≈ HTML + Java
6.5.6 JSP运行原理
image-20240520115020515
所以JSP是在Severlet之前使用的,通过翻译转换生成Serverlet需要的代码
6.5.7 JSP页面的组成
- HTML标记
- JSP标记(分为指令标记、动作标记)
- 成员变量与成员方法<%! %>
- Java程序片<% %>
- Java表达式<%= %>
- 注释
给出一个JSP的例子:
疑问这玩意会不会考。。。(其实要写的部分应该也是之后<%%>中的java代码部分,而且这里的java代码实际上就是JDBC那一套。因为JDBC就是在数据库服务器上使用的,所以就是在服务器上对数据库进行查询)
6.5.8 Mybatise
- MyBatis简介
- MyBatis 是Apache的开源项目iBatis,基于Java的持久层框架,提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
- MyBatis 是支持普通 SQL查询、存储过程和高级映射的优秀持久层框架。MyBatis 使用简单的XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录,而不是使用JDBC代码和参数实现对数据的检索。
6.5.9 Mybatis 功能架构
稍微记一下是哪三层就好了
image-20240520122722193
6.5.10 Mybatis访问数据库基本步骤
混个眼熟就好了
image-20240520122918599
最重要的应该是映射语句的那一步吧,实际上就是把一般java对象直接映射到了数据库中去对应一个元组,而JDBC中是直接返回一个结果集对象,其中每一个元组并不是一个对象
- 读取xml配置文件,xml文件中配置了Mybatis的运行环境等信息以及操作数据库的SQL语句
- SqlSessionFactoryBuilder通过Configuration生成sqlSessionFactory对象。
- 通过sqlSessionFactory打开一个数据库会话sqlSession会话,操作数据库需要通过sqlSession进行。
- Mybatis底层自定义了Executor执行器接口操作数据库,Executor接口负责动态SQL的生成和查询缓存的维护,将MappedStatement对象进行解析,SQL参数转化、动态SQL拼接,生成JDBC Statement对象
所以Mybatis框架做的事实际上就是将一个映射关系转化为了真正的JDBC查询。就是把JDBC变得更易用了
终于结束哩
不会的地方搜索疑问
不确定的地方搜索copilot
疑问如果一个二元关系实体的一方是可选的,那么这个关系要画虚线吗?或者说什么时候要画虚线?应该是要画虚线的,就是如果一个实体是可选的,那么他那一半关系就要画成虚线:
image-20240517195138298
- 作者:Noah
- 链接:https://imnoah.top/article/DatabaseReview/ChapterSix
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。