JavaWeb_DB

安装和运行MySQL:
​⁠⁠‍‌​​​‌​​‬​​​‍​⁠​​​​​​‍‌​‬​‍⁠​‌‌​​​​​​MySQL安装文档 - 飞书云文档
现在已经安装好了。
运行步骤:

1
2
3
4
mysql -uroot -p[YourKey]
or
mysql -uroot -p
-- 然后在命令行中输入密码后回车

or

1
mysql -u用户名 -p密码 [-h数据库服务器的IP地址 -P端口号]

-h参数不加,则默认连接的是本地127.0.0.1MySQL服务器
-P参数不加,默认连接端口号是3306

退出:

1
exit

数据模型

  • 关系型数据库
    基于二维表存储数据的数据库就成为关系型数据库,不是基于二维表存储数据的数据库,就是非关系型数据库(比Redis,就属于非关系型数据库)。

在Mysql数据库服务器当中存储数据,你需要:

  1. 先去创建数据库(可以创建多个数据库,之间是相互独立的)
  2. 在数据库下再去创建数据表(一个数据库下可以创建多张表)
  3. 再将数据存放在数据表中(一张表可以存储多行数据)

SQL语句

DDL

DDL语句是数据定义语句,查询、创建、使用、删除。
查询数据库

1
show databases;


查询当前数据库

1
select database()

  • 要操作某一个数据库,需要切换到对应数据库中

创建数据库

1
create database name;

  • 同一个数据库服务器中,数据库名称不可重复
1
create database [ if not exists ] 数据库名  [default charset utf8mb4];

使用数据库(切换数据库)

1
use name;

删除数据库

1
drop database [if exists] name;

上述语法中的database可以替换为schema

创建表

1
2
3
4
5
6
create table  表名(
字段1 字段1类型 [约束] [comment 字段1注释 ],
字段2 字段2类型 [约束] [comment 字段2注释 ],
......
字段n 字段n类型 [约束] [comment 字段n注释 ]
) [ comment 表注释 ] ;

如果我们直接手动插入数据,虽然我们一开始设置了id是唯一标识,但是并没有加约束,所以这个唯一标识不过是应该备注信息(给人看罢了)。

约束

约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。

auto_increment关键字:
每次插入新的行记录时,数据库自动生成id字段下的值
具有auto_increment的数据列是一个正数序列开始增长

1
2
3
4
5
6
7
create table tb_user (
id int primary key auto_increment comment 'ID,唯一标识', #主键自动增长
username varchar(20) not null unique comment '用户名',
name varchar(10) not null comment '姓名',
age int comment '年龄',
gender char(1) default '男' comment '性别'
) comment '用户表';

数据类型


查询数据库表:
查看指定的表结构

1
2
3
4
5
6
7
8
-- 查询当前数据库的所有表
show tables;

-- 查看指定的表结构
desc 表名 ; -- 可以查看指定表的字段、字段的类型、是否可以为NULL、是否存在默认值等信息

-- 查询指定表的建表语句
show create table 表名 ;

修改数据库表

1
2
3
4
5
6
-- 添加字段
alter table 表名
add 字段名 类型(长度) [comment 注释] [约束];

-- 比如: 为tb_emp表添加字段qq,字段类型为 varchar(11)
alter table tb_emp add qq varchar(11) comment 'QQ号码';

修改字段:

1
2
3
4
5
-- 修改字段类型
alter table 表名 modify 字段名 新数据类型(长度);

-- 比如: 修改qq字段的字段类型,将其长度由11修改为13
alter table tb_emp modify qq varchar(13) comment 'QQ号码';
1
2
3
4
5
-- 修改字段名,字段类型
alter table 表名 change 旧字段名 新字段名 类型(长度) [comment 注释] [约束];

-- 比如: 修改qq字段名为 qq_num,字段类型varchar(13)
alter table tb_emp change qq qq_num varchar(13) comment 'QQ号码';

删除字段

1
2
3
4
5
-- 删除字段
alter table 表名 drop 字段名;

-- 比如: 删除tb_emp表中的qq_num字段
alter table tb_emp drop qq_num;

修改表名:

1
2
3
4
5
-- 修改表名
rename table 表名 to 新表名;

-- 比如: 将当前的emp表的表名修改为tb_emp
rename table emp to tb_emp;

删除表结构

1
2
3
4
5
-- 删除表
drop table [ if exists ] 表名;

-- 比如:如果tb_emp表存在,则删除tb_emp表
drop table if exists tb_emp; -- 在删除表时,表中的全部数据也会被删除。

DML语句(数据操作语言)

对数据库中表的数据进行增删改
增加:

  • 向指定字段添加数据
1
insert into 表名 (字段名1, 字段名2) values (值1, 值2);
  • 向全部字段添加数据
1
insert into 表名 values (值1, 值2, ...);
  • 批量添加数据
1
2
3
insert into 表名 (字段名1, 字段名2) values (值1, 值2), (值1, 值2);

insert into 表名 (值1,值2,...),(值1,值2,...);

修改

1
update 表名 set 字段名1 = 值1 , 字段名2 = 值2 , .... [where 条件] ;

删除

1
delete from 表名  [where  条件] ;

DQL 语句(数据查询语句)

用于查询数据库表中的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
字段列表
FROM
表名列表
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后条件列表
ORDER BY
排序字段列表
LIMIT
分页参数

这里只说明一些之前在sql sever没有使用过的

分页查询

1
select  字段列表  from  表名  limit  起始索引, 查询记录数 ;

Java操作数据库

需要用到 JDBC :使用Java语言操作关系型数据库的API
但是这个接口太底层了,所以我们一般会使用JDBC封装的高级框架,比如:Mybatis,MybatisPlus,Hibernate,SpringDataJPA

JDBC


入门程序

基于JDBC实现用户登陆功能:
本质就是基于JDBC程序,执行select语句,并将查询的结果输出到控制台。

1
select * from user where username='daqiao' and password='123456';

首先创建一个数据库Web,并创建user表。
用于我们登陆时查找用户名和密码是否匹配。

然后在pom.xml中添加需要使用的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<!-- MySQL JDBC driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
</dependencies>

第一个是MySQL JDBC驱动依赖。

  • 用于连接和操作MySQL数据库
    Java需要通过JDBC驱动作为桥梁,才可以与MySQL数据库通信

第二个是用于测试。

  • 编写和运行单元测试

我们要实现一个查询,需要有以下几个步骤:

  • 获取连接
1
Connection conn=DriverManager.getConnection("jdbc:mysql://localhose:3306/web","root","yourKey");

如果不希望将用户名和密码暴露,且这两个值应该是动态的(页面传递给服务端–用户输入–不同用户值不同)
我们可以基于JUnit中的参数化测试进行单元测试

  • @ParameterizedTest参数化测试
  • @CsvSource提供测试参数
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
public class JDBCTest {

/**
* 编写JDBC程序, 查询数据
*/
@ParameterizedTest
@CsvSource({"daqiao,123456"})
public void testJdbc(String _username, String _password) throws Exception {
// 获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");
// 创建预编译的PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");
// 设置参数
pstmt.setString(1, _username); // 第一个问号对应的参数
pstmt.setString(2, _password); // 第二个问号对应的参数
// 执行查询
ResultSet rs = pstmt.executeQuery();
// 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String uName = rs.getString("username");
String pwd = rs.getString("password");
String name = rs.getString("name");
int age = rs.getInt("age");

System.out.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);
}
// 关闭资源
rs.close();
pstmt.close();
conn.close();
}

}
  • 创建预编译的PreparedStatement对象
1
PreparedStatement pstmt=conn.prepareStatement("select * from user where username=? and password=?");

在编写sql语句有两种方式:

  1. 静态sql(参数硬编码–写死了)

  2. 预编译sql(参数动态传递)
    参数使用 ? 进行占位

预编译sql好处:
防止sql注入,性能更高

  • 设置参数:
1
2
pstmt.setString(1,"daqiao");
pstmt.setString(2,"123456");

设置好参数之后,我们的查询语句就完整了

  • 执行查询
1
ResultSet rs=pstmt.executeQuery();

这里的ResultSet封装了DQL查询语句查询的结果

  • 处理结果集(我们获取到的是一个元组?
1
2
3
4
5
6
7
8
while(rs.next()){
int id=rs.getInt("id");
String uName = rs.getString("username");
String pwd = rs.getString("password");
String name = rs.getString("name");
int age = rs.getInt("age");
System._out_.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);
}

对查询结果集的操作:
next() 将光标从当前位置向前移动一行,并判断当前行是否为有效行,返回值为boolean.
getXxx()获取数据,可根据罗列的编号获取,也可以格局列名获取。

1
2
3
4
5
6
7
rs.first();        // 移动到第一行
rs.last(); // 移动到最后一行
rs.beforeFirst(); // 移动到第一行之前
rs.afterLast(); // 移动到最后一行之后
rs.absolute(5); // 移动到第5行
rs.relative(-2); // 从当前位置向前移动2行
rs.previous(); // 移动到上一行
  • 后续处理–关闭资源
1
2
3
rs.close();
pstmt.close();
conn.close();

SQL注入

通过控制输入来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean loginVulnerable(String username, String password) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");
Statement stmt = conn.createStatement();

// 🚨 危险:直接拼接用户输入到SQL中
String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'";

System.out.println("🚨 执行的SQL语句: " + sql);

ResultSet rs = stmt.executeQuery(sql);
boolean loginSuccess = rs.next();

rs.close();
stmt.close();
conn.close();

return loginSuccess;
}

这里如果我们用的是硬编码,则会出现:
如果有人通过控制表单输入:

1
2
user: ababbabab
key: 'or'1'='1

则会在我们的拼接作用下变为:

1
SELECT * FROM user WHERE username = 'ababbabab' AND password = ''or'1'='1'

那结果就会返回1了!!!
导致登陆成功。

但是如果是预编译,则不会出现这种状况

  • 会把整个' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)
1
SELECT * FROM user WHERE username = 'hacker' AND password = '\' or \'1\' = \'1'

尝试update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ParameterizedTest  
@CsvSource({"1,123456,25"})
public void testUpdate(int newId,String newPassword,int newAge) throws Exception {
// 获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "123456");
// 创建预编译的PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement("update user set password=?,age=? where id=?");
// 设置参数
pstmt.setString(1, newPassword); // 第一个问号对应的参数
pstmt.setInt(2, newAge); // 第二个问号对应的参数
pstmt.setInt(3,newId);//第三个
// 执行更新
int rowsUpdated = pstmt.executeUpdate();
// 输出结果
System.out.println(rowsUpdated+" rows(s) updated.");

// 关闭资源
pstmt.close();
conn.close();
}

Mybatis

持久层框架,用于简化JDBC的开发

  • 持久层:数据访问层(dao),用于操作数据库
  • 框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型


大大简化了操作

简单尝试

在application.properties里添加:

1
2
3
4
5
6
7
8
#数据库访问的url地址
spring.datasource.url=jdbc:mysql://localhost:3306/web
#数据库驱动类类名
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#访问数据库-用户名
spring.datasource.username=root
#访问数据库-密码
spring.datasource.password=123456

编写一个mybatis程序:

1
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.demo2.mapper;  
import com.itheima.demo2.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper // 表示这是mybatis中的mapper接口
public interface UserMapper {
// 查询全部
@Select("select * from User")
public List<User> findAll();
}

这里@Mapper表示这是mybatis中的mapper接口,程序运行时,框架会自动生成接口的实体类对象,并交给Spring的IOC容器管理

@Select用于表示要用select查询

单元测试

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
package com.itheima.demo2;  

import com.itheima.demo2.mapper.UserMapper;
import com.itheima.demo2.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class Demo2ApplicationTests {

@Test
void contextLoads() {
}
@Autowired // 自动注入
private UserMapper userMapper;

@Test
public void testFindAll(){
List<User> userList=userMapper.findAll(); //调用mapper接口方法
for(User user:userList){
System.out.println(user); //遍历并打印结果
}
}

}


可以正确查找

配置IDEA关联MySQL 数据库


这里发现无法解析表user,因为我们还没有建立连接


加入mybatis配置,可以看到日志

1
2
#mybatis的配置
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

mybatis的好处

我们前面在使用jdbc时,总是要将数据库连接参数硬编码在java代码中。
但是mybatis将数据库连接四要素放在了appllication.properties里。
查询结果的解析和封装我们都无需关注,mybatis自动完成映射封装
在mybatis中使用了数据库连接池技术,从而避免了频繁的创建链接、销毁链接带来的资源浪费。

数据库连接池

  • 没有数据库连接池的情况:
    每次客户端执行sql语句,都要创建一个新的连接对象,执行完再关闭连接对象进而释放资源。

  • 有数据库连接池的情况:
    数据库连接池是个容器,负责分配、管理数据库连接(Connection)

程序启动后,会在数据库连接池中创建一定数量的connection对象。

允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个(借过来用然后再归还)

释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

sun官方提供了数据库连接池标准

1
public Connection getConnection() throws SQLException;
  • 第三方组织必须按照DataSource接口实现

增删查改

在Mybatis中,我们可以通过参数占位符号 #{...} 来占位,在调用deleteById方法时,传递的参数值,最终会替换占位符。

例子:

比如这里,会将占位符替换为?,生成一个预编译的sql语句

insert:

Mybatis的开发有两种,一种是注解一种是xml

Spring配置

使用yml格式来配置

yml语法

  • 大小写敏感
  • 数值前边必须有空格,作为分隔符
  • 使用缩进表示层级关系,缩进不可以使用tab键!只可以用空格(idea里会将tab自动转换为空格)
  • 缩进的空格数目不重要,只要相同层级的元素左侧对其即可。
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略

yml的数据格式

  • 对象/map集合
1
2
3
4
user:
name: zhangsan
age: 18
password: 123456
  • 数组/List/Set结合
1
2
3
4
hobby:
- java
- game
- sport

在yml格式的配置文件中,如果配置项的值是以 0 开头的,值需要使用 ‘’ 引起来,因为以0开头在yml中表示8进制的数据。