学习总结(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;# 查找该列数据
关键字:
where:寻找符合条件的指定数据行。
distinct:过滤结果相同的记录。
limit:限制显示的行数。
as:别名,对查询结果的列和表起名。
order by:排序。可接desc表示降序。
group by:分组。可接having对分组后的结果进行过滤。
聚合函数:
max:最大值
min:最小值
avg:平均值
sum:总和
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 DriverManager.registerDriver(new Driver ()); String url = "jdbc:mysql://localhost:3306/40th?useSSL=false&characterEncoding=utf8" ; String username = "root" ; String password = "123456" ; Connection connection = DriverManager.getConnection(url, username, password); Statement statement = connection.createStatement(); int affectedRows = statement.executeUpdate("insert into stu values (4,'云飞兄',20,'358班')" );ResultSet resultSet = statement.executeQuery("select * from stu" ); 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); } 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 = connection.prepareStatement("select * from user where name = ? and password = ?" ); preparedStatement.setString(1 ,username); preparedStatement.setString(2 ,password);ResultSet resultSet = preparedStatement.executeQuery();
在安全性上,PreparedStatement要比Statement安全;在效率上,因为PreparedStatement需要与数据库通信两次,一次传输预编译的SQL语句,一次传输参数,所以相比于只通信一次的Statement要慢一些。
批处理 对SQL语句有三种批处理的方式:
for循环插入
1 2 3 4 5 6 7 8 9 10 11 12 13 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 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); } statement.executeBatch(); }
PreparedStatement批处理
需要在数据库的url后面加上配置rewriteBatchedStatements=true ,表示开启批处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void batchUsePrepapreStatement () throws SQLException { 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(); } preparedStatement.executeBatch(); }
由代码可知,三种方式与SQL服务器的通信次数、SQL语句的编译次数和执行次数如下表。
通信次数
编译次数
执行次数
时间
for循环
n
n
n
最长
Statement
1
n
n
次之
PreparedStatement
2
1
1
最短
然而通过实际测试发现在数据较大(>= 1000000)的情况下statement批处理的时间要长于for循环,可能原因是操作系统对内存的分配:在for循环中,每一次循环时操作系统不必去一直维护内存,在一次循环结束后可以将内存释放;而statement批处理时操作系统需要维护内存去记录sql语句,这导致了statement批处理的时间变长。
事务 事务是指组成事务的一组操作,这些操作要么一起操作成功,要么一起操作不成功。
事务具有四个特性:
原子性。事务是一个不可分割的操作单元,里面的一组操作要么都成功,要么都不成功。
一致性。事务必须使数据库从一个一致性状态到另一个一致性状态(比如无论怎么转账,所有账户的总金额应该不变)。
隔离性。事务与事务间应该互相隔离,互不影响。
持久性。一个事务一旦生效,对数据库的改变是永久的,即事务提交以后就无法回滚。
针对事务的隔离性,提出了事务的四个隔离级别:
读未提交
读已提交
可重复读
串行化
在这些隔离级别中,会出现如下三个隔离问题:
脏读。即一个事务读取到了另一个事务还未提交的数据。
不可重复读,即在同一个事务中读取同一个数据,前后读取的数据不一致。一般是一个事务读取到了另一个事务修改的数据。
虚幻读,即在同一个事务中读取同一个数据,前后读取的数据不一致。一般是一个事务读取到了另一个事务插入或删除的数据。
四个隔离级别在处理三个隔离问题的能力如下
脏读
不可重复读
虚幻读
读未提交(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 > <property name ="jdbcUrl" > jdbc:mysql://localhost:3306/40th?useSSL=false& 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;public static void close (ResultSet rs) throws SQLException;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 = new 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>());