JDBC调用存储过程
-
加载驱动Class.forName
-
创建数据库连接conn= DriverManager.getConnection
-
创建处理块CallableStatement cs = conn.prepareCall
-
放入参数prepareCall.setString
-
执行存储过程prepareCall.execute()
-
关闭连接
无参数的存储过程
create procedure p1() begin
select * from t_users;
end;
-- 命令行调用 call p1();
JDBC编码调用
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///test?serverTimezone=UTC", "root", "123456");
CallableStatement cs = conn.prepareCall("{call p1()}");
cs.execute();
ResultSet rs = cs.getResultSet();
while(rs.next()){
System.out.println(rs.getLong("id")+"\t"+rs.getString(2) +"\t"+rs.getDate("birth"));
}
rs.close();
cs.close();
conn.close();
带参数的存储过程定义
CREATE PROCEDURE add_pro(a INT,b INT,OUT ret INT)
BEGINSET
ret=a+b;
END
-- 调用方法
call add_pro(1,2,@cc);
select @cc;
调用存储过程
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
CallableStatement cs = conn.prepareCall("{call add_pro(?,?,?)}");
cs.setInt(1, 123);
cs.setInt(2, 300);
cs.registerOutParameter(3, Types.INTEGER);
//处理参数中的返回值 注册返回值类型
cs.execute();
int res=cs.getInt(3);
//获取输出参数的返回值
System.out.println(res);
cs.close();
conn.close();
特殊存储过程参数inout
CREATE PROCEDURE mul_pro(inout ret INT)
BEGINSET
ret=ret*11;
END
-- 命令行的调用方式
set @cc=5;
call mul_pro(@cc);
select @cc;
JDBC调用
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
CallableStatement cs = conn.prepareCall("{call mul_pro(?)}");
cs.setInt(1, 9);
cs.registerOutParameter(1, Types.INTEGER);
//处理参数中的返回值 注册返回值类型
cs.execute();
int res=cs.getInt(1);
System.out.println(res);
cs.close();
conn.close();
存储过程包含多个查询结果集
create procedure show1 (aid bigint)
begin
select * from t_users where id=aid;
select * from t_roles where id=aid;
end;
-- 命令行调用
call show1(2);
JDBC调用
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
CallableStatement cs = conn.prepareCall("{call show1(?)}");
cs.setLong(1, 2L);
cs.execute();
ResultSet rs = cs.getResultSet();
ResultSetMetaData rsmd = rs.getMetaData();
System.out.println("====================");
while (rs.next()) {
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
System.out.print(rs.getObject(i) + "\t");
}
}
System.out.println(); //需要自定义方法的方式获取数据,否则获取数据部分代码多次出现
while (cs.getMoreResults()) {
rs = cs.getResultSet();
rsmd = rs.getMetaData();
System.out.println("====================");
while (rs.next()) {
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
System.out.print(rs.getObject(i) + "\t");
}
}
System.out.println();
}
rs.close();
cs.close();
conn.close();
MySQL触发器
触发器是一种数据库对象,就是在数据表中发生插入、删除、更新操作之类的操作,然后自动触发了预
先编好的若干条SQL语句的执行
特点
触发事件的操作和触发器里面的SQL语句是一个事务操作,具有原子性,要么全部执行,要么都不执行。比如:银行转账。
优势
- SQL触发器提供了检查数据完整性的替代方法
检查型约束
create table t1(id int,name varchar(32),age int check(age between 18 and 65));
数据库引擎需要使用innodb,innodb存储引擎支持事务、外键和检查性约束。检查型约束定
在表中,不会出现语法错误,但是DBMS并不会进行检查
-
SQL触发器可以捕获数据库层中业务逻辑中的错误。
-
SQL触发器提供了运行计划任务的另一种方法。通过使用SQL触发器,不必等待运行计划的任务,因为在对表中的数据进行更改之前或之后自动调用触发器
MySQL为了容灾,一般需要针对数据库进行备份。备份的一般计划为周1全量备份,其它时间增量备份。备份时间一般选择在访问量最少的时间端执行,例如0点到1点之间,可以通过计划任务的方式执行或者使用触发器自动进行备份
- SQL触发器对于审核表中数据的更改非常有用。
缺点
-
SQL触发器只能提供扩展验证,并且无法替换所有验证。一些简单的验证必须在应用层完成。 例如可以使用JavaScript或服务器端使用服务器端脚本语言来验证客户端的用户输入
-
从客户端应用程序调用和执行SQL触发器不可见,因此很难弄清数据库层中发生的情况。
-
SQL触发器可能会增加数据库服务器的开销。
基础语法
CREATE TRIGGER trigger_name触发器名称 trigger_time触发事件 trigger_event触发事件
ON table_name表名称 FOR EACH ROW [trigger_order]
trigger_body
-
Trigger_time: 触发器执行时间: AFTER | BEFORE
-
Trigger_event: 触发器触发的事件: INSERT | UPDATE | DELETE
MySQL中的触发器可以分为3大类
- FOR EACH ROW:表示任何一条记录上的操作满足触发事件都会触发该触发器
触发器可以分为行级触发和语句级触发
-
Table_name: 表示触发事件操作表的名字
-
Trigger_body: 创建触发器的SQL语句
典型应用
- BEFORE INSERT : 在插入数据前,检测插入数据是否符合业务逻辑,如不符合返回错误信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0OR3K2x-1657355401471)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220618165527770.png)]
- AFTER INSERT : 在表 A 创建新账户后,将创建成功信息自动写入表 B 中。典型的对账处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CgLXKFdn-1657355401472)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220618165609680.png)]
auto_increment问题:
insert操作只有new表,没有old表。如果列auto_increment,则before时new.id值为0,after时new.id就是新增生成的id值
- BEFORE UPDATE :在更新数据前,检测更新数据是否符合业务逻辑,如不符合返回错误信息
CREATE TRIGGER validate_customer_level
BEFORE UPDATE
ON customers
FOR EACH ROW
IF OLD.level='VIP' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'VIP 级别客户不能降级为普通级别客户';
END IF
update表既有old其中存储需要被替代的数据,又有new是需要新添加进入的数据。old是update之前的数据,new是update之后的数据
- AFTER UPDATE :在更新数据后,将操作行为记录在 log 中
CREATE TRIGGER log_sales_updates
AFTER UPDATE
ON sales
FOR EACH ROW
Insert into audit_log(sales_id, previous_amount, new_amount, updated_by, updated_on) VALUES (NEW.sales_id新数据,OLD.sales_amount老数据,
NEW.sales_amount,(SELECT USER()), NOW())
-
BEFORE DELETE :在删除数据前,检查是否有关联数据,如有,停止删除操作
CREATE TRIGGER validate_related_records BEFORE DELETE ON customers FOR EACH ROW IF OLD.customer_id in (select customer_id from sales) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '这位客户有相关联的销售记录,不能删除。'; END IF;
删除操作有逻辑删除和物理删除之分,基本上都是逻辑删除,所谓的逻辑删除是指在表中新增一个列用于标识该行数据是否已经被删除,deleted boolean default 0。delete操作只有old表,其中存储需要删除的数据,没有new表
-
AFTER DELETE :删除表 A 信息后,自动删除表 B 中与表 A 相关联的信息。
CREATE TRIGGER delete_related_info AFTER DELETE ON sales FOR EACH ROW Delete from customers where customer_id=OLD.customer_id;
查看存储器
-
SHOW TRIGGERS;查看数据库中有哪些触发器
-
SHOW CREATE TRIGGER trigger_name;
NEW和OLD的应用
MySQL中定义了NEW和OLD两个临时表,用来表示触发器的所在表中,触发了触发器的哪一行数据,来引用触发器中发生变化的记录内容,具体修改是否能够生效取决于事务是提交还是回滚
-
在 INSERT 型触发器中,NEW 用来拦截并保存将要(BEFORE)或已经(AFTER)插入的新数据;
-
在 UPDATE 型触发器中,OLD用来拦截并保存将要或已经被修改的原数据,NEW 用来拦截并保存将要或已经修改为的新数据。
-
在 DELETE 型触发器中,OLD 用来拦截并保存将要或已经被删除的原数据
删除触发器
DELETE TRIGGER trigger_name;
应用限制
触发器在有限的场合能够发挥其优势,比如统计数据、数据表变更日志等。但是也会有一些缺陷,比如大数据量的更新由于逐行触发,会降低效率。还有就是,MyISAM 引擎无法保障原子性。因此,要根据应用场景是否要是有触发器。
游标
游标提供了一种灵活的操作方式,能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过程开发的能力在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,可以通过操作游标来对数据行进行操作。
优点
-
允许程序对由查询语句select返回的行集合中的每一行执行相同或不同的操作,而不是对整个行集合执行同一个操作
-
提供对基于游标位置的表中的行进行删除和更新的能力
-
游标实际上作为面向集合的数据库管理系统RDBMS和面向行的程序设计之间的桥梁,使这两种处理方式通过游标沟通起来。
缺点
游标会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源,造成内存不足,这是因为游标是在内存中进行的处理。
原理
游标就是把数据按照指定要求提取出相应的数据集,然后逐条进行数据处理。使用游标的顺序:声名游标、打开游标、读取数据、关闭游标、删除游标。
触发器限制
mysql触发器覆盖标准SQL中定义的所有功能,但是,在应用程序中使用它们也会有一些限制:
-
使用在SHOW,LOAD DATA,LOAD TABLE,BACKUP DATABASE,RESTORE,FLUSH和RETURN语句之上
-
使用隐式或明确提交或回滚的语句,如COMMIT,ROLLBACK,START TRANSACTION,LOCK/UNLOCK TABLES,ALTER,CREATE,DROP,RENAME等
-
使用准备语句,如PREPARE,EXECUTE等
-
使用动态SQL语句
注意的问题
声名游标、打开游标、读取数据、关闭游标、删除游标。
触发器限制
mysql触发器覆盖标准SQL中定义的所有功能,但是,在应用程序中使用它们也会有一些限制:
-
使用在SHOW,LOAD DATA,LOAD TABLE,BACKUP DATABASE,RESTORE,FLUSH和RETURN语句之上
-
使用隐式或明确提交或回滚的语句,如COMMIT,ROLLBACK,START TRANSACTION,LOCK/UNLOCK TABLES,ALTER,CREATE,DROP,RENAME等
-
使用准备语句,如PREPARE,EXECUTE等
-
使用动态SQL语句
注意的问题
从MySQL 5.7.2+版本开始,可以为相同的触发事件和动作时间定义多个触发器。当使用不使用INSERT,DELETE或UPDATE语句更改表中数据的语句时,不会调用与表关联的触发器。 例如,truncate语句删除表的所有数据,但不调用与该表相关联的触发器。但是,有些语句使用了后台的INSERT语句,如REPLACE语句或LOAD DATA语句。如果使用这些语句,则调用与表关联的相应触发器。所以必须要为与表相关联的每个触发器使用唯一的名称。可以为不同的表定义相同的触发器名称,这是一个很好的做法。