数据库

数据库知识点总结

1. MySQL

1. 数据库的基本概念

1
2
数据库(DataBase,简称:DB),用于存储和管理数据的仓库。
类比:数据库:文件夹(仓库);表:文件(货架子); 数据:数据(货物)。

数据库的特点:

  1. 持久化存储数据,数据库就是一个文件系统
  2. 方便存储和管理数据
  3. 使用了统一的方式操作数据库 – SQL

2. MySQL配置操作

1
2
3
4
5
6
7
8
9
服务启动:
net start mysql:启动mysql的服务
net stop mysql:关闭mysql服务
登录:
mysql -u用户名 -p密码
mysql -hip地址 -u用户名 -p连接目标的密码
退出:
exit/quit
配置文件 my.ini

3. SQL概念

1
2
Structured Query Language:结构化查询语言。
SQL定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为“方言”。

SQL通用语法:

  1. SQL 语句可以单行或多行书写,以分号结尾。
  2. 可使用空格和缩进来增强语句的可读性。
  3. MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写。
  4. 注释:
    • 单行注释: – 注释内容 或 # 注释内容(mysql 特有)
    • 多行注释: /* 注释 */

SQL分类:

  • DDL(Data Definition Language)数据定义语言:用来定义数据库对象:数据库,表,列等。
  • DML(Data Manipulation Language)数据操作语言:用来对数据库中表的数据进行增删改。
  • DQL(Data Query Language)数据查询语言:用来查询数据库中表的记录(数据)。
  • DCL(Data Control Language)数据控制语言:用来定义数据库的访问权限和安全级别,创建用户。

4. DDL 定义数据库

操作数据库

操作 代码 备注
创建 create database 数据库名称 创建数据库
创建 create database if not exists 数据库名称 创建数据库,判断不存在,再创建
创建 create database 数据库名称 character set 字符集名称 创建数据库,并指定字符集
查询 show databases 查询所有数据库的名称
查询 show create database 数据库名称 查询某个数据库的字符集/创建语句
修改 alter database数据库名称 character set 字符集名称 修改数据库的字符集
删除 drop database 数据库名称 删除数据库
删除 drop database if exists 数据库名称 判断数据库存在,存在再删除
使用 select database() 查询当前正在使用的数据库名称
使用 use 数据库名称 使用数据库

注意:数据库的名称不可修改

操作表

操作 代码 备注
创建 create table 表名(列名1 数据类型1,....列名n 数据类型n); 创建一个表
创建 create table 表名 like 被复制的表名 复制表
查询 show tables 查询某个数据库中所有的表名称
查询 desc 表名 查询表结构
修改 alter table 表名 rename to 新的表名 修改表名
修改 alter table 表名 character set 字符集名称 修改表的字符集
修改 show create table 表名 查看表的字符集
修改 alter table 表名 add 列名 数据类型 添加一列
修改 alter table 表名 add 列名 数据类型 after 指定列名 在指定的列后添加一列
修改 alter table 表名 change 列名 新列别 新数据类型 修改列名称和数据类型
修改 alter table 表名 modify 列名 新数据类型 修改数据类型
修改 alter table 表名 drop 列名 删除列
删除 drop table 表名 删除表
删除 drop table if exists 表名 判断表存在,存在再删除

数据类型:

  1. int:整数类型 age int
  2. double:小数类型 score double(5,2)
  3. date:日期,只包含年月日 yyyy-MM-dd
  4. datetime:日期,包含年月日时分秒 yyyy-MM-dd HH:mm:ss
  5. timestamp:时间戳,包含年月日时分秒 yyyy-MM-dd HH:mm:ss(默认使用系统当前时间赋值)
  6. varchar:字符串 name varchar(20) (括号里数字为可接收最大字符数)

5. DML 增删改表中数据

操作 代码 备注
添加 insert into 表名 values (值1,值2,...值n) 给表中每一列赋值
添加 insert into 表名(列名1,列名2,...列名n)values(值1,值2,...值n) 选择地给表中某一列赋值
删除 delete from 表名 [where 条件] 删除符合条件的记录
删除 truncate table 表名 删除整张表
修改 update 表名 set 列名1 = 值1, 列名2 = 值2,... [where 条件] 修改指定列的数据

注意:

  1. 除了数字类型,其他类型需要使用引号(单双都可以)引起来,数值类型加引号也不会报错,但是不推荐加
  2. 若要删除整表,推荐使用truncate table,效率高;delete from 内部采用逐个删除,效率低
  3. 若要修改多条记录,可以指定条件数据范围in()

6. DQL 查询表中记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
语法格式:
select
字段列表
from
表名列表
where
条件列表
group by
分组字段
having
分组之后的条件
order by
排序
limit
分页限定

基础查询

  1. 多个字段的查询:select 字段名1,字段名2... from 表名;(*来替代查询所有字段)

  2. 去除重复:select distinct 字段名1,字段名2... from 表名;

  3. 计算列:一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算)

  4. null参与的运算,计算结果都为null。ifnull(表达式1,表达式2):

    表达式1:需要判断是否为null的字段;表达式2:若该字段为null后的替换值

    1. 起别名:as(as也可以省略)

条件查询

 1. where子句后跟条件
 2. 运算符
 * <> (相当于!=)
 * BETWEEN...AND  
 * IN( 集合) 

3. 模糊查询 LIKE:占位符:`_`:单个任意字符;`%`:多个任意字符(含0个)
4. IS NULL (是否为空,不能用=null判断) ;IS NOT NULL:不为空

排序查询

  1. 语法:order by 排序字段1 排序方式1 ,排序字段2 排序方式2...
  2. 排序方式:ASC:升序,默认的;DESC:降序
  3. 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件

聚合函数

将一列数据作为一个整体,进行纵向的计算,需要排除null值

  1. count:计算个数(count(*):只要该数据有一列不为空)
  2. max:计算最大值
  3. min:计算最小值
  4. sum:计算和
  5. avg:计算平均值

分组查询

语法:group by 分组字段;

要求:查询条件的字段只能是分组字段聚合函数

分页查询

  1. 语法:limit 要开始显示的索引,每页查询的条数;

  2. 公式:开始的索引 = (当前的页码 - 1) * 每页显示的条数

  3. limit 是一个MySQL”方言”

where 和 having 的区别?

  1. where 在分组之前进行限定,如果不满足条件,则不参与分组;having在分组之后进行限定,如果不满足结果,则不会被查询出来。
  2. where 后不可以跟聚合函数,having可以进行聚合函数的判断。

7. 约束

非空约束

1
not null,某一列的值不能为null
  1. 创建表时,添加非空约束:CREATE TABLE stu(id INT,NAME VARCHAR(20) NOT NULL);
  2. 创建表后,添加非空约束:ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL;
  3. 删除非空约束:ALTER TABLE stu MODIFY NAME VARCHAR(20);

唯一约束

1
unique,某一列的值不能重复,但可以有多个NULL值
  1. 创建表时,添加唯一约束:CREATE TABLE stu(id INT, NAME VARCHAR(20) UNIQUE );
  2. 创建表后,添加唯一约束:ALTER TABLE stu MODIFY NAME VARCHAR(20) UNIQUE;
  3. 删除唯一约束:ALTER TABLE stu DROP INDEX NAME;

主键约束

1
primary key,非空且唯一,一张表只能有一列的值设为主键,是表中记录的唯一标识,一般是数据无关列
  1. 创建表时,添加主键约束:CREATE TABLE stu(id INT PRIMARY KEY, NAME VACHAR(20));
  2. 创建表后,添加主键约束:ALTER TABLE stu MODIFY id INT PRIMARY KEY;
  3. 删除主键约束:ALTER TABLE stu DROP PRIMARY KEY;
  4. 主键约束添加自增长:id INT PRIMARY KEY AUTO_INCREMENT

外键约束

1
foreign key,让表于表产生关系,从而保证数据的正确性
  1. 创建表时,添加外键约束:

    constraint 外键名称 foreign key(外键列名称) references 主表名称(主键列名称)

    外键列名称为受约束的列名称,主键列名称为约束别人的列名称;

    外键名称可以自定义,起到删除时索引的作用 。一般格式:当前表名 _ 外键表名 _ fk

  2. 创建表之后,添加外键约束:ALTER TABLE stu ADD + ①

  3. 删除外键约束:ALTER TABLE stu DROP FOREIGN KEY 外键名称

  4. 级联操作:级联更新:ON UPDATE CASCADE ;级联删除:ON DELETE CASCADE

8. 数据库的设计

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
1. 一对一:实现方式:一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键。
2. 一对多(多对一):实现方式:在多的一方建立外键,指向一的一方的主键。
3. 多对多:实现方式:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键。
-- 旅游网案例展示:一个旅游线路分类中有多个旅游线路;一个用户收藏多个线路,一个线路被多个用户收藏。
/*创建旅游线路分类表 tab_category
cid 旅游线路分类主键,自动增长
cname 旅游线路分类名称非空,唯一,字符串 100
*/
CREATE TABLE tab_category (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(100) UNIQUE NOT NULL
);

/*创建旅游线路表 tab_route
rid 旅游线路主键,自动增长
rname 旅游线路名称非空,唯一,字符串 100
price 价格
rdate 上架时间,日期类型
cid 外键,所属分类
*/
CREATE TABLE tab_route(
rid INT PRIMARY KEY AUTO_INCREMENT,
rname VARCHAR(100) NOT NULL UNIQUE,
price DOUBLE,
rdate DATE,
cid INT,
FOREIGN KEY (cid) REFERENCES tab_category(cid)
);

/*创建用户表 tab_user
uid 用户主键,自增长
username 用户名长度 100,唯一,非空
password 密码长度 30,非空
name 真实姓名长度 100
birthday 生日
sex 性别,定长字符串 1
telephone 手机号,字符串 11
email 邮箱,字符串长度 100
*/
CREATE TABLE tab_user (
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) NOT NULL UNIQUE,
PASSWORD VARCHAR(30) NOT NULL,
NAME VARCHAR(100),
birthday DATE,
sex CHAR(1) DEFAULT '男',
telephone VARCHAR(11),
email VARCHAR(100)
);

/*创建收藏表 tab_favorite
rid 旅游线路 id,外键
date 收藏时间
uid 用户 id,外键
rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次
*/
CREATE TABLE tab_favorite (
rid INT,
FOREIGN KEY (rid) REFERENCES tab_route(rid),
DATE DATETIME,
uid INT,
FOREIGN KEY (uid) REFERENCES tab_user(uid),
PRIMARY KEY (rid,uid) -- 联合主键
);

9. 数据库设计范式

1
2
3
1. 第一范式(1NF):每一列都是不可分割的原子数据项
2. 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于码(在1NF基础上消除部分函数依赖)
3. 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)

几个概念:

  1. 函数依赖:A–>B,如果通过A属性(属性组)的值,可以确定唯一B属性的值,则称B依赖于A。
  2. 完全函数依赖:A–>B, 如果A是一个属性组,则B属性值的确定需要依赖于A属性组中所有的属性值。
  3. 部分函数依赖:A–>B, 如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可。
  4. 传递函数依赖:A–>B, B – >C . 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称 C 传递函数依赖于A。
  5. 码:一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码。

第一范式表(1NF):原子数据项

学号 姓名 系名 系主任 课程名称 分数
10010 张无忌 经济系 张三丰 高等数学 95
10010 张无忌 经济系 张三丰 大学英语 87
10010 张无忌 经济系 张三丰 计算机基础 65
10011 令狐冲 法律系 任我行 法理学 77
10011 令狐冲 法律系 任我行 大学英语 87
10011 令狐冲 法律系 任我行 法律社会学 65
10012 杨过 法律系 任我行 法律社会学 95
10012 杨过 法律系 任我行 法理学 97
10012 杨过 法律系 任我行 大学英语 99

存在问题:

  1. 存在非常严重的数据冗余(重复):姓名、系名、系主任
  2. 数据添加存在问题:添加新开设的系和系主任时,数据不合法
  3. 数据删除存在问题:张无忌同学毕业了,删除数据,会将系的数据一起删除。

第二范式表(2NF):消除部分函数依赖

学号 课程名称 分数 学号 姓名 系名 系主任
10010 高等数学 95 10010 张无忌 经济系 张三丰
10010 大学英语 87 10011 令狐冲 法律系 任我行
10010 计算机基础 65 10012 杨过 法律系 任我行
10011 法理学 77
10011 大学英语 87
10011 法律社会学 65
10012 法律社会学 95
10012 法理学 97
10012 大学英语 99

第三范式表(3NF):消除传递依赖

学号 课程名称 分数 学号 姓名 系名 系名 系主任
10010 高等数学 95 10010 张无忌 经济系 经济系 张三丰
10010 大学英语 87 10011 令狐冲 法律系 法律系 任我行
10010 计算机基础 65 10012 杨过 法律系
10011 法理学 77
10011 大学英语 87
10011 法律社会学 65
10012 法律社会学 95
10012 法理学 97
10012 大学英语 99

10. 数据库的备份和还原

备份: mysqldump -u用户名 -p密码 数据库名称 > 保存的路径

还原:登录数据库—>创建数据库—>使用数据库—>执行文件 source 文件路径

11. 多表查询

隐式内连接

使用where条件消除无用数据:SELECT * FROM emp,dept WHERE emp.dept_id = dept.id

显式内连接

表名1 [inner] join 表名2 on 条件:SELECT * FROM emp INNER JOIN dept ON emp.dept_id= dept.id

外链接查询

左外连接:查询左表所有数据以及其和右表交集部分

语法:select 字段列表 from 表1 left [outer] join 表2 on 条件

右外连接与左外连接正好相反:select 字段列表 from 表1 right [outer] join 表2 on 条件;

子查询

1
查询中嵌套查询,称嵌套查询为子查询。
  1. 子查询的结果是单行单列的:子查询可以作为条件,使用运算符去判断。
  2. 子查询的结果是多行单列的:子查询可以作为条件,使用in(结果)来判断。
  3. 子查询的结果是多行多列的:子查询可以作为一张虚拟表参与查询。

12. 事务

1
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。

基本操作:

  1. 开启事务: start transaction;
  2. 回滚:rollback;
  3. 提交:commit;

提交方式:

  • 自动提交:一条DML(增删改)语句会自动提交一次事务。(mysql)
  • 手动提交:需要先开启事务,再提交。(Oracle)
  • 修改事务的默认提交方式:
    • 查看事务的默认提交方式:SELECT @@autocommit; 1 代表自动提交,0 代表手动提交
    • 修改默认提交方式: set @@autocommit = 0;

四大特征ACID

  1. 原子性Atomic:是不可分割的最小操作单位,要么同时成功,要么同时失败。
  2. 一致性Consistent:事务操作前后,数据总量不变
  3. 隔离性Isolated:多个事务之间。相互独立。
  4. 持久性Duration:当事务提交或回滚后,数据库会持久化的保存数据。

隔离级别

1
2
3
4
多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题:
1. 脏读:一个事务,读取到另一个事务中没有提交的数据
2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样。
3. 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。

四种隔离级别

  1. read uncommitted:读未提交。产生的问题:脏读、不可重复读、幻读
  2. read committed:读已提交 (Oracle)。产生的问题:不可重复读、幻读
  3. repeatable read:可重复读 (MySQL默认)。产生的问题:幻读
  4. serializable:串行化。可以解决所有的问题

数据库查询隔离级别:select @@tx_isolation;

数据库设置隔离级别:set global transaction isolation level 级别字符串;

注意:隔离级别从小到大安全性越来越高,但是效率越来越低

13. DCL 控制用户权限

用户管理

操作 代码 备注
添加 CREATE USER '用户名'@'主机名'IDENTIFIED BY'密码' 创建一个用户
删除 DROP USER '用户名'@'主机名' 删除指定用户
修改 UPDATE USER SET PASSWORD = PASSWORD('新密码')WHERE USER ='用户名' 修改用户密码
修改 SET PASSWORD FOR '用户名'@'主机名'= PASSWORD('新密码') 修改用户密码
查询 切换到mysql数据库后,SELECT * FROM 用户名 查询用户信息

重置root用户密码:

  1. 停止mysql服务:管理员运行cmd – > net stop mysql
  2. 使用无验证方式启动mysql服务: mysqld –skip-grant-tables
  3. 打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功
  4. 切换到mysql数据库:use mysql;
  5. 重置密码:update user set password = password(‘你的新密码’) where user = ‘root’;
  6. 关闭两个窗口
  7. 打开任务管理器,手动结束mysqld.exe 的进程
  8. 启动mysql服务:net start mysql
  9. 使用新密码登录

权限管理

操作 代码 备注
查询 SHOW GRANTS FOR '用户名'@'主机名' 查询权限
授予 grant 权限列表 on数据库名.表名 to '用户名'@'主机名' 授予权限
撤销 revoke 权限列表 on数据库名.表名 from '用户名'@'主机名' 撤销权限

通配符: % 表示可以在任意主机使用用户登录数据库;localhost主机名仅限于本机使用。

给user用户授予所有权限,在任意数据库任意表上:

GRANT ALL ON * . * TO ‘user’ @ ‘%’;

2. JDBC

1. JDBC

1
2
Java DataBase Connectivity Java :数据库连接
本质:官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

操作步骤:

  1. 导入驱动jar包

    1. 复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
    2. 右键–>Add As Library
  2. 注册驱动(mysql5之后的驱动jar包可以省略注册驱动的步骤)

    Class.forName("com.mysql.jdbc.Driver");

  3. 获取数据库连接对象 Connection

    Connection conn = DriverManager.getConnection(String url, String user, String password);

  4. 定义sql语句

    String sql = "update account set balance = 500 where id = 1";

  5. 获取执行sql的对象 Statement
    Statement stmt = conn.createStatement();

  6. 执行sql
    int count = stmt.executeUpdate(sql);

  7. 处理结果

    System.out.println(count);

  8. 释放资源
    stmt.close(); conn.close();

Connection 数据库连接对象

1
2
3
Connection conn = DriverManager.getConnection(String url, String user, String password);
url是指定的连接路径,例如jdbc:mysql://localhost:3306/数据库名称;
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则可以简写为jdbc:mysql:///数据库名称

功能:

  • 获取执行sql 的对象:
    • Statement createStatement()
    • PreparedStatement prepareStatement(String sql)
  • 管理事务:
    • 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
    • 提交事务:commit()
    • 回滚事务:rollback()

Statement 执行sql的对象

  1. boolean execute(String sql):可以执行任意的sql
  2. int executeUpdate(String sql):执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句。返回值为影响的行数,可以通过这个影响的行数判断DML语句是否执行成功:返回值>0的则执行成功,反之,则失败。
  3. ResultSet executeQuery(String sql):执行DQL(select)语句

ResultSet 结果集对象

  1. boolean next():游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true。

  2. getXxx(参数):获取数据,XXX如:int getInt(),String getString()

    参数可以是int,代表列的编号,从1开始;也可以是String,代表列名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码展示,展示指定表中数据
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection("jdbc:mysql:///db_day01", "root", "root");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from dept");)
{
while (rs.next()) {
String DEPTON = rs.getString("DEPTNO");
String DNAME = rs.getString("DNAME");
String LOC = rs.getString("LOC");
System.out.println(DEPTON + "---" + DNAME + "---" + LOC);
}
} catch (SQLException e) {
e.printStackTrace();}}

2. JDBCUtils 抽取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
40
41
42
43
44
//JDBCUtils 代码展示
public class JDBCUtils {
private static String url; //配置文件的四个参数
private static String user;
private static String password;
private static String driver;
static{ //静态代码块,完成各个参数的赋值,省略了try...catch语句
Properties pro = new Properties();
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream
("jdbc.properties"); //通过类加载器获得配置文件路径
pro.load(is);
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
Class.forName(driver);}
//获取Connection对象的方法
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
//释放资源的方法,两种重载形式
public static void close(Statement stmt,Connection conn){
if( stmt != null){
try {stmt.close();
} catch (SQLException e) {
e.printStackTrace();}}
if( conn != null){
try {conn.close();
} catch (SQLException e) {
e.printStackTrace();}}}

public static void close(ResultSet rs, Statement stmt, Connection conn){
if( rs != null){
try {rs.close();
} catch (SQLException e) {
e.printStackTrace();}}
if( stmt != null){
try {stmt.close();
} catch (SQLException e) {
e.printStackTrace();}}
if( conn != null){
try {conn.close();
} catch (SQLException e) {
e.printStackTrace();}}}}

3. 用户登录案例与sql注入问题

1
在拼接sql时,有一些sql的特殊关键字参与字符串的拼接,会造成安全性问题。PreparedStatement对象可以解决这个问题,预编SQL语句时使用“?”作为占位符,可以防止SQL注入,同时效率更高。
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
40
41
42
43
// 使用JDBCUtils完成登录案例代码展示
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
boolean flag = login(username, password);
if (flag) {
System.out.println("登录成功");
}else{
System.out.println("用户名或密码错误");
}}

public static boolean login(String username, String password){
if (username == null || password == null) {
return false;
}
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
/*特殊关键字拼接sql时会造成安全性问题,例如密码:a' or 'a' = 'a
String sql = "select * from user where
username = '"+username+"' and password = '"+password+"' ";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
*/
//使用PreparedStatement对象来解决,参数使用?作为占位符
String sql = "select * from user where username = ? and password = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); //setXXX方法,参数1为第几个问号的位置,参数2为赋值
pstmt.setString(2, password);
rs = pstmt.executeQuery();

return rs.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, pstmt, conn);
}
return false;}

4. 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
40
41
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//执行张三sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
//执行李四sql
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) { //发生任何异常,事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}

5. 数据库连接池

1
本质是一个存放数据库连接的容器(集合)。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

好处:针对数据库连接对象的复用,可以节约创建以及销毁数据库连接对象的资源,提高用户访问效率。

标准接口:DataSource

常用方法:获取连接:getConnection();归还连接:Connection.close()

C3P0 数据库连接池技术

1
2
3
4
5
6
7
//使用步骤:
//1. 导入jar包:【c3p0-0.9.5.2.jar】和【mchange-commons-java-0.2.12.jar】
//2. 定义配置文件:【c3p0.properties】或【c3p0-config.xml】直接放在src目录下
//3. 创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//4. 获取连接对象
Connection conn = ds.getConnection();

Druid 数据库连接池技术

1
2
3
4
5
6
7
8
9
10
11
//使用步骤:
//1. 导入jar包【druid-1.0.9.jar】
//2. 定义配置文件:properties文件,可以叫任意名称,放在任意目录下
InputStream is = Demo.class.getClassLoader().getResourceAsStream("druid.properties");
//3. 加载配置文件:
Properties pro = new Properties();
pro.load(is);
//4. 获取数据库连接池对象:通过工厂来来获取
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5. 获取连接:
Connection conn = ds.getConnection();
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
//代码展示:根据Druid技术,定义工具类JDBCUtils
private static DataSource ds;
//静态代码块中加载配置文件,生成DataSource对象
static{
Properties pro = new Properties();
pro.load
(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
}
//返回DataSource对象的方法,用于SpringJDBC
public static DataSource getDataSource(){
return ds;
}
//返回Connection对象的方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//两个重载的归还连接方法
public static void close(Statement stmt, Connection conn) {
close(null, stmt, conn);
}
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {rs.close();
} catch (SQLException e) {
e.printStackTrace();}}
if (stmt != null) {
try {stmt.close();
} catch (SQLException e) {
e.printStackTrace();}}
if (conn != null) {
try {conn.close();
} catch (SQLException e) {
e.printStackTrace();}}}

6. Spring JDBC

1
Spring框架对JDBC的简单封装,提供了一个JDBCTemplate对象简化JDBC的开发。

常用方法:

  • 使用数据源DataSource,创建JdbcTemplate对象:

    JdbcTemplate template = new JdbcTemplate(ds);

  • update():执行DML增、删、改语句:

    int count = template.update(sql);

  • queryForMap():将列名作为key,值作为value,将一条记录封装为一个map集合

    Map<String, Object> map = template.queryForMap(sql);

  • queryForList():将每一条记录封装为一个Map集合,再将Map集合装载到List集合中

    List<Map<String, Object>> list = template.queryForList(sql);

  • query():查询结果,将结果封装为JavaBean对象

    List<T> list = template.query(sql, new BeanPropertyRowMapper<T>(T.class));

  • queryForObject:查询结果,将结果封装为对象,一般用于获取单行单列的数值数据

    String count = template.queryForObject(sql, String.class);

3. Redis

1. Redis

1
Redis是用C语言开发的一个开源的高性能键值对的NOSQL(Not Only SQL)非关系型数据库,通过将数据缓存于内存中,提升程序的读写效率。官方测试:50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s。

NOSQL和关系型数据库

比较 非关系型数据库 关系型数据库
成本 基本开源,部署简单 价格高昂
性能 不支持事务处理,不经过SQL层解析,性能高 事务支持,安全性高,可实现多表复杂查询
查询速度 数据存储于缓存中,速度快 数据存储于硬盘中,速度慢
拓展性 数据间没有耦合,容易水平拓展 多表查询机制导致拓展艰难
维护性 新技术维护工具和资料有限 长时间积累,工具丰富
存储格式 格式丰富(value、文档、图片等等) 只支持基础类型
学习成本 没有统一工业标准,学习成本较高 支持sql标准,技能易迁移

对于这两类数据库,对方的优势就是自己的弱势,反之亦然。两者是互补的关系,一般会将全部数据存储在关系型数据库中,然后在nosql数据库中备份存储不常变化,使用频率高的数据。

主流的NOSQL产品
  • 键值(Key-Value)存储数据库:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
  • 列存储数据库:Cassandra, HBase, Riak
  • 文档型数据库:CouchDB、MongoDB
  • 图形(Graph)数据库:Neo4J、InfoGrid、Infinite Graph

Redis特点

Redis速度快的原因:

  • 因为Redis使用C语言开发 C语言可以直接操作内存
  • Redis 是内存(JVM)操作内存(redis数据库)
  • Redis多路复用的IO
  • Redis是单线程的,避免了并发和锁对性能的消耗

Redis支持的键值数据类型:

  • 字符串类型 string
  • 哈希类型 hash
  • 列表类型 list:linkedlist格式,支持重复元素
  • 集合类型 set:不允许重复元素
  • 有序集合类型 sortedset:不允许重复元素,且元素有顺序

Redis命令操作

  • 字符串类型(String)

    1. 存储:set key value
    2. 获取:get key
    3. 删除:del key
  • 哈希类型(hash)

    1. 存储:hset key field value

    2. 获取:hget key field:获取指定的field对应的值

      hgetall key:获取所有的field和value

    3. 删除:hdel key field

  • 列表类型(list)

    1. 添加:lpush key value:将元素加入列表左表

      rpush key value:将元素加入列表右边

    2. 获取:lrange key start end:start为起始位置,end为结束位置(0 -1可获取所有元素)

    3. 删除:lpop key:删除列表最左边的元素,并将元素返回

      rpop key:删除列表最右边的元素,并将元素返回

  • 集合类型(set):不允许重复元素

    1. 存储:sadd key value:将一个value放入key集合中
    2. 获取:smembers key:获取set集合中所有元素
    3. 删除:srem key value:删除set集合中的某个元素
  • 有序集合类型(sortedset) :不允许重复元素,且元素有顺序。

    每个元素都会关联一个double类型的分数,redis通过该分数来为集合中的成员进行从小到大的排序。

    1. 存储:zadd key score value
    2. 获取:zrange key start end [withscores]
    3. 删除:zrem key value
  • 通用命令:

    1. 获取所有的键:keys *
    2. 获取键对应的value类型:type key
    3. 删除指定的key-value:del key

Redis持久化机制

  • RDB:在一定的间隔时间中,检测key的变化情况,然后持久化数据。为Redis默认持久化方式,通过编辑redis.windwos.conf文件可以自定义持久化参数:

    save 900 1:after 900 sec (15 min) if at least 1 key changed

    save 60 10000:after 60 sec if at least 10000 keys changed

  • AOF:日志记录的方式,可以记录每一条命令的操作。触发设定的动作后,持久化数据,通过编辑redis.windwos.conf文件可以自定义持久化参数:

    appendonly no(关闭aof) –> appendonly yes(开启aof)

    appendfsync always:每一次操作都进行持久化

    appendfsync everysec:每隔一秒进行一次持久化

    appendfsync no:不进行持久化

2. Jedis

1
一款java操作redis数据库的工具。

Jedis方法:

  • 获取连接:Jedis jedis = new Jedis("localhost",6379);
  • 操作:jedis.set("username","zhangsan");
  • 关闭连接:jedis.close();
  • 指定过期时间:jedis.setex("activecode",20,"hehe");
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
//jedis连接池
public class JedisPoolUtils {
private static JedisPool jedisPool;
static{
//读取配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().
getResourceAsStream("jedis.properties");
//创建Properties对象
Properties pro = new Properties();
//关联文件
try {
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据,设置到JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
// 将字符串转化为int 使用Integer.parseInt("")
config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
//初始化JedisPool
jedisPool = new JedisPool(config,pro.getProperty("host"),
Integer.parseInt(pro.getProperty("port")));
}
//获取连接方法
public static Jedis getJedis(){
return jedisPool.getResource();
}
}

4. Oracle

1. Oracle

1
ORACLE数据库是目前最流行的C/S或B/S体系结构的关系型数据库,具有完整的数据管理和分布式处理的功能。

数据库:Oracle数据库是一个操作系统只有一个库,可以看做是Oracle就只有一个大数据库。

实例:一个Oracle实例(Oracle Instance)有一系列的后台进程(Backguound Processes)和内存结构 (Memory Structures)组成。一个数据库可以有n个实例。

用户:用户在实例下建立,不同实例可以创建相同名字的用户。

表空间:表空间是Oracle对物理数据库上相关数据文件(ORA或者DBF文件)的逻辑映射。每个表空间由同一磁盘上的一个或多个文件组成,这些文件叫数据文件(datafile)。一个数据文件只能属于一个表空间。

数据文件:数据文件(dbf、ora)是数据库的物理存储单位,一个表空间可以由一个或多个数据文件组成,一个数据文件只能属于一个表空间。一旦数据文件被加入到某个表空间后,就不能删除这个文件,如果要删除某个数据文件,只能删除其所属于的表空间才行。

oracle通过用户和表空间对数据进行管理和存放,但是表不是由表空间去查询的,而是由用户去查询。不同用户可以在同一个表空间建立同一个名字的表,同名的表通过不同用户来区分。

创建表空间

1
2
3
4
5
create tablespace itcast   	//指定表空间名称
datafile 'c:\itcast.dbf' //指定表空间对应的数据文件
size 100m //指定表空间的初始大小
autoextend on //当表空间存储都占满时,自动增长
next 10m //指定一次自动增长的大小

创建用户

1
2
3
4
create user itcastuser		//指定用户名 
identified by itcast //指定用户密码
default tablespace itcast //指定表空间名称
grant dba to itcastuser //为用户赋予权限

Oracle中三个重要的角色:connect角色(用户),resource角色(开发人员),dba角色(最高权限)。

表的管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
创建person表: 
create table person(
pid number(10),
name varchar2(10),
gender number(1) default 1,
birthday date
);
表删除:
DROP TABLE 表名
表的修改:
在person表中增加列address:
alter table person add(address varchar2(10));
把person表的address列的长度修改成20长度:
alter table person modify(address varchar2(20));

数据的管理

1
2
3
4
5
6
INSERT(增加):
INSERT INTO表名[(列名1,列名2,...)]VALUES(值1,值2,...)
UPDATE(修改):
UPDATE 表名 SET 列名1=值1,列名2=值2,....WHERE 修改条件;
DELETE(删除):
语法 : DELETE FROM 表名 WHERE 删除条件;

序列

1
2
3
4
创建序列:
CREATE SEQUENCE 序列名
select seqpersonid.nextval from dual 取得序列的下一个内容
select seqpersonid.currval from dual 取得序列的当前内容

其他函数

空值处理nvl:由于null和任何数值计算都是null,通过nvl处理,可以将null转换为0,nvl(comm,0)

Rownum与分页查询

1
2
3
4
5
6
----emp表工资倒叙排列后,每页五条记录,查询第二页。
select * from(
select rownum rn, tt.* from(
select * from emp order by sal desc
) tt where rownum<11
) where rn>5

Oracle视图

1
2
3
4
视图就是封装了一条复杂查询的语句,提供一个查询的窗口,所有数据来自于原表。
语法:CREATE OR REPLACE VIEW 视图名称 AS 子查询 with read only
例如:create view v_emp as select ename, job from emp with read only;
作用:可以屏蔽掉一些敏感字段,保证总部和分部数据及时统一。

索引

1
2
3
4
5
6
7
索引就是在表的列上构建一个二叉树 B+Tree,达到大幅度提高查询效率的目的,但是索引会影响增删改的效率。
单列索引:create index idx_ename on emp(ename); 触发条件:必须是索引列中的原始值
复合索引:create index idx_enamejob on emp(ename, job); 触发条件:能优先检索列中的原始值
使用原则:
1. 在大表上建立索引才有意义
2. 在where子句后面或者是连接条件上的字段建立索引
3. 表中数据修改频率高时不建议建立索引

2. pl/sql语言

1
PL/SQL(Procedure Language/SQL)是Oracle对sql语言的过程化扩展,在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力,结合了SQL语言的数据操纵能力与过程语言的数据处理能力。

基本语法

1
2
3
4
5
6
7
8
程序语法:
declare
说明部分 (变量说明,游标申明,例外说明)
begin
语句序列 (DML语句)…
exception
例外处理语句
End;
变量的类型

基本类型:char, varchar2, date, number, boolean, long

引用变量:Myname emp.ename%type; 在sql中使用into来赋值

记录型变量:e1 emp%rowtype

if分支
1
2
3
4
5
6
7
8
9
10
11
12
13
语法1:
IF 条件 THEN 语句1;
语句2;
END IF;
语法2:
IF 条件 THEN 语句序列1;
ELSE 语句序列 2;
END IF;
语法3:
IF 条件 THEN 语句;
ELSIF 语句 THEN 语句;
ELSE 语句;
END IF;
LOOP循环语句
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
--while循环
declare
i number(2) := 1;
begin
while i < 11 loop
dbms_output.put_line(i);
i := i + 1;
end loop;
end;

--exit循环
declare
i number(2) := 1;
begin
loop
exit when i > 10;
dbms_output.put_line(i);
i := i + 1;
end loop;
end;

--for循环
declare

begin
for i in 1..10 loop
dbms_output.put_line(i);
end loop;
end;
游标
1
2
3
4
5
6
7
8
游标可以存储查询返回的多条数据
游标的创建:
cursor c1 is select ename from emp;
游标使用步骤:
打开游标: open c1; (打开游标执行查询)
取一行游标的值:fetch c1 into pjob; (取一行到变量中)
关闭游标: close c1;(关闭游标释放资源)
游标的结束方式 exit when c1%notfound
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
范例:按员工的工种涨工资,总裁1000元,经理涨800元其,他人员涨400元
create table myemp as select * from emp;
declare
cursor pc is select * from myemp;
addsal myemp.sal%type;
pemp myemp%rowtype;
begin
open pc;
loop
fetch pc into pemp;
exit when pc%notfound;
if pemp.job = 'PRESIDENT' then
addsal := 1000;
elsif pemp.job = 'MANAGER' then
addsal := 800;
else addsal := 400;
end if;
update myemp t set t.sal = t.sal + addsal where t.empno = pemp.empno;
end loop;
close pc;
end;

3. 存储过程

1
存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。
1
2
3
4
5
6
7
8
9
10
11
12
创建存储过程:
create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)]
AS
begin
PLSQL子程序体;
End;

调用存储过程:
begin
-- Call the procedure
过程名;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建存储过程,通过工号求年薪
create or replace procedure p_yearsal(eno emp.empno%type, yearsal out number)
is
s number(10);
c emp.comm%type;
begin
select sal*12,nvl(comm,0) into s,c from emp where empno = eno;
yearsal := s+c;
end;
-- 调用存储过程
declare
yearsal number(10);
begin
p_yearsal(7788,yearsal);
dbms_output.put_line(yearsal);
end;

4. 存储函数

1
2
3
4
5
create or replace function 函数名(Name in type, Name in type, ...) return 数据类型 is
结果变量 数据类型;
begin
return(结果变量);
end函数名;
1
2
3
4
5
6
7
8
9
10
11
--使用存储函数来实现提供一个部门编号,输出一个部门名称
create table dept as select * from scott.dept;
create or replace function fdna(dno dept.deptno%type) return dept.dname%type
is
dna dept.dname%type;
begin
select dname into dna from dept where deptno = dno;
return dna;
end;
-- 调用存储函数
select e.ename, fdna(e.deptno) from emp e;

存储过程和存储函数的区别
存储过程和函数的区别在于函数可以有一个返回值;而过程没有返回值。
但过程和函数都可以通过out指定一个或多个输出参数。利用out参数可以在过程和函数中实现返回多个值。

5. 触发器

1
数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 禁止给员工降薪的触发器
create or replace trigger t2
before
update
on emp
for each row
declare

begin
if :old.sal>:new.sal then
raise_application_error(-20001,'不能给员工降薪');
end if;
end;

select * from emp where empno = 7788;
update emp set sal = sal-1 where empno = 7788;
commit;

6. Java程序调用存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void javaCallProcedure() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.44.130:1521:orcl", "itcastuser", "itcast");
CallableStatement call = connection.prepareCall("{call p_yearsal(?,?)}");
call.setObject(1, 7788);
call.registerOutParameter(2, OracleTypes.NUMBER);
call.execute();
System.out.println(call.getObject(2));
call.close();
connection.close();
}

5. Lucene

1. 全文检索

1
2
3
结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。通常都是使用sql语句进行查询,能很快的得到查询结果。
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等磁盘上的文件。非结构化数据查询方法:(1)顺序扫描法(Serial Scanning)(2)全文检索(Full-text Search)。全文检索技术通过先建立索引,再对索引进行搜索。虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。
Lucene:是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

2. 创建索引

索引库的结构

关系型数据库 索引库
库/表 index
一行记录 Document 文档
字段 Field 域

每个Document可以有多个Field,每个文档都有一个唯一的编号,就是文档的id。

分析文档

1
将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term。

创建索引

1
创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构,最终可实现只搜索被索引的语汇单元从而找到Document(文档)。倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。

代码演示

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
public void createIndex() throws IOException {
//创建一个Director对象,指定索引库保存的位置。
Directory directory = FSDirectory.open(new File("C:\\index").toPath());
//基于Directory对象创建一个IndexWriter对象
IndexWriterConfig config = new IndexWriterConfig();
IndexWriter indexWriter = new IndexWriter(directory, config);
//读取磁盘上的文件,对应每个文件创建一个文档对象。
File dir = new File(("C:\\searchsource"));
for (File f : dir.listFiles()) {
//获取文件的各项属性
String fileName = f.getName();
String fileContent = FileUtils.readFileToString(f);
String filePath = f.getPath();
long fileSize = FileUtils.sizeOf(f);
//将文件的各项属性存入域中
Field fileNameField = new TextField("filename", fileName, Field.Store.YES);
Field fileContentField = new TextField("content", fileContent, Field.Store.YES);
Field filePathField = new TextField("path", filePath, Field.Store.YES);
Field fileSizeField = new TextField("size", fileSize + "", Field.Store.YES);
//向文档对象中添加域
Document document = new Document();
document.add(fileNameField);
document.add(fileContentField);
document.add(filePathField);
document.add(fileSizeField);
//把文档对象写入索引库
indexWriter.addDocument(document);
}
//关闭indexWriter对象
indexWriter.close();
}

3. 查询索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void searchIndex() throws IOException {
//创建一个Directory对象,也就是索引库存放的位置
Directory directory = FSDirectory.open(new File("C:\\index").toPath());
//创建一个indexReader对象,需要指定Directory对象
IndexReader indexReader = DirectoryReader.open(directory);
//创建一个indexSearcher对象,需要指定IndexReader对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建一个TermQuery对象,指定查询的域和查询的关键词
Query query = new TermQuery(new Term("content", "apache"));
//执行查询,查询结果返回的最大值为5
TopDocs topDocs = indexSearcher.search(query, 5);
//返回查询结果。遍历查询结果并输出。
System.out.println("查询结果的总条数:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("filename"));
System.out.println(document.get("content"));
System.out.println(document.get("size"));
System.out.println("-------------------------");
}
//关闭IndexReader对象
indexReader.close();
}

4. 分词器

IKAnalyzer 中文分词器

使用方法:

  1. 把jar包添加到工程中

  2. 把配置文件和扩展词典和停用词词典添加到classpath下(禁止使用windows记事本编辑扩展词典文件)

  3. 使用自定义分词器

    1
    2
    3
    4
    5
    6
    >    public void createIndex() throws IOException {
    > Directory directory = FSDirectory.open(new File("C:\\index").toPath());
    > //基于Directory对象创建一个IndexWriter对象
    > IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    > IndexWriter indexWriter = new IndexWriter(directory, config);}
    >

5. Field域的属性

Field类 数据类型 Analyzed Indexed Stored Case
StringField 字符串 N Y Y/N 身份证号
LongPoint Long型 Y Y N
StoredField 支持多种类型 N N Y 文件路径
TextField 字符串或流 Y Y Y/N 知网文章

是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。

是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。

是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取

6. 索引库维护

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
@Test
public void test() throws Exception {
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
Document document = new Document();
//索引库的添加
document.add(new TextField("filename", "新添加的文档", Field.Store.YES));
document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO));
document.add(new LongPoint("size", 1000l)); //LongPoint创建索引
document.add(new StoredField("size", 1000l)); //StoreField存储数据
document.add(new StoredField("path", "d:/temp/1.txt")); //不需要创建索引的用StoreField
indexWriter.addDocument(document); //添加文档到索引库
//索引库的删除
indexWriter.deleteAll(); //删除全部索引
Query query = new TermQuery(new Term("filename", "apache")); //创建一个查询条件
indexWriter.deleteDocuments(query); //根据查询条件删除
//索引库的修改(原理就是先删除后添加)
indexWriter.updateDocument(new Term("content", "java"), document);
//索引库的查询
//TermQuery不使用分析器所以建议匹配不分词的Field域查询
Query query = new TermQuery(new Term("content", "lucene"));
TopDocs topDocs = indexSearcher.search(query, 10);
//newRangeQuery数值范围查询
Query query = LongPoint.newRangeQuery("size", 0l, 10000l);
//使用queryparser查询,第一个参数默认搜索的域,第二个参数就是分析器对象
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("Lucene是java开发的");
}