Ykuri98
文章46
标签14
分类1
学习总结(2022.05.10-2022.05.16)

学习总结(2022.05.10-2022.05.16)

关系型数据库与非关系型数据库

关系型数据库:不仅存储数据,也存储数据与数据间的关系。一般存储在磁盘上,读取较慢。

非关系型数据库:仅仅用于存储数据,不能存储数据间的关系。但是一般存储在内存中,读取较快。

SQL语句

库的操作

1
2
3
4
5
6
create database dbName;# 添加库
drop database dbName;# 删除库
alter database dbName character set utf8 collate utf8_bin;# 修改库的字符集和校对规则(不能修改名字)
show databases;# 查询所有的库
show create database dbName;# 查询对应库的建库语句
use dbName;# 选中库(重要)

表的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create table tableName(
columnName1 dataType,
columnName2 dataType,
...
) character set utf8 collate utf8_bin;# 创建表(后面为设置表的字符集和校对规则)
drop table tableName;# 删除表
rename table tableName to newTableName;# 修改表名
alter table tableName character set latin1 collate latin1_bin;# 修改表的字符集和校对规则
alter table tableName add columnName dataType;# 添加列(列名后跟列的数据类型)
alter table tableName drop columnName;# 删除列
alter table tableName modify columnName dataType;# 修改列的类型
alter table tableName change columnName newColumnName dataType;# 修改列的类型和名字
show tables;# 查询所有表
describe tableName;# 查询表结构
show create table tableName;# 查询建表语句

数据的操作

1
2
3
4
5
6
7
8
9
insert into tableName values 
(...),
(...),
...
;# 插入数据
delete from tableName columnName = ...;# 删除数据
update tableName set columnName = ... where columnName = ...;# 修改数据
select * from tableName;# 查找所有数据
select columnName from tableName;# 查找该列数据

关键字:

  1. where:寻找符合条件的指定数据行。
  2. distinct:过滤结果相同的记录。
  3. limit:限制显示的行数。
  4. as:别名,对查询结果的列和表起名。
  5. order by:排序。可接desc表示降序。
  6. group by:分组。可接having对分组后的结果进行过滤。

聚合函数:

  1. max:最大值
  2. min:最小值
  3. avg:平均值
  4. sum:总和
  5. count:计数

SQL语句执行顺序

1
2
3
4
5
6
7
(5) SELECT column_name, ... 
(1) FROM table_name, ...
(2) [WHERE ...]
(3) [GROUP BY ...]
(4) [HAVING ...]
(6) [ORDER BY ...]
(7) [Limit ...]

数据完整性

实体完整性:确定表中数据是唯一且不重复的,确保用户不会输入重复的数据,实现的关键是主键。主键是一行数据中唯一不允许重复和值为null的字段。主键一般可以自增。在建表时可以声明一个字段为主键。

1
2
3
create table tableName(
columnName dataType PRIMARY KEY auto_increment# 声明为主键且自增
) auto_increment=1000# 设置自增的起始数

域完整性:每一个字段都应该有类型以约束值。也可以使用unique(值唯一)和not null(值不为空)来约束值。unique修饰的字段可以为空。

参照完整性:一个表如果存在外键,那么这个外键应该与另一个表的主键相对应。外键使用foreign key来声明。

三大范式

第一范式:每一列应该保持原子性。原子性表示表中的数据都是一个不可拆分的最小单元。

第二范式:数据应该有唯一性。唯一性表示每一条数据都有唯一的标识,如主键。

第三范式:数据不应该冗余。

多表查询

内连接inner join

1
2
select * from user inner join user_detail on 
user.id = user_detail.user_id;

外连接outer join

外连接分为左连接和右连接,使用左连接时会保留左表数据,右连接反之。

1
2
select * from user left outer join user_detail on 
user.id = user_detail.user_id;

嵌套查询

一个SQL语句的结果可以作为另一个SQL语句的条件。

1
2
select * from student where clazz_id = 
(select id from clazz where name = '一班');

JDBC

JDBC是java的一套连接数据库的标准接口。但JDBC只提供接口,具体的实现(如何连接数据库)由数据库厂家的驱动实现,如MySQL的mysql-connector。

JDBC的使用如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 1.加载驱动  
DriverManager.registerDriver(new Driver());

// String url = "协议 + ip + 端口 + 路径 + 参数";
String url = "jdbc:mysql://localhost:3306/40th?useSSL=false&characterEncoding=utf8";
String username = "root";
String password = "123456";

// 2.发送用户名和密码,建立连接
// 返回的当前是一个Connection接口,但是实际上在运行的时候,返回是Connection接口的实现类的实例
Connection connection = DriverManager.getConnection(url, username, password);

// 3.获取statement对象
Statement statement = connection.createStatement();

// 4.发送SQL语句
// 增删改使用executeUpdate方法
int affectedRows = statement.executeUpdate("insert into stu values (4,'云飞兄',20,'358班')");
// 查使用executeQuery方法
ResultSet resultSet = statement.executeQuery("select * from stu");

// 5.解析结果集
System.out.println("affectedRows:" + affectedRows);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String className = resultSet.getString("class");

System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("age:" + age);
System.out.println("className:" + className);
}

// 6.断开连接
resultSet.close();
statement.close();
connection.close();

数据库注入

在使用Statement时,如果输入的SQL语句是字符串拼接而成的,那么用户就可能输入一些包含SQL关键字的字符,这样就可能改变SQL语句的格式,引发安全性的问题。

解决方式是使用PreparedStatement(预编译的Statement)。

1
2
3
4
5
6
7
8
9
10
11
// 在创建PreparedStatement的时候,PreparedStatement会把当前这个没有参数的SQL语句,发送给MySQL服务器,执行预编译
// 预编译:其实就是去解析这个SQL语句中的关键字,变成MySQL可以执行的命令
// 在预编译之后,后续输入的字符串,就只会被MySQL当成纯文本来解析
PreparedStatement preparedStatement = connection.prepareStatement("select * from user where name = ? and password = ?");

// 设置参数
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);

// 传递参数,执行SQL语句
ResultSet resultSet = preparedStatement.executeQuery();

在安全性上,PreparedStatement要比Statement安全;在效率上,因为PreparedStatement需要与数据库通信两次,一次传输预编译的SQL语句,一次传输参数,所以相比于只通信一次的Statement要慢一些。

批处理

对SQL语句有三种批处理的方式:

for循环插入

1
2
3
4
5
6
7
8
9
10
11
12
13
// for循环
public static void batchUseForEach() throws SQLException {

Statement statement = connection.createStatement();

for (int i = 0; i < 10000; i++) {

String sql = "insert into user values ("+i+",'foreach',null,null)";

statement.executeUpdate(sql);

}
}

statement批处理

1
2
3
4
5
6
7
8
9
10
11
12
13
// Statement处理
public static void batchUseStatement() throws SQLException {

Statement statement = connection.createStatement();

for (int i = 10000; i < 20000; i++) {
String sql = "insert into user values ("+i+",'batchUseStatement',null,null)";
statement.addBatch(sql);
}

// 发送SQL语句,执行
statement.executeBatch();
}

PreparedStatement批处理

需要在数据库的url后面加上配置rewriteBatchedStatements=true ,表示开启批处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PreparedStatement处理
public static void batchUsePrepapreStatement() throws SQLException {

// 获取PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement("insert into user values (?,?,null,null)");

// 循环,设置参数
for (int i = 20000; i < 30000; i++) {
preparedStatement.setInt(1,i);
preparedStatement.setString(2,"PrepapreStatement");
preparedStatement.addBatch();
}

// 把参数发送给MySQL服务器,执行SQL语句
preparedStatement.executeBatch();
}

由代码可知,三种方式与SQL服务器的通信次数、SQL语句的编译次数和执行次数如下表。

通信次数 编译次数 执行次数 时间
for循环 n n n 最长
Statement 1 n n 次之
PreparedStatement 2 1 1 最短

然而通过实际测试发现在数据较大(>= 1000000)的情况下statement批处理的时间要长于for循环,可能原因是操作系统对内存的分配:在for循环中,每一次循环时操作系统不必去一直维护内存,在一次循环结束后可以将内存释放;而statement批处理时操作系统需要维护内存去记录sql语句,这导致了statement批处理的时间变长。

事务

事务是指组成事务的一组操作,这些操作要么一起操作成功,要么一起操作不成功。

事务具有四个特性:

  1. 原子性。事务是一个不可分割的操作单元,里面的一组操作要么都成功,要么都不成功。
  2. 一致性。事务必须使数据库从一个一致性状态到另一个一致性状态(比如无论怎么转账,所有账户的总金额应该不变)。
  3. 隔离性。事务与事务间应该互相隔离,互不影响。
  4. 持久性。一个事务一旦生效,对数据库的改变是永久的,即事务提交以后就无法回滚。

针对事务的隔离性,提出了事务的四个隔离级别:

  1. 读未提交
  2. 读已提交
  3. 可重复读
  4. 串行化

在这些隔离级别中,会出现如下三个隔离问题:

  1. 脏读。即一个事务读取到了另一个事务还未提交的数据。
  2. 不可重复读,即在同一个事务中读取同一个数据,前后读取的数据不一致。一般是一个事务读取到了另一个事务修改的数据。
  3. 虚幻读,即在同一个事务中读取同一个数据,前后读取的数据不一致。一般是一个事务读取到了另一个事务插入或删除的数据。

四个隔离级别在处理三个隔离问题的能力如下

脏读 不可重复读 虚幻读
读未提交(read uncommitted)
读已提交(read committed) X
可重复读(repeatable read) X X
串行化(serializable) X X X

在MySQL中,默认的隔离级别是可重复读。MySQL通过自身解决了部分虚幻读的问题,但并没有完全解决。

串行化虽然安全级别最高,但是此时数据库完全丧失了多线程的能力,在一个事务操作期间,其他事务都会变成阻塞状态,数据库的效率会因此下降。

在JDBC中,开启事务的API如下。

1
2
3
4
5
6
7
8
//  开启事务
connection.setAutoCommit(false);

// 提交事务
connection.commit();

// 回滚事务
connection.rollback();

Datasource

Datasource具体指的是数据库连接池。数据库连接池是利用池化的思想,把数据库连接存放在池中,在我们需要连接时,从池里获取连接;用完之后再放回池中,这样就避免了反复创建、销毁连接,提高程序的效率。

目前使用的第三方数据库连接池有三个:DBCP、c3p0、Druid。

DBCP:需要使用properties配置文件配置。

1
2
3
4
5
6
username=root
password=123456
url=jdbc:mysql://localhost:3306/40th
driverClassName=com.mysql.jdbc.Driver
connectionProperties=useSSL=false;characterEncoding=utf8
initialSize=10
1
2
3
4
5
6
7
// 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("dbcp.properties"));

// 获取连接
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();

c3p0:需要使用xml文件配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 在xml文件中,有一些特殊字符会有歧义,会报错,这些字符需要写转义字符 & > <
转义字符
& &amp;
< &lt;
> &gt;
-->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/40th?useSSL=false&amp;characterEncoding=utf8</property>
<property name="user">root</property>
<property name="password">123456</property>

<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>

</default-config>

</c3p0-config>

使用方式同DBCP,只是dataSource改为c3p0的。

1
dataSource = new ComboPooledDataSource();

Druid:需要使用properties配置文件配置。

1
2
3
4
username=root
password=123456
url=jdbc:mysql://localhost:3306/40th?useSSL=false&characterEncoding=utf8
driverClassName=com.mysql.jdbc.Driver

使用方式同DBCP,只是dataSource改为Druid的。

1
dataSource = DruidDataSourceFactory.createDataSource(properties);

DBUtils

DBUtils是一个可以帮助提高JDBC开发效率的框架。主要使用下面三个类。

DBUtils:可以关闭连接、提交事务等。

1
2
3
4
5
6
7
8
9
10
11
// 关闭连接
public static void close(Connection conn) throws SQLException;

// 关闭ResultSet
public static void close(ResultSet rs) throws SQLException;

// 关闭statement
public static void close(Statement stmt) throws SQLException;

// 提交并且关闭
public static void commitAndClose(Connection conn) throws SQLException;

QueryRunner:可以执行SQL语句并获取结果集。

1
2
3
4
5
6
7
8
// 构造无参的queryRunner
QueryRunner queryRunner = new QueryRunner();
// 构造QueryRunner
DataSource dataSource = DruidUtils.getDataSource();
QueryRunner queryRunner = new QueryRunner(dataSource);

// 获取增删改影响的行数
int affectedRows = queryRunner.update("update stu set name = ? where id = ?", "嘉靖皇帝", 4);

ResultSetHandler:可以帮助解析结果集,但本身是接口,实际用的是以下的具体实现。

// BeanHandler,把结果集中的第一行数据转化为一个JavaBean
Stu stu = queryRunner.query("select id,name,age,class as clazz from stu where id = ?",new BeanHandler<>(Stu.class),2);

// BeanListHandler,把结果集对象解析为一个 BeanList
List<Stu> stuList = queryRunner.query("select id,name,age,class as clazz from stu",new BeanListHandler<>(Stu.class));

// MapHandler,把结果集中的第一行记录解析为一个Map
Map<String, Object> map = queryRunner.query("select * from stu", new MapHandler());

// MapListHandler,把结果集中的记录 映射到一个 List<map<String,Object>> 对象中
List<Map<String, Object>> mapList = queryRunner.query("select * from  stu", new MapListHandler());

// ColumnListHandler,用来接收单列值的解析结果。
List<Object> list = queryRunner.query("select name from student", new ColumnListHandler<>());

// ScalarHandler,用来处理查询结果是单个值的情况。
Long count = queryRunner.query("select count(*) from student", new ScalarHandler<Long>());
本文作者:Ykuri98
本文链接:https://ykuri98.github.io/2022/05/16/%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%EF%BC%882022-05-10-2022-05-16%EF%BC%89/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×