学编程,来源栈;先学习,再交钱
当前系列: 持久化 修改讲义
学习基础:
  • Java/C#语法
  • 数据库和SQL


为什么是持久化?

其实就是用Java/C#,连接数据库,进行增删改查的操作。

所谓持久化,建立在以下逻辑基础上:

  1. Java和C#里面的数据,都是保存在内存中的,断电就丢失,不持久
  2. 要长期保存数据,(理论上)不一定要用关系型数据库啊

体现的这样的思想:应该面向对象(或者领域),而不是面向数据库

PS:DDD(Domain Design Driven),领域驱动。


接口和驱动

和发送Email一样,底层的东西我们其实不用操心(比如:JVM和CLR怎么就连接上数据库的),我们只需要学会调用类库就OK了。

这些类库使用共同的接口,这一系列的接口集被称之为:

这些接口又被称之为API复习:所谓API,以上API都是语言标准库自带的。

但接口没有实现,不能运行,所以还需要驱动(driver,实现了这些接口的类库,dll或jar)

驱动由数据库厂商(官方)/第三方提供。

@想一想@:为什么要这样做?(多态)

  • 对语言类库设计者而言,不需要考虑具体的数据库实现(抽象的思考)
  • 对应用开发人员而言,方便理解记忆,还可以基于这种抽象,重用通用的代码,接入不同的driver,访问不同的数据库
  • 对数据库厂商而言,只需要按部就班的实现接口就万事大吉


通用的流程

所有对数据库的操作,其实都可以抽象/概括成:

  1. 建立和数据库的连接(connection)
  2. 准备好对数据库进行何种操作的命令(command)/语句(statement)
  3. 予以执行(execute),返回结果:
  4. 关闭连接,释放资源
以上都被封装成对象(面向对象嘛,^_^)

#常见面试题:JDBC/ADO.NET的n大对象是?#


连接

连接到数据库,通常需要连接字符串,包含:

  • 数据库的地址,即数据库服务器在哪儿?
  • 具体要连接哪个数据库
  • 连接的方法:比如使用的协议(TCP吗?),加密吗……
  • 账户信息:用户名和密码

不同的驱动连不同的数据库,有不同的格式要求:

  • ADO.NET连SQL Server:
    Data Source=数据库地址;Initial Catalog=数据库名称;User Id=数据库登录名;Password=数据库密码;
  • Java连MySQL:
    jdbc:mysql://数据库地址:端口号/数据库?user=用户名&password=密码

实际开发中,连接字符串

  • 远程的直接问老大要
  • 本地的GUI工具(VisualStudio/WorkBench)先连上,再复制粘贴(演示

连接使用策略

#常见面试题#
  1. 尽可能在一次连接中完成更多的工作:建立一个和数据库的连接,消耗的资源(花费的时间)可能比进行数据库操作多很多。类似于打电话:铃声响了10秒钟,就一句话回家吃饭。
  2. 尽可能晚的打开,尽可能早的关闭(释放资源,同IO读写流):一个数据库能够提供的连接是有限的。别占着茅坑不拉屎!

注意:上述1和2是矛盾的。所以需要根据具体情况选择合适的策略(Web开发中常用的是ConnectionPerRequest

一些常见错误:

  1. 忘记/或者因异常等原因无法关闭数据库
  2. 在循环中打开/关闭数据库连接
  3. 在连接中做一些和数据库操作无关的事情(只能尽量避免)

连接池

作用同字符串池和线程池。

.NET内置了连接池,Java需要自己选择配置。


命令语句

增删改查都是由包含SQL语句的Command/Statement对象实现的:

SQL注入

早期开发人员会将用户输入和SQL语句拼接成字符串,之后进行查询,会造成SQL注入的问题。

比如实现准备好这么一条语句:

UPDATE Student SET Enroll = '2020/5/29' WHERE [Name] = '{name}'
然后用用户输入值代替{name},我们期待的是用户输入学生的姓名,比如:atai,这是OK的。

但恶意用户想得更多,……,比如他想利用这个机制删除整张表!怎么办?

只需要做成这样的SQL语句就OK了:

高亮部分就是用户的输入。(演示:删除一行

@想一想@:假设你是一个恶意用户,想绕过用户名密码检查登录一起帮,你该怎么做?假设后台用到的SQL语句是:

SELECT COUNT(*) FROM [User] WHERE [Name] = N'{name}'AND Password = N'{password}'

参考答案:fg' OR 1=1 --

参数化

早期开发人员会在拼接字符串之前做一些验证,过滤恶意输入。

但现在主流的办法是直接使用:参数化SQL语句

其核心就是:

  • SQL语句本身是完整的,会被“编译”成一个独立的SQL命令,不需要用户输入来“拼接”
  • 用户的输入会当做一个“参数”传入上述SQL命令,而不是拼接成SQL语句的一部分

使用参数化查询,上述SQL代码会变成:

UPDATE Student SET Enroll = '2020/5/29' WHERE [Name] = @name

为了便于理解,同学们可以将其“想象成”这样的SQL执行方式:

DECLARE @name NVARCHAR = N'飞哥;DELETE Student';
UPDATE Student SET Enroll = '{DateTime.Now}' WHERE [Name] = @name;

理解:用户的输入无法变成可执行的SQL语句。


批处理

有时候,我们可能需要一次性的执行多条SQL语句(比如:标记已读/删除消息通知)。

即使是在同一个连接里面,SQL语句一条一条的执行,一行一行的更新/删除,可能(是肯定!需要测试)不如一次性的执行效率高。

所以,JDBC和ADO.NET有点绕都提供了批(batch)处理的执行方式:

  • 一次性的生成多条SQL语句,
  • 把生成的SQL语句一次性的发送给数据库,要求一次性的执行


事务

分两种:

  1. 单个数据库:完全依赖于数据库。JDBC和ADO.NET只是做配置,生成begin transaction/commit/roll back之类的SQL语句
  2. 多个数据库:又被称之为分布式事务。需要额外的控制……
    • ADO.NET提供了内置的、简洁干净的
    • JDBC还是靠第三方jar包……


执行结果

如果是(单个元素的)标量,可以直接转换成Java/C#类型。

如果是(表结构的)结果集,就会用一个特殊对象(JDBC:ResultSet / ADO.NET:DataReader)来存放,要求可以:

  1. 逐行地读取(JDBC:next() / ADO.NET:read())出行数据
  2. 再根据下标或列名取出该行的某列值

注意结果集的读取,需要保持和数据库的连接。换言之,结果集对象并不是一个真正存放了查询结构的数据容器,而是一个能够获取数据库数据的工具。

我们可以想象这样一种常见的场景(基于性能优化等原因考虑):

  1. 数据库修改了SELECT语句,只进行了分段查询,将分段查询(比如100条)结果放入其(数据库的)缓冲区
  2. 使用next()读取缓存区的数据不需要再次执行SELECT查询,
  3. 但如果要读取缓存区以外的数据,就需要再次执行下一段的SELECT查询了

注意

  • SELECT查询的结果,实际上存储在客户端的网络缓冲区中,每一次Read()会再次连接数据库进行查询

  • 但是,在调用Read()时,还是需要保持connection的Open状态:这被称之为“连接式”查询



作业

Main()函数调用实体类(静态/实例?)方法,连接数据库,控制台模拟输入输出,实现以下功能:
  1. 注册/登录:User类中的
    1. LogOn():使用SQL语句
    2. Register():使用存储过程bp_user_register
  2. 内容:Content类的
    1. 发布/修改:Publish()/Edit(),至少包含作者
    2. 单页呈现:Show(),根据Id输出内容的标题、正文、作者、评论等
    3. 列表页呈现:包括过滤(作者/文章系列/求助状态)和分页
  3. 消息批量标记为已读/删除:Message类的Read()和Delete()方法
  4. 用事务完成帮帮币的交易:???类的Sale()方法
学习笔记
源栈学历
键盘敲烂,月薪过万作业不做,等于没学

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 持久化 中继续学习:

上一课: 已经是第一课了……

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码