NewJavaWeb

单元测试

在pom.xml中添加JUnit依赖

1
2
3
4
5
6
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
  • Junit单元测试规范:类名以Test结尾,方法名以test开头,每个方法都是一个测试用例。
  • 方法上添加@Test注解,标识这是一个测试方法。 规定:
    • 测试方法必须是public void类型。
    • 测试方法不能有参数。
    • 测试方法不能抛出异常。

断言

断言是单元测试中常用的一种机制,用于验证代码的行为是否符合预期。在JUnit中,断言通常用于测试方法中,用于检查测试结果是否与预期结果一致。

  • 常用的断言方法Assertions:
    • assertEquals(expected, actual, String message->这里选填有对应的重载方法):检查预期值与实际值是否相等。
    • assertTrue(condition):检查条件是否为真。
    • assertFalse(condition):检查条件是否为假。
    • assertNotNull(object):检查对象是否不为空。
    • assertNull(object):检查对象是否为空。

常见注解

  • @Test:标识这是一个测试方法。
  • @ParameterizedTest:标识这是一个参数化测试方法。(可以在一个测试方法中使用多个不同的参数进行测试)
  • @ValueSource:为参数化测试方法提供参数值。(可以指定多个参数值,每个值之间用逗号隔开)与上面@ParameterizedTest进行搭配使用
  • @BeforeEach:在每个测试方法执行前执行。
  • @AfterEach:在每个测试方法执行后执行。
  • @BeforeAll:在所有测试方法执行前执行。在每个测试方法执行前执行,用于初始化测试环境所以应该加上static关键字。
  • @AfterAll:在所有测试方法执行后执行。
  • @DisplayName:为测试类或测试方法指定自定义的显示名称。

JUnit 常见问题总结

1. JUnit单元测试的方法,是否可以声明方法形参?

  • 可以的,通过参数化测试实现
  • 使用 @ParameterizedTest + @ValueSource 组合

2. 如何实现在单元测试方法运行之前,做一些初始化操作?

  • 使用 @BeforeEach 注解:在每个测试方法执行前执行
  • 使用 @BeforeAll 注解:在所有测试方法执行前执行(需要声明为static)

3. 如何实现在单元测试方法运行之后,释放对应的资源?

  • 使用 @AfterEach 注解:在每个测试方法执行后执行
  • 使用 @AfterAll 注解:在所有测试方法执行后执行(需要声明为static)

单元测试-Maven依赖范围

  • 测试范围(test scope):
    • 仅在测试阶段有效,不会被包含在最终的可执行文件(如JAR或WAR)中。
    • 常用的测试范围依赖包括JUnit、Mockito、AssertJ等。
  • 编译范围(compile scope):
    • 编译时需要,运行时也需要。
    • 常用的编译范围依赖包括Servlet API、JSP API等。
  • 运行时范围(runtime scope):
    • 运行时需要,编译时不需要。
    • 常用的运行时范围依赖包括数据库驱动、日志框架等。
  • 提供范围(provided scope):
    • 编译时需要,运行时由容器提供。
    • 常用的提供范围依赖包括Servlet容器、JSP容器等。
      1
      2
      3
      4
      5
      6
      7
      <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.9.1</version>
      <scope>test</scope>
      <!-- 测试范围依赖,仅在测试阶段有效 -->
      </dependency>

SpringBoot快速入门

项目创建

  • 项目创建:
    • 打开Spring Initializr(https://start.spring.io/)。
    • 填写项目元数据(Group、Artifact、Name、Description、Package Name等)。
    • 选择项目类型(Maven Project或Gradle Project)。
    • 选择Java版本(如Java 17)。
    • 选择Spring Boot版本(如2.7.13)。
    • 添加所需的依赖(如Web、Data JPA、MySQL Driver等)。
    • 点击“Generate”按钮,下载项目压缩包。
  • 项目目录结构:
    • src/main/java:包含应用程序的Java源文件。
    • src/main/resources:包含应用程序的资源文件(如配置文件、静态资源等)。
    • src/test/java:包含测试用的Java源文件。
    • src/test/resources:包含测试用的资源文件。
    • pom.xml(或build.gradle):项目的构建配置文件,包含项目依赖、插件等。
  • 项目依赖管理:
    • pom.xml(或build.gradle):项目的构建配置文件,包含项目依赖、插件等。
    • 依赖管理:
      • 直接依赖:在项目的构建配置文件中直接声明的依赖,会被包含在最终的可执行文件中。
      • 传递依赖:项目依赖的其他库,会被自动下载并包含在项目中。
    • 依赖范围:
      • 编译范围(compile scope):编译时需要,运行时也需要。
      • 测试范围(test scope):仅在测试阶段有效,不会被包含在最终的可执行文件中。
      • 运行时范围(runtime scope):运行时需要,编译时不需要。
      • 提供范围(provided scope):编译时需要,运行时由容器提供。

HTTP协议

HTTP请求协议

  • 请求行:包含请求方法、请求URL和HTTP版本。
  • 请求头:包含请求的元数据,如请求的客户端信息、请求的内容类型等。常见的请求头字段如下:
请求头字段 说明
Host 请求的主机名
User-Agent 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 … Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT …) like Gecko
Accept 表示浏览器能接收的资源类型,如text/*, image/或者/*表示所有
Accept-Language 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页
Accept-Encoding 表示浏览器可以支持的压缩类型,例如gzip, deflate等
Content-Type 请求主体的数据类型
Content-Length 请求主体的大小(单位:字节)
  • 空行:用于分隔请求头和请求体。
  • 请求体:包含请求的数据,如表单数据、JSON数据等。

请求数据获取

Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。

HTTP请求示例:

1
2
3
4
5
6
GET /brand/findAll?name=OPPO&status=1 HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...

Tomcat会将这些请求数据解析并封装到HttpServletRequest对象中,开发者可以通过该对象方便地获取请求信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class requestController {
@RequestMapping("/request")
public String request(HttpServletRequest request) {//获取前端请求数据封装好了对应的方法
//获取请求行里面的请求地址
String url = request.getRequestURL().toString();//这里获取的是完整的请求地址
//获取请求uri地址
String requestURI = request.getRequestURI();
System.out.println(url);
System.out.println(requestURI);
//获取请求HTTP协议版本号
String protocol = request.getProtocol();
System.out.println(protocol);
//获取请求行中的请求参数
String name = request.getParameter("name");
String age = request.getParameter("age");
System.out.println(name);
System.out.println(age);
//获取请求头
String accept = request.getHeader("Accept");
System.out.println(accept);
return "ok";
}
}

响应数据格式

HTTP响应示例:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 10 May 2022 07:51:07 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[{"id": 1, "brandName": "阿里巴巴", "companyName": "腾讯计算机系统有限公司", "description": "玩玩玩"}]

HTTP响应协议由以下几部分组成:

  • 响应行:响应数据第一行(协议、状态码、描述)

    • 示例:HTTP/1.1 200 OK
    • HTTP版本:如HTTP/1.1
    • 状态码:三位数字,表示请求的处理结果(如200表示成功,404表示资源未找到)
    • 状态消息:对状态码的简短描述
  • 响应头:第二行开始,格式为key: value

    • 示例:Content-Type: application/json
    • 包含响应的元数据,如内容类型、内容长度、服务器信息等
    • 常见的响应头字段:
      • Content-Type:响应内容的类型(如text/html、application/json)
      • Content-Length:响应内容的长度
      • Server:服务器的名称和版本
      • Date:响应的日期和时间
      • Cache-Control:缓存控制策略
  • 空行:用于分隔响应头和响应体

  • 响应体:最后一部分,存放响应数据

    • 示例:[{"id": 1, "brandName": "阿里巴巴", "companyName": "腾讯计算机系统有限公司", "description": "玩玩玩"}]
    • 包含响应的实际数据,如HTML页面、JSON数据、图片等

响应状态码

HTTP状态码分为五大类:

类别 范围 描述
1xx 100-199 信息性状态码,表示请求已接收,继续处理
2xx 200-299 成功状态码,表示请求已成功处理
3xx 300-399 重定向状态码,表示需要进一步操作才能完成请求
4xx 400-499 客户端错误状态码,表示请求有错误
5xx 500-599 服务器错误状态码,表示服务器处理请求时出错

常见的状态码:

  • 200 OK:请求成功
  • 400 Bad Request:请求参数错误
  • 401 Unauthorized:未授权
  • 403 Forbidden:禁止访问
  • 404 Not Found:资源未找到
  • 500 Internal Server Error:服务器内部错误
  • 503 Service Unavailable:服务器暂时不可用

响应数据设置

在Controller方法中,可以通过HttpServletResponse对象来设置响应数据。

操作案例中的核心

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
@RestController // 标记为Spring MVC的控制器,负责处理HTTP请求
public class UserController {

// 映射GET /list请求,返回用户列表
@RequestMapping("/list")
public List<User> list() {
// 从类路径下加载user.txt文件,获取输入流
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
// 使用IoUtil工具类按行读取文件内容,默认UTF-8编码,结果存入ArrayList
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());

// 将每行文本解析成User对象:按逗号拆分字段并封装
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(","); // 拆分每行数据
Integer id = Integer.parseInt(parts[0]); // 解析用户ID
String username = parts[1]; // 用户名
String password = parts[2]; // 密码
String name = parts[3]; // 真实姓名
Integer age = Integer.parseInt(parts[4]); // 年龄
// 解析更新时间,格式为yyyy-MM-dd HH:mm:ss
LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new User(id, username, password, name, age, updateTime); // 构造User对象
}).toList(); // 收集为不可变List

// 将封装好的用户列表返回给前端(Spring自动转为JSON)
return userList;
}
}
  • 问题解决
    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
    <properties>
    <!-- Source: https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.42</version>
    <!-- 这里要加版本号不加版本号要报错 -->
    </dependency>
    <!-- Source: https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <annotationProcessorPaths>
    <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.42</version>
    <!-- 这里要加版本号不加版本号要报错 -->
    <exclude>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.42</version>
    <!-- 这里要加版本号不加版本号要报错 -->
    </exclude>
  • response 作用将其转换为 JSON 格式并返回给前端

Web分层解耦

  • 控制器(Controller):负责处理HTTP请求,调用服务层(Service)处理业务逻辑,最后将结果返回给前端。

  • 服务层(Service):负责业务逻辑的处理,调用数据访问层(Repository)进行数据操作。

  • 数据访问层(Dao):负责与数据库或其他数据源交互,执行CRUD操作。

  • 调用的时候使用多态的调用形式: UserService userService = new UserServiceImpl();

  • 控制反转(IoC):

    • 控制反转是一种设计模式,通过将对象的创建和依赖关系的管理交给容器来实现。
    • 容器负责创建对象并管理它们的生命周期,开发人员只需要关注业务逻辑的实现。
    • 控制反转的实现方式有两种:基于XML配置文件的方式和基于注解的方式。
  • 依赖注入(DI):

    • 依赖注入是控制反转的一种实现方式,通过容器将依赖的对象注入到目标对象中。
    • 依赖注入可以分为构造函数注入、Setter方法注入和接口注入等方式。
    • 构造函数注入:通过构造函数参数来注入依赖的对象。
    • Setter方法注入:通过Setter方法来注入依赖的对象。
    • 接口注入:通过接口方法来注入依赖的对象。
  • Bean对象:

    • Bean对象是指在IoC容器中管理的对象,通常是业务逻辑组件、数据访问组件等。
    • Bean对象的创建和管理由IoC容器负责,开发人员只需要关注业务逻辑的实现。
    • Bean对象的生命周期包括:实例化、依赖注入、初始化、使用、销毁。

Spring IOC & DI 实际应用

Spring IOC和依赖注入在实际开发中的核心应用包括:

  1. 将Dao及Service层的实现类,交给IOC容器管理
  2. 为Controller及Service注入运行时所依赖的对象

代码示例

UserController(控制器层)

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class UserController {
@Autowired
private UserService userService;

@RequestMapping("/list")
public List<User> list(){
//1.调用service,查询用户信息
List<User> userlist = userService.list();
//2...
}
}

UserServiceImpl(业务逻辑层)

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;

@Override
public List<User> list() {
//1.调用dao获取数据
List<String> lines = userDao.list();
//2...
}
}

UserDaoImpl(数据访问层)

1
2
3
4
5
6
7
8
9
@Component
public class UserDaoImpl implements UserDao {

@Override
public List<String> list() {
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
return IOUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
}
}

实现原理

在这个示例中:

  • 使用@Component注解将UserDaoImplUserServiceImpl注册到IOC容器中
  • 使用@RestController注解(特殊的@Component)将UserController注册到IOC容器中
  • 使用@Autowired注解自动注入依赖对象:
    • UserController注入UserService
    • UserServiceImpl注入UserDao

这样,Spring容器会自动管理这些Bean的生命周期和依赖关系,简化了代码开发和维护。

IOC详解

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时,用此注解
@Controller @Component的衍生注解 标注在控制层类上
@Service @Component的衍生注解 标注在业务层类上
@Repository @Component的衍生注解 标注在数据访问层类上(由于与mybatis整合,用的少)

三层架构与IOC容器

在Web应用中,通常采用三层架构:

  1. Controller(控制层):接收请求,响应数据
  2. Service(业务层):处理业务逻辑
  3. Dao(数据访问层):进行数据访问

Spring IOC容器可以管理这三层中的所有组件,通过不同的注解来区分组件类型,使代码结构更加清晰。

组件扫描机制

前面声明bean的四大注解(@Component、@Controller、@Service、@Repository)要想生效,还需要被组件扫描注解@ComponentScan扫描。

该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。

项目结构示例

1
2
3
4
5
6
7
8
9
10
11
tlias-management/
├── src/main/
│ ├── java/
│ │ └── com.itheima/
│ │ ├── controller/ # 控制层
│ │ ├── dao/ # 数据访问层
│ │ ├── pojo/ # 实体类
│ │ ├── service/ # 业务层
│ │ └── TliasManagementApplication.java # 启动类
│ └── resources/
└── pom.xml

启动类示例

1
2
3
4
5
6
@SpringBootApplication
public class TliasManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasManagementApplication.class, args);
}
}

在这个示例中,@SpringBootApplication注解包含了@ComponentScan,会自动扫描com.itheima包及其子包下的所有带有组件注解的类,将它们注册到IOC容器中。

DI详解

基于@Autowired进行依赖注入的常见方式有如下三种:

1. 属性注入

1
2
3
4
5
6
7
@RestController
public class UserController {
@Autowired
private UserService userService;

//......
}

2. 构造函数注入

1
2
3
4
5
6
7
8
9
@RestController
public class UserController {
private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
}

3. Setter注入

1
2
3
4
5
6
7
8
9
@RestController
public class UserController {
private UserService userService;

@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}

三种注入方式的对比

注入方式 优点 缺点
属性注入 简洁,使用方便 1. 无法注入final修饰的属性
2. 容易违反单一职责原则
3. 可能导致循环依赖
构造函数注入 1. 可以注入final修饰的属性
2. 避免循环依赖
3. 保证依赖不为空
代码相对复杂
Setter注入 1. 可以选择性注入
2. 灵活性更高
1. 无法注入final修饰的属性
2. 可能导致循环依赖

最佳实践

  • Spring 4.x推荐使用构造函数注入
  • Spring 5.x推荐使用构造函数注入或属性注入
  • 对于必需的依赖,优先使用构造函数注入
  • 对于可选的依赖,可以使用Setter注入

@Autowired的注入规则

@Autowired注解默认是按照类型进行注入的。如果存在多个相同类型的bean,将会报出如下错误:

1
2
3
4
5
Field userService in com.itheima.controller.UserController required a single bean, but 2 were found:
- userServiceImpl: defined in file [D:\idea_workspace\web\ai\web-project1\springboot-web-quickstart\target\classes\com\itheima\service\impl\UserServiceImpl.class]
- userServiceImpl2: defined in file [D:\idea_workspace\web\ai\web-project1\springboot-web-quickstart\target\classes\com\itheima\service\impl\UserServiceImpl2.class]
Action:
Consider making one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

解决方法

有三种方法可以解决多个同类型bean的注入问题:

方案一:@Primary
1
2
3
4
5
6
7
8
@Primary
@Service
public class UserServiceImpl implements UserService {
@Override
public List<User> list() {
// 省略实现...
}
}
方案二:@Qualifier
1
2
3
4
5
6
@RestController
public class UserController {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
}
方案三:@Resource
1
2
3
4
5
@RestController
public class UserController {
@Resource(name = "userServiceImpl")
private UserService userService;
}

三种方案的对比

  • @Primary:在bean定义时指定优先级,适合全局默认选择
  • @Qualifier:在注入时指定具体的bean名称,需要与@Autowired配合使用
  • @Resource:JDK提供的注解,默认按名称注入,也可以指定name属性

注意@Resource不是Spring特有的注解,而是JSR-250规范中的注解,功能与@Autowired类似但有一些差异。

mysql数据库

DDL-数据库

操作语法

查询所有数据库

1
show databases;

查询当前数据库

1
select database();

使用/切换数据库

1
use 数据库名;

创建数据库

1
create database [if not exists] 数据库名 [default charset utf8mb4];

删除数据库

1
drop database [if exists] 数据库名;

说明

  • [if not exists]:创建数据库时的可选条件,如果数据库不存在则创建
  • [default charset utf8mb4]:设置数据库的默认字符集为utf8mb4,支持表情符号
  • [if exists]:删除数据库时的可选条件,如果数据库存在则删除

DDL-表结构-创建

创建表的语法

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

说明

  • tablename:要创建的表名
  • 字段1 字段类型:定义表的字段名称和数据类型
  • [约束]:可选的字段约束(如PRIMARY KEY, NOT NULL, UNIQUE等)
  • [comment 字段1注释]:可选的字段注释,用于说明字段的含义
  • [comment 表注释]:可选的表注释,用于说明表的用途

约束

约束的定义

约束是作用于表中字段上的规则,用于限制存储在表中的数据。

约束的目的

保证数据库中数据的正确性、有效性和完整性。

约束类型

约束 描述 关键字
非空约束 限制字段取值不能为空 not null
唯一约束 保证字段的所有数据都是唯一、不重复的 unique
主键约束 主键是一行数据的唯一标识,要求非空且唯一 primary key
默认约束 保存数据时,如果未指定该字段值,则采用默认值 default
外键约束 让两张表的数据建立连接,保证数据的一致性和完整性 foreign key

约束示例

以一个用户表为例,展示各种约束的应用:

id username name age gender
1 qingyifuwang 韦一笑 45
2 baimeiyingwang 殷天正 55
3 jinmaoshiwang 谢逊 50
4 zixiaonvwang 黛绮丝 38

约束说明

  • id:主键约束(唯一标识)
  • username:非空约束 + 唯一约束
  • name:非空约束
  • gender:默认约束(默认值:男)

通过这些约束,可以保证表中数据的完整性和一致性,防止无效或错误的数据被插入到表中。

  • 主键自增:auto_increment

DDL-表结构-数据类型

字符串类型

分类 类型 大小 描述 示例 优势
字符串类型 char 0-255 bytes 定长字符串 idcard char(18)
phone char(11)
性能较高
字符串类型 varchar 0-65535 bytes 变长字符串 username varchar(50) 节省磁盘空间
字符串类型 tinyblob 0-255 bytes 不超过255个字节的二进制数据
字符串类型 tinytext 0-255 bytes 短文本字符串
字符串类型 blob 0-65 535 bytes 二进制形式的长文本数据
字符串类型 text 0-65 535 bytes 长文本数据
字符串类型 mediumblob 0-16 777 215 bytes 二进制形式的中等长度文本数据
字符串类型 mediumtext 0-16 777 215 bytes 中等长度文本数据
字符串类型 longblob 0-4 294 967 295 bytes 二进制形式的极大文本数据
字符串类型 longtext 0-4 294 967 295 bytes 极大文本数据

char与varchar的区别

  • char(10):固定占用10个字符空间,存储A占10个空间,存储ABC占10个空间
  • varchar(10):最多占用10个字符空间,存储A占1个空间,存储ABC占3个空间

日期类型

分类 类型 大小(byte) 范围 格式 描述 示例
日期类型 date 3 1000-01-01 至 9999-12-31 YYYY-MM-DD 日期值 birthday date
日期类型 time 3 -838:59:59 至 838:59:59 HH:MM:SS 时间值或持续时间
日期类型 year 1 1901 至 2155 YYYY 年份值
日期类型 datetime 8 1000-01-01 00:00:00 至 9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值 operateTime datetime
日期类型 timestamp 4 1970-01-01 00:00:01 至 2038-01-19 03:14:07 YYYY-MM-DD HH:MM:SS 混合日期和时间值,时间戳

datetime与timestamp的区别

  • datetime:存储范围更广,占用8字节,不会自动更新
  • timestamp:存储范围较小,占用4字节,会自动更新为当前时间(当插入或更新数据时)

数值类型

MySQL还支持多种数值类型,包括整数类型和浮点类型:

整数类型

  • tinyint:1字节,-128至127或0至255
  • smallint:2字节,-32768至32767或0至65535
  • mediumint:3字节,-8388608至8388607或0至16777215
  • int:4字节,-2147483648至2147483647或0至4294967295
  • bigint:8字节,-9223372036854775808至9223372036854775807或0至18446744073709551615

浮点类型

  • float:4字节,单精度浮点数值
  • double:8字节,双精度浮点数值
  • decimal:可变长度,精确数值(用于财务数据)

注意:选择数据类型时,应根据实际存储需求选择合适的类型,以节省存储空间并提高查询性能。

DDL-表结构-查询、修改、删除

表结构的查询、修改、删除相关语法如下:

查询表结构

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

desc 表名; -- 查询表结构

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

修改表结构

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

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

-- 修改字段名与字段类型
alter table 表名 change 旧字段名 新字段名 类型(长度) [comment 注释] [约束];

-- 修改表名
alter table 表名 rename to 新表名;

删除表结构

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

-- 删除表
drop table [if exists] 表名;

DML-数据操作

DML-insert(新增数据)

1
2
3
4
5
6
7
8
9
10
11
-- 指定字段添加数据
insert into 表名(字段名1, 字段名2) values (值1, 值2);

-- 全部字段添加数据
insert into 表名 values (值1, 值2, ...);

-- 批量添加数据(指定字段)
insert into 表名(字段名1, 字段名2) values (值1, 值2), (值1, 值2);

-- 批量添加数据(全部字段)
insert into 表名 values (值1, 值2, ...), (值1, 值2, ...);

DML-update(修改数据)

1
2
3
4
-- 修改数据
update 表名 set 字段名1 =1, 字段名2 =2, ... [where 条件];

-- 注意:如果不添加where条件,会修改表中所有数据

DML-delete(删除数据)

1
2
3
4
-- 删除数据
delete from 表名 [where 条件];

-- 注意:如果不添加where条件,会删除表中所有数据

注意

  • DDL语句(数据定义语言)用于定义数据库对象,如数据库、表、约束等
  • DML语句(数据操作语言)用于操作数据库中的数据,如插入、修改、删除数据
  • DQL语句(数据查询语言)用于查询数据库中的数据
  • 执行DML语句时,应谨慎使用where条件,避免误操作影响大量数据

DQL-数据查询

DQL完整语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select
字段列表
from
表名列表
where
条件列表
group by
分组字段列表
having
分组后条件列表
order by
排序字段列表
limit
分页参数

DQL查询类型

DQL查询包括以下五种类型:

  1. 基本查询 (select...from...)
  2. 条件查询 (where)
  3. 分组查询 (group by)
  4. 排序查询 (order by)
  5. 分页查询 (limit)

DQL-基本查询

查询多个字段

1
select 字段1,字段2,字段3 from 表名;

查询所有字段(通配符)

1
select * from 表名;

为查询字段设置别名

1
2
3
4
select 字段1 [as 别名1], 字段2 [as 别名2] from 表名;

-- as关键字可以省略
select 字段1 别名1, 字段2 别名2 from 表名;

去除重复记录

1
select distinct 字段列表 from 表名;

注意

  • select * 虽然方便,但在实际开发中应尽量避免使用,建议明确指定需要查询的字段
  • distinct 会去除查询结果中所有字段值都相同的记录
  • 设置别名可以使查询结果更易读,特别是在进行多表查询或使用函数时

DQL-条件查询

条件查询基本语法

1
select 字段列表 from 表名 where 条件列表 ;

比较运算符

比较运算符 功能
> 大于
>= 大于等于
< 小于
<= 小于等于
= 等于
<> 或 != 不等于
between … and … 在某个范围之内(含最小、最大值)
in(…) 在in之后的列表中的值,多选一
Like 占位符 模糊匹配(_匹配单个字符,%匹配任意个字符)
is null 是null

逻辑运算符

逻辑运算符 功能
and 或 && 并且 (多个条件同时成立)
or 或 || 或者 (多个条件任意一个成立)
not 或 ! 非 ,不是

条件查询示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 查询年龄大于等于18的用户
select * from user where age >= 18;

-- 查询年龄在18-30之间的用户
select * from user where age between 18 and 30;

-- 查询年龄为18、20、22的用户
select * from user where age in(18,20,22);

-- 查询姓名中包含"张"的用户
select * from user where name like '%张%';

-- 查询姓名以"李"开头的用户
select * from user where name like '李%';

-- 查询姓名为2个字符的用户
select * from user where name like '__';

-- 查询年龄不为null的用户
select * from user where age is not null;

-- 查询年龄大于18且性别为男的用户
select * from user where age > 18 and gender = '男';

注意

  • 在SQL中,字符串和日期类型的数据需要使用单引号括起来
  • like 运算符中,_ 表示匹配单个字符,% 表示匹配任意个字符
  • null 值不能使用 =!= 进行比较,必须使用 is nullis not null

5. 聚合函数

5.1 聚合函数概述

聚合函数是将一列数据作为一个整体,进行纵向计算的函数。

5.2 常用聚合函数

函数 功能
count 统计数量
max 最大值
min 最小值
avg 平均值
sum 求和

5.3 注意事项

  • 所有的聚合函数不参与null的统计

5.4 聚合函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 统计员工数量
-- count(字段): 统计指定字段非空值的数量
select count(id) from emp;

-- count(*): 统计所有记录的数量(推荐使用)
select count(*) from emp;

-- count(常量): 统计所有记录的数量(推荐使用)
select count(1) from emp;

-- 统计员工的最高工资
select max(salary) from emp;

-- 统计员工的最低工资
select min(salary) from emp;

-- 统计员工的平均工资
select avg(salary) from emp;

-- 统计员工的工资总和
select sum(salary) from emp;

6. 分组查询

6.1 分组查询语法

1
select 字段列表 from 表名 [where 条件列表] group by 分组字段名 [having 分组后过滤条件];

6.2 where与having的区别

区别 where having
执行时机 分组之前进行过滤 分组之后对结果进行过滤
判断条件 不能对聚合函数进行判断 可以对聚合函数进行判断

6.3 分组查询示例

1
2
3
4
5
6
7
8
9
10
11
-- 根据性别分组,统计男性和女性员工的数量
select gender, count(*) from emp group by gender;

-- 根据性别分组,统计男性和女性员工的平均工资
select gender, avg(salary) from emp group by gender;

-- 先查询年龄大于20的员工,再根据性别分组,统计男性和女性员工的数量
select gender, count(*) from emp where age > 20 group by gender;

-- 根据性别分组,统计男性和女性员工的平均工资,筛选平均工资大于5000的结果
select gender, avg(salary) from emp group by gender having avg(salary) > 5000;

6.4 分组查询注意事项

  • 分组查询的查询列表中,只能包含分组字段聚合函数,不能包含其他字段(除非这些字段在所有分组内的取值都相同)
  • 如果需要对分组后的结果进行过滤,必须使用 having 子句,而不能使用 where 子句
  • where 子句用于在分组前对数据进行过滤,having 子句用于在分组后对数据进行过滤
  • 可以对多个字段进行分组,分组字段之间用逗号分隔
1
2
-- 根据部门和性别分组,统计各部门各性别的员工数量
select dept, gender, count(*) from emp group by dept, gender;

7. 排序查询

7.1 排序查询语法

1
select 字段列表 from 表名 [where 条件列表] [group by 分组字段名 having 分组后过滤条件] order by 排序字段 排序方式;

7.2 排序方式

  • 升序asc(默认值,可以省略)
  • 降序desc

7.3 排序查询示例

1
2
3
4
5
6
7
8
9
10
11
-- 按照工资从低到高排序(升序)
select * from emp order by salary;

-- 按照工资从高到低排序(降序)
select * from emp order by salary desc;

-- 先按照工资从高到低排序,再按照年龄从大到小排序
select * from emp order by salary desc, age desc;

-- 查询年龄大于20的员工,按照工资从高到低排序
select * from emp where age > 20 order by salary desc;

8. 分页查询

8.1 分页查询语法

1
select 字段列表 from 表名 [where 条件列表] [group by 分组字段名 having 分组后过滤条件] [order by 排序字段 排序方式] limit 起始索引, 查询记录数;

8.2 分页查询说明

  • 起始索引:从0开始,即第一页的起始索引为0
  • 查询记录数:每页显示的记录条数
  • 分页查询是数据库方言:不同的数据库实现方式不同,MySQL中使用LIMIT关键字

8.3 分页查询示例

1
2
3
4
5
6
7
8
9
10
11
-- 查询第1页,每页显示5条记录
select * from emp limit 0, 5;

-- 查询第2页,每页显示5条记录(起始索引 = (页码 - 1) * 每页记录数)
select * from emp limit 5, 5;

-- 查询第3页,每页显示5条记录
select * from emp limit 10, 5;

-- 先按照工资从高到低排序,再查询第2页,每页显示3条记录
select * from emp order by salary desc limit 3, 3;

8.4 注意事项

  • 如果起始索引为0,可以省略起始索引,直接写limit 记录数
    1
    2
    -- 查询第1页,每页显示5条记录(简写形式)
    select * from emp limit 5;
  • 分页查询在实际开发中非常常用,通常用于数据列表的分页显示
  • 为了保证分页结果的稳定性,建议在分页查询时添加order by排序条件

JDBC

1. JDBC入门程序

1.1 需求

基于JDBC程序,执行update语句:

1
update user set age = 25 where id = 1

1.2 实现步骤

1.2.1 准备工作

  1. 创建一个Maven项目
  2. 引入MySQL驱动依赖
  3. 准备数据库表user

1.2.2 代码实现

编写JDBC程序,操作数据库

1.3 Maven依赖配置

pom.xml文件中添加MySQL驱动依赖:

1
2
3
4
5
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>

1.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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class JdbcDemo {
public static void main(String[] args) throws Exception {
// 1. 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 获取连接
String url = "jdbc:mysql://localhost:3306/web01";
String username = "root";
String password = "1234";
Connection connection = DriverManager.getConnection(url, username, password);

// 3. 获取SQL语句执行对象
Statement statement = connection.createStatement();

// 4. 执行SQL
String sql = "update user set age = 25 where id = 1";
int i = statement.executeUpdate(sql);

// 5. 释放资源
statement.close();
connection.close();

System.out.println("影响行数: " + i);
}
}

1.5 代码详解

  1. 注册驱动

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

    加载并注册MySQL驱动类。在JDBC 4.0之后,这一步可以省略,因为会自动加载驱动。

  2. 获取连接

    1
    2
    3
    4
    String url = "jdbc:mysql://localhost:3306/web01";
    String username = "root";
    String password = "1234";
    Connection connection = DriverManager.getConnection(url, username, password);
    • url:数据库连接地址,格式为jdbc:mysql://主机:端口/数据库名
    • username:数据库用户名
    • password:数据库密码
    • Connection:数据库连接对象,用于建立与数据库的连接
  3. 获取SQL执行对象

    1
    Statement statement = connection.createStatement();

    Statement是SQL语句执行对象,用于发送SQL语句到数据库执行。

  4. 执行SQL

    1
    2
    String sql = "update user set age = 25 where id = 1";
    int i = statement.executeUpdate(sql);
    • executeUpdate()方法用于执行DML语句(INSERT、UPDATE、DELETE)
    • 返回值是受影响的行数
  5. 释放资源

    1
    2
    statement.close();
    connection.close();

    释放数据库资源,先关闭Statement,再关闭Connection。

2. JDBC核心API

2.1 DriverManager

DriverManager是JDBC驱动程序的管理类,主要负责加载、注册JDBC驱动,并根据连接字符串创建数据库连接。

2.1.1 主要功能

  • 加载驱动Class.forName("com.mysql.cj.jdbc.Driver")
  • 获取连接DriverManager.getConnection(url, username, password)

2.1.2 示例

1
2
3
4
5
6
7
8
// 注册驱动(JDBC 4.0后可省略)
Class.forName("com.mysql.cj.jdbc.Driver");

// 获取连接
String url = "jdbc:mysql://localhost:3306/web01";
String username = "root";
String password = "1234";
Connection connection = DriverManager.getConnection(url, username, password);

2.2 Connection

Connection是数据库连接对象,负责建立与数据库的连接,并提供创建SQL执行对象的方法。

2.2.1 主要功能

  • 创建Statement对象connection.createStatement()
  • 创建PreparedStatement对象connection.prepareStatement(sql)
  • 创建CallableStatement对象connection.prepareCall(sql)
  • 管理事务:提交、回滚、设置事务隔离级别

2.2.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建Statement对象
Statement statement = connection.createStatement();

// 创建PreparedStatement对象
String sql = "insert into user(name, age) values(?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);

// 事务管理
connection.setAutoCommit(false); // 关闭自动提交
try {
// 执行SQL语句
// ...
connection.commit(); // 提交事务
} catch (SQLException e) {
connection.rollback(); // 回滚事务
e.printStackTrace();
}

2.3 Statement

Statement是SQL语句执行对象,用于发送SQL语句到数据库执行。

2.3.1 主要功能

  • 执行DML语句executeUpdate(sql) - 返回受影响的行数
  • 执行DQL语句executeQuery(sql) - 返回ResultSet结果集
  • 执行任意SQL语句execute(sql) - 返回boolean值,表示是否有结果集

2.3.2 示例

1
2
3
4
5
6
7
// 执行DML语句
String sql1 = "update user set age = 25 where id = 1";
int rows = statement.executeUpdate(sql1);

// 执行DQL语句
String sql2 = "select * from user";
ResultSet resultSet = statement.executeQuery(sql2);

2.4 ResultSet

ResultSet是查询结果集对象,用于存储和处理查询结果。

2.4.1 主要功能

  • 遍历结果集next() - 移动到下一行记录
  • 获取字段值getXxx(字段名)getXxx(字段索引)
  • 释放资源close()

2.4.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String sql = "select id, name, age from user";
ResultSet resultSet = statement.executeQuery(sql);

// 遍历结果集
while (resultSet.next()) {
int id = resultSet.getInt("id"); // 或 resultSet.getInt(1)
String name = resultSet.getString("name"); // 或 resultSet.getString(2)
int age = resultSet.getInt("age"); // 或 resultSet.getInt(3)

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

// 释放资源
resultSet.close();
statement.close();
connection.close();

2.5 PreparedStatement

PreparedStatement是Statement的子类,用于执行预编译的SQL语句。它比Statement更加安全和高效,可以防止SQL注入攻击。

2.5.1 主要功能

  • 预编译SQL语句:提高执行效率
  • 防止SQL注入:通过参数化查询
  • 设置参数setXxx(参数索引, 参数值)
  • 执行SQL语句executeUpdate()executeQuery()

2.5.2 PreparedStatement与Statement的区别

特性 Statement PreparedStatement
SQL语句 静态SQL,每次执行都需要重新解析 预编译SQL,只需要解析一次
性能 较低 较高
安全性 容易受到SQL注入攻击 可以防止SQL注入攻击
参数处理 直接拼接字符串 使用参数占位符(?)

2.5.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
25
26
27
28
29
30
31
// 创建PreparedStatement对象
String sql = "insert into user(name, age) values(?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);

// 设置参数
preparedStatement.setString(1, "张三"); // 第一个参数,索引从1开始
preparedStatement.setInt(2, 25); // 第二个参数

// 执行SQL语句
int rows = preparedStatement.executeUpdate();
System.out.println("插入了" + rows + "条记录");

// 查询示例
String sql2 = "select * from user where age > ?";
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.setInt(1, 20);
ResultSet resultSet = preparedStatement2.executeQuery();

// 遍历结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
System.out.println("id: " + id + ", name: " + name + ", age: " + age);
}

// 释放资源
resultSet.close();
preparedStatement2.close();
preparedStatement.close();
connection.close();

2.5.4 SQL注入防护

使用Statement时,SQL语句是通过字符串拼接形成的,容易受到SQL注入攻击:

1
2
3
4
5
6
// 不安全的做法
String username = "admin' or '1'='1";
String password = "任意密码";
String sql = "select * from user where username = '" + username + "' and password = '" + password + "'";
// 生成的SQL:select * from user where username = 'admin' or '1'='1' and password = '任意密码'
// 由于'1'='1'永远为真,这条SQL会查询出所有用户记录

使用PreparedStatement可以防止SQL注入:

1
2
3
4
5
6
7
8
// 安全的做法
String username = "admin' or '1'='1";
String password = "任意密码";
String sql = "select * from user where username = ? and password = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
// PreparedStatement会将参数中的特殊字符进行转义,防止SQL注入

3. JDBC事务管理

3.1 事务的概念

事务是一组SQL语句的集合,这些语句要么全部执行成功,要么全部执行失败,是数据库操作的最小工作单元。

3.2 事务的四大特性(ACID)

特性 描述
原子性(Atomicity) 事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败
一致性(Consistency) 事务执行前后,数据库的完整性约束没有被破坏
隔离性(Isolation) 多个事务并发执行时,一个事务的执行不影响其他事务的执行
持久性(Durability) 事务一旦提交,它对数据库的改变就是永久性的

3.3 JDBC事务管理API

JDBC通过Connection对象提供事务管理功能:

  • 设置自动提交connection.setAutoCommit(false) - 关闭自动提交,开启事务
  • 提交事务connection.commit() - 提交事务,使所有已执行的SQL语句生效
  • 回滚事务connection.rollback() - 回滚事务,取消所有已执行的SQL语句
  • 设置保存点connection.setSavepoint() - 设置事务保存点,允许部分回滚
  • 回滚到保存点connection.rollback(savepoint) - 回滚到指定的保存点

3.4 事务管理示例

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
Connection connection = null;
PreparedStatement preparedStatement = null;

String url = "jdbc:mysql://localhost:3306/web01";
String username = "root";
String password = "1234";

try {
// 获取连接
connection = DriverManager.getConnection(url, username, password);

// 关闭自动提交,开启事务
connection.setAutoCommit(false);

// 执行第一条SQL语句:转账(转出)
String sql1 = "update account set balance = balance - ? where id = ?";
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setDouble(1, 1000.0);
preparedStatement.setInt(2, 1);
int rows1 = preparedStatement.executeUpdate();

// 执行第二条SQL语句:转账(转入)
String sql2 = "update account set balance = balance + ? where id = ?";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setDouble(1, 1000.0);
preparedStatement.setInt(2, 2);
int rows2 = preparedStatement.executeUpdate();

// 模拟异常
// int i = 1 / 0;

// 提交事务
connection.commit();
System.out.println("转账成功");

} catch (SQLException e) {
// 发生异常,回滚事务
if (connection != null) {
try {
connection.rollback();
System.out.println("转账失败,事务已回滚");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 释放资源
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

3.5 事务隔离级别

事务隔离级别决定了事务之间的隔离程度,JDBC提供了以下隔离级别:

隔离级别 描述 可能产生的问题
TRANSACTION_READ_UNCOMMITTED(读未提交) 允许读取未提交的数据 脏读、不可重复读、幻读
TRANSACTION_READ_COMMITTED(读已提交) 只能读取已提交的数据 不可重复读、幻读
TRANSACTION_REPEATABLE_READ(可重复读) 保证同一事务中多次读取同一数据的结果一致 幻读
TRANSACTION_SERIALIZABLE(串行化) 最高隔离级别,事务串行执行 性能较差

3.6 设置事务隔离级别

1
2
3
4
5
6
// 获取当前隔离级别
int isolationLevel = connection.getTransactionIsolation();
System.out.println("当前隔离级别: " + isolationLevel);

// 设置隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

MyBatis

1. 使用MyBatis查询所有用户数据

1.1 准备工作

  1. 创建SpringBoot工程、引入MyBatis相关依赖

    • 创建SpringBoot工程时,选择MyBatis Framework和MySQL Driver依赖
  2. 准备数据库表user和实体类User

    • 数据库表结构:

      id username password name age
      1 dqiaodai 1234567890 大乔 22
      2 xiaqiao 123456 小乔 17
      3 luban 123456 鲁班 18
      4 zhaoyun 12345678 赵云 27
    • 实体类User:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class User {
      private Integer id; // ID
      private String username; // 用户名
      private String password; // 密码
      private String name; // 姓名
      private Integer age; // 年龄

      // getter和setter方法
      // ...
      }
  3. 配置MyBatis
    application.properties文件中配置数据库连接信息:

    1
    2
    3
    4
    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=1234

1.2 编写MyBatis程序

编写MyBatis的持久层接口,定义SQL

1
2
3
4
5
@Mapper
public interface UserMapper {
@Select("select * from user")
public List<User> findAll();
}

1.3 辅助配置-配置MyBatis的日志输出

默认情况下,在MyBatis中,SQL语句执行时,我们并看不到SQL语句的执行日志。加入如下配置,即可查看日志:

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

数据库连接池

1. 数据库连接池概述

数据库连接池是一种创建和管理数据库连接的技术,它允许应用程序从一个预创建的连接池中获取连接,使用完毕后将连接归还到池中,而不是每次都创建和销毁连接。

1.1 数据库连接池的优势

  • 提高性能:避免频繁创建和销毁数据库连接的开销
  • 资源管理:控制数据库连接的数量,防止资源耗尽
  • 连接复用:通过连接复用,减少系统开销
  • 统一配置:集中管理数据库连接参数

2. 切换到Druid数据库连接池

Druid是阿里巴巴开源的数据库连接池,具有强大的监控功能和扩展性。

2.1 引入Druid依赖

pom.xml文件中添加Druid依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version>
</dependency>

2.2 配置Druid数据源

application.properties文件中配置数据源类型为Druid:

1
2
3
4
5
6
7
8
# 数据源类型配置为Druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 数据库连接信息
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=1234

2.3 常用Druid配置参数

可以根据需要添加更多Druid配置参数:

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
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size=5

# 连接池的最小空闲数量
spring.datasource.druid.min-idle=5

# 连接池最大连接数量
spring.datasource.druid.max-active=20

# 获取连接时最大等待时间(毫秒)
spring.datasource.druid.max-wait=60000

# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接(毫秒)
spring.datasource.druid.time-between-eviction-runs-millis=60000

# 配置一个连接在池中最小生存的时间(毫秒)
spring.datasource.druid.min-evictable-idle-time-millis=300000

# 测试连接是否可用的SQL语句
spring.datasource.druid.validation-query=SELECT 1

# 申请连接时执行validationQuery检测连接是否有效
spring.datasource.druid.test-on-borrow=false

# 归还连接时执行validationQuery检测连接是否有效
spring.datasource.druid.test-on-return=false

# 连接空闲时执行validationQuery检测连接是否有效
spring.datasource.druid.test-while-idle=true

Mybatis-增删改查-新增用户

1. 新增用户需求

需求:添加一个用户到数据库中。

SQL语句示例

1
insert into user(username,password,name,age) values('zhouyu','123456','周瑜',20);

2. Mapper接口实现

2.1 错误示例

不要使用硬编码的SQL语句,这样无法接收动态参数:

1
2
3
// 错误的做法
@Insert("insert into user(username,password,name,age) values('zhouyu', '123456', '周瑜', 20)")
public void insert();

2.2 正确示例

应该使用#{}占位符接收User对象的属性值:

1
2
3
// 正确的做法
@Insert("insert into user(username,password,name,age) values(#{username},#{password},#{name},#{age})")
public void insert(User user);

3. 实现说明

  1. 参数类型:使用User对象作为方法参数,这样可以一次性传递多个字段的值
  2. 占位符:使用#{属性名}的形式引用User对象的属性值,MyBatis会自动从对象中获取对应属性的值
  3. 返回值:可以使用void,也可以使用Integer表示受影响的行数

4. 测试插入功能

可以在Controller中添加插入接口,或者使用单元测试来测试插入功能:

4.1 添加插入接口

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;

// 新增用户
@PostMapping("/insert")
public String insert(@RequestBody User user) {
userMapper.insert(user);
return "添加成功";
}
}

4.2 使用Postman测试插入接口

  1. 启动SpringBoot应用程序
  2. 打开Postman,选择POST请求方式
  3. 输入请求URL:http://localhost:8080/user/insert
  4. 在Body中选择JSON格式,输入用户信息:
    1
    2
    3
    4
    5
    6
    {
    "username": "zhouyu",
    "password": "123456",
    "name": "周瑜",
    "age": 20
    }
  5. 点击发送按钮
  6. 查看响应结果:”添加成功”

Mybatis-增删改查-修改用户

1. 修改用户需求

需求:根据ID更新用户信息。

SQL语句示例

1
update user set username = 'zhouyu', password = '123456', name = '周瑜', age = 20 where id = 1;

2. Mapper接口实现

1
2
@Update("update user set username=#{username}, password=#{password}, name=#{name}, age=#{age} where id=#{id}")
public void update(User user);

3. 实现说明

  1. 参数类型:使用User对象作为方法参数,包含所有需要更新的字段和ID
  2. 占位符:使用#{属性名}引用User对象的属性值
  3. where条件:必须包含ID条件,否则会更新所有记录

4. 测试修改功能

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;

// 修改用户
@PutMapping("/update")
public String update(@RequestBody User user) {
userMapper.update(user);
return "更新成功";
}
}

Mybatis-增删改查-查询用户

1. 查询用户需求

需求:根据用户名和密码查询用户信息。

SQL语句示例

1
select * from user where username = 'zhouyu' and password = '666888';

2. Mapper接口实现

2.1 直接参数传递

1
2
@Select("select * from user where username=#{username} and password=#{password}")
public User findByUsernameAndPassword(String username, String password);

2.2 使用@Param注解

如果方法参数名与SQL中的占位符不一致,可以使用@Param注解为参数起别名:

1
2
@Select("select * from user where username=#{username} and password=#{password}")
public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);

3. @Param注解说明

@Param注解的作用是为接口的方法形参起名字,便于在SQL语句中引用参数值。

4. 测试查询功能

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;

// 根据用户名和密码查询用户
@GetMapping("/find")
public User findByUsernameAndPassword(String username, String password) {
return userMapper.findByUsernameAndPassword(username, password);
}
}

Mybatis-增删改查-删除操作

1. 使用MyBatis完成删除操作

使用MyBatis完成删除操作的步骤与查询操作类似,但需要使用@Delete注解来定义删除SQL语句。

2. 编写删除操作的Mapper接口方法

在之前创建的UserMapper接口中添加删除操作的方法:

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
@Mapper
public interface UserMapper {
// 查询所有用户
@Select("select * from user")
public List<User> findAll();

// 新增用户
@Insert("insert into user(username,password,name,age) values(#{username},#{password},#{name},#{age})")
public void insert(User user);

// 修改用户
@Update("update user set username=#{username}, password=#{password}, name=#{name}, age=#{age} where id=#{id}")
public void update(User user);

// 根据用户名和密码查询用户
@Select("select * from user where username=#{username} and password=#{password}")
public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);

// 根据ID删除用户
@Delete("delete from user where id = #{id}")
public Integer deleteById(Integer id);

// 批量删除用户
@Delete("<script>delete from user where id in (<foreach collection='ids' item='id' separator=','>#{id}</foreach>)</script>")
public Integer batchDelete(@Param("ids") List<Integer> ids);
}

4. 执行删除操作的注意事项

  1. 参数传递:删除操作通常需要传递一个或多个参数来确定要删除的记录。在MyBatis中,可以使用#{}占位符来接收参数。

  2. 返回值:删除操作的返回值通常是一个整数,表示受影响的行数。如果返回值大于0,则表示删除成功;否则表示删除失败。

  3. 事务处理:删除操作是一个DML操作,默认情况下会自动提交事务。如果需要手动控制事务,可以在方法上添加@Transactional注解。

  4. SQL注入防护:MyBatis的#{}占位符会自动进行参数类型转换和SQL注入防护,因此不需要手动处理。

5. 执行删除操作的示例

5.1 使用Postman测试删除接口

  1. 启动SpringBoot应用程序
  2. 打开Postman,选择DELETE请求方式
  3. 输入请求URL:http://localhost:8080/user/deleteById?id=1
  4. 点击发送按钮
  5. 查看响应结果:”删除成功”

5.2 查看数据库记录变化

执行删除操作后,可以查看数据库中的记录,确认ID为1的用户是否已被删除。

6. 批量删除操作

如果需要同时删除多条记录,可以使用MyBatis的批量删除功能。例如:

1
2
3
4
5
6
@Mapper
public interface UserMapper {
// 批量删除用户
@Delete("<script>delete from user where id in (<foreach collection='ids' item='id' separator=','>#{id}</foreach>)</script>")
public Integer batchDelete(@Param("ids") List<Integer> ids);
}

注意:使用批量删除时,需要使用<script>标签包裹SQL语句,并使用<foreach>标签遍历参数列表。

7. MyBatis中的#和$符号

在MyBatis中,#{}和${}`是两种不同的参数传递方式,它们在实现原理、安全性和使用场景上存在明显区别。

7.1 #{}和${}的区别

符号 说明 场景 优缺点
#{} 占位符。执行时,会将#{…}替换为?,生成预编译SQL 参数值传递 安全、性能高(推荐)
${} 拼接符。直接将参数拼接在SQL语句中,存在SQL注入问题 表名、字段名动态设置时使用 不安全、性能低

7.2 使用示例

7.2.1 #{}的使用示例

1
2
3
// 根据ID删除部门
@Delete("delete from dept where id = #{id}")
public Integer deleteDeptById(Integer id);

执行时,MyBatis会将#{id}替换为?,生成预编译SQL:

1
delete from dept where id = ?

然后将参数值传递给预编译的SQL语句,这样可以防止SQL注入攻击。

7.2.2 ${}的使用示例

1
2
3
// 根据动态表名和排序字段查询数据
@Select("select id, name, score from ${tableName} order by ${sortField}")
public List<Student> findStudents(@Param("tableName") String tableName, @Param("sortField") String sortField);

执行时,MyBatis会直接将参数值拼接到SQL语句中:

1
select id, name, score from student order by score

这种方式适用于表名或字段名需要动态设置的场景,但存在SQL注入风险,因此不推荐用于普通参数传递。

7.3 如何选择

  • **优先使用#{}**:对于普通参数传递,优先使用#{},因为它更安全、性能更高
  • **谨慎使用${}**:只有在需要动态设置表名、字段名等场景下才使用${},并且需要确保参数值的安全性

7.4 防止SQL注入

使用${}时,需要特别注意防止SQL注入攻击:

  1. 参数验证:对传入的参数进行严格验证,确保只包含允许的字符
  2. 白名单机制:只允许特定的表名或字段名
  3. 使用预编译:如果可能,尽量使用#{}代替${}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 安全的动态表名查询
public List<Student> findStudents(String tableName, String sortField) {
// 验证表名是否在白名单中
List<String> validTables = Arrays.asList("student", "teacher", "course");
if (!validTables.contains(tableName)) {
throw new IllegalArgumentException("Invalid table name");
}

// 验证排序字段是否在白名单中
List<String> validSortFields = Arrays.asList("id", "name", "score", "age");
if (!validSortFields.contains(sortField)) {
throw new IllegalArgumentException("Invalid sort field");
}

// 执行查询
return studentMapper.findStudents(tableName, sortField);
}

8. XML映射配置

在MyBatis中,既可以通过注解配置SQL语句,也可以通过XML配置文件配置SQL语句。

8.1 默认规则

  1. 文件命名与位置:XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。

  2. 命名空间配置:XML映射文件的namespace属性与Mapper接口全限定名一致。

  3. SQL语句配置:XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型一致。

8.2 项目结构示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mybatis-demo
└── src
└── main
├── java
│ └── com.itheima
│ ├── mapper
│ │ └── UserMapper.java # Mapper接口
│ ├── pojo
│ │ └── User.java
│ └── MybatisDemoApplication.java
└── resources
├── com
│ └── itheima
│ └── mapper
│ └── UserMapper.xml # XML映射文件
└── application.properties

8.3 接口与XML示例

**Mapper接口 (UserMapper.java)**:

1
2
3
public interface UserMapper {
public List<User> findAll();
}

**XML映射文件 (UserMapper.xml)**:

1
2
3
4
5
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="findAll" resultType="com.itheima.pojo.User">
select id, username, password, name, age from user
</select>
</mapper>

8.4 XML映射的优势

  • 复杂SQL支持:对于复杂的SQL语句(如多表关联查询、动态SQL等),XML配置方式更加清晰和易于维护
  • SQL语句集中管理:所有SQL语句都集中在XML文件中,便于统一管理和优化
  • 代码与SQL分离:业务代码与SQL语句完全分离,提高代码的可读性和可维护性
  • 更好的格式化支持:XML文件支持SQL语句的格式化,便于阅读和调试

8.5 注解与XML的选择

  • 注解方式:适用于简单的SQL语句,配置简单,开发效率高
  • XML方式:适用于复杂的SQL语句,维护性好,可读性强

在实际开发中,可以根据具体情况选择合适的配置方式,也可以混合使用两种方式。

8.6 XML映射文件位置配置

在Spring Boot项目中,可以在application.properties文件中指定XML映射配置文件的位置:

1
2
# 指定XML映射配置文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml

项目结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring-boot-mybatis-quickstart
└── src
└── main
├── java
│ └── com.itheima
│ ├── mapper
│ │ └── UserMapper.java
│ ├── pojo
│ └── SpringbootMybatisQuickstartApplication.java
└── resources
├── mapper
│ └── *.xml # XML映射文件放在此处
└── application.properties

8.7 MyBatisX插件

MyBatisX是一款基于IDEA的快速开发MyBatis的插件,为效率而生。

主要功能:

  • mapper.xml和mapper.java可以互相跳转
  • mybatis.xml文件支持代码提示
  • mapper接口和xml映射文件支持类似JPA的自动提示和引用

安装方式:

  1. 打开IDEA的Settings(设置)
  2. 选择Plugins(插件)
  3. 在Marketplace中搜索”mybatisx”
  4. 点击Install(安装)按钮进行安装
  5. 安装完成后重启IDEA即可使用

9. resultMap

9.1 问题描述

MyBatis在查询时,当实体类的属性名与表的字段名不一致时,会出现无法自动封装数据的问题。

例如:

  • 实体类属性:id、username、password、name、age
  • 表字段名:id、username、password、real_name、age

此时,由于namereal_name不一致,导致查询结果中name属性为null。

9.2 解决方案

方案一:使用别名

1
2
3
<select id="findAll" resultType="com.itheima.pojo.User">
select id, username, password, real_name as name, age from user
</select>

方案二:使用resultMap

resultMap是MyBatis提供的用于解决实体类属性名与表字段名不一致问题的一种方式,它可以定义映射规则,将查询结果中的列映射到实体类的属性中。

使用步骤:

  1. 在XML映射文件中定义resultMap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 定义resultMap,id为映射规则的唯一标识,type为实体类的全限定名 -->
    <resultMap id="userResultMap" type="com.itheima.pojo.User">
    <!-- id元素用于映射主键,column为表的主键字段,property为实体类的主键属性 -->
    <id column="id" property="id"/>

    <!-- result元素用于映射普通字段,column为表的字段名,property为实体类的属性名 -->
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <result column="real_name" property="name"/>
    <result column="age" property="age"/>
    </resultMap>
  2. 在查询语句中使用resultMap

    1
    2
    3
    4
    <!-- 使用resultMap属性引用定义好的映射规则,而不是使用resultType -->
    <select id="findAll" resultMap="userResultMap">
    select id, username, password, real_name, age from user
    </select>

9.3 resultMap的优势

  • 可复用性:定义一次映射规则,可以在多个查询语句中复用
  • 灵活性:支持复杂的映射关系(如一对一、一对多、多对多关联查询)
  • 可读性:将映射规则集中管理,提高代码的可读性和可维护性
  • 性能:比使用别名的方式性能更好,因为别名需要在每次查询时都进行解析

9.4 自动映射

MyBatis默认支持自动映射,即当实体类的属性名与表的字段名一致时,会自动将查询结果封装到实体类中。

自动映射的条件:

  • 实体类的属性名与表的字段名一致(不区分大小写)
  • 开启自动映射功能(默认开启)

开启自动映射的配置:

1
2
# 开启自动映射
mybatis.configuration.map-underscore-to-camel-case=true

该配置会将表中的下划线命名(如real_name)自动映射到实体类的驼峰命名(如realName)。

9.5 复杂映射

resultMap还支持复杂的映射关系,如:

  • 一对一关联:使用<association>元素
  • 一对多关联:使用<collection>元素
  • 多对多关联:通过中间表实现,结合<collection>元素

这些高级特性使得MyBatis能够处理复杂的业务场景,满足各种数据查询需求。