JavaWeb学习
1. JavaWeb 基础
前端部分暂时跳过
1.1 web基础知识
1.1.1 SpringBoot 项目的结构
springboot-demo/
│
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/demo/
│ │ │ ├── DemoApplication.java ← 程序入口(启动类)
│ │ │ ├── controller/ ← 控制层(接收请求)
│ │ │ ├── service/ ← 业务逻辑层
│ │ │ ├── mapper/ ← 数据访问层(MyBatis接口)
│ │ │ └── entity/ ← 实体类(与数据库表对应)
│ │ ├── resources/
│ │ │ ├── application.properties ← 核心配置文件
│ │ │ ├── static/ ← 静态资源(js、css、图片)
│ │ │ ├── templates/ ← 页面模板(如 Thymeleaf)
│ │ │ └── mapper/ ← MyBatis 的 XML 映射文件
│ │ └── ...
│ └── test/
│ └── java/
│ └── com/example/demo/
│ └── DemoApplicationTests.java ← 单元测试类
│
├── pom.xml ← Maven 配置文件(依赖管理)
└── README.md ← 项目说明(可选)
自动生成的 Application 是整个 Spring Boot 项目的 启动入口类,作用为:
- 项目启动时从这里执行;
- 启动内置的 Tomcat;
- 加载所有 Spring 配置;
- 扫描组件(Controller、Service、Repository 等);
- 运行你的 Web 应用或后台服务。
1.2 相关注解解释
| 注解 | 所属层 | 主要功能 | 注入方式 |
|---|---|---|---|
@RestController |
控制层 | 定义 REST 接口,返回 JSON 数据 | —— |
@RequestMapping |
控制层 | 映射请求路径 | —— |
@Resource |
通用 | 按名称注入 Bean(JDK 标准) | byName → byType |
@Repository |
数据层 | 标识 DAO 层组件,异常转换 | —— |
@Service |
业务层 | 标识业务逻辑组件 | —— |
@Autowired |
通用 | 按类型注入 Bean(Spring 特有) | byType |
@Component |
通用 | 标注不属于 Controller / Service / Repository 层的通用类 | —— |
-
@RestController是 控制层(Controller) 的注解,用于定义一个 RESTful 风格的控制器。等价于:
@Controller + @ResponseBody功能:
- 表示该类负责处理 HTTP 请求;
- 返回的数据不是页面,而是 JSON / XML 等数据格式;
- 通常用于开发前后端分离的接口。
-
@RequestMapping用于 映射请求路径 到控制器方法或类上。常用位置:
- 类上:定义公共路径前缀;
- 方法上:定义具体的请求路径。
Spring 提供了更细粒度的注解:
注解 说明 @GetMapping处理 GET 请求 @PostMapping处理 POST 请求 @PutMapping处理 PUT 请求 @DeleteMapping处理 DELETE 请求 -
@Resource是 JDK 提供的依赖注入注解(属于javax.annotation包)。用于 自动注入 Bean 对象(类似于@Autowired)。特点:
- 默认按 名称(byName) 注入;
- 如果找不到同名 Bean,则按类型(byType)匹配;
- 可以通过
name属性显式指定 Bean 名称。
-
@Repository用于标注 持久层(DAO / Mapper) 组件。功能:
- 表示该类是与数据库交互的组件;
- 使异常转换机制生效(Spring 会将数据库异常转换为统一的 Spring DataAccessException)。
在 MyBatis 中,一般接口上不用手动加
@Repository,因为 Mapper 会被自动扫描注册为 Bean。 -
@Service用于标注 业务逻辑层(Service 层) 的组件。功能:
- 标识业务逻辑类;
- 让 Spring 自动将其注册为 Bean;
- 便于与 Controller、Repository 层区分。
-
@Autowired:Spring 的依赖注入(DI)注解,用于自动装配 Bean。特点:
- 按 类型(byType) 自动注入;
- 如果有多个同类型 Bean,可以用
@Qualifier指定; - 也可以和构造方法、Setter、字段一起使用。
-
@Component是 Spring 中最基础的 组件注解,表示一个类被 Spring 管理,成为 IOC 容器中的 Bean。只要类上加了 @Component,Spring 启动时就会自动创建这个类的对象,并放进容器中管理(实现依赖注入)。
使用场景:
一般用于标注不属于 Controller / Service / Repository 层的通用类;
1.2.1 代码案例
- 通过前端页面来请求本地的文件中的数据,然后将数据显示在前端中
@RestController
public class UserController {
//放在 static/user.html 下的 HTML 文件,就相当于网站的一个页面。
//所以使用 http://localhost:8080/list 会返回原始数据,后端接口,只返回数据(JSON 格式)。
// 使用http://localhost:8080/user.html显示前端页面,前端页面,展示 UI,并通过 JS 请求后端数据。
@RequestMapping("/list")
public List<Day250928_User> list(){
// 1. 读取资源文件(user.txt),文件放在 resources/static 目录下
//从 classpath(即 src/main/resources 下的资源目录)查找资源文件 "static/user.txt"。
InputStream in = this.getClass().getClassLoader().getResourceAsStream("static/user.txt");
// 2. 用 Hutool 工具类把文件内容读成一行一行的字符串,然后把每一行作为一个 String 放到这个 ArrayList 里。
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
// 3. 把每一行字符串解析成 User 对象
List<Day250928_User> userList = lines.stream().map(line -> {
//stream() 把列表转换成 流,方便用函数式操作(map、filter、collect 等)。
//map 会对流中的每一个元素执行 转换操作,这里每个元素就是 line(一行文本)。
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
String sex = parts[4];
Integer age = Integer.parseInt(parts[5]);
LocalDateTime updateTime = LocalDateTime.parse(parts[6],
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new Day250928_User(id, username, password, name, sex, age, updateTime);
}).collect(Collectors.toList());//collect(Collectors.toList()) 把处理后的对象流 收集成一个 List。
//上面这是lambda表达式,下面这是完整写法,是重写了Function接口
InputStream in2 = this.getClass().getClassLoader().getResourceAsStream("static/user.txt");
ArrayList<String> lines2 = IoUtil.readLines(in2, StandardCharsets.UTF_8, new ArrayList<>());
List<Day250928_User> userList2 = lines2.stream().map(new Function<String, Day250928_User>() {
@Override
public Day250928_User apply(String line) {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
String sex = parts[4];
Integer age = Integer.parseInt(parts[5]);
LocalDateTime updateTime = LocalDateTime.parse(parts[6],
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new Day250928_User(id, username, password, name, sex, age, updateTime);
}
}).collect(Collectors.toList());//collect(Collectors.toList()) 把处理后的对象流 收集成一个 List。
return userList2;// 4. 返回用户列表 -> Spring Boot 自动把它转成 JSON
// 因为类上有 @RestController,Spring Boot 会自动把这个对象序列化为 JSON,再写到 HTTP 响应里。
}
}
上述代码将所有的处理逻辑都放在了controller方法中,为了解耦,实现三层架构:
- Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
将上述代码进行分开:
- 控制层
@RestController
public class newUserController {
//private UserServiceImpl userService = new UserServiceImpl();
/**
* 必须初始化,或者在其他的地方进行初始化,不然会报错,但是这种方式,
* NewUserController 直接依赖 UserServiceImpl 的具体实现。
* 如果将来想换成 UserServiceV2Impl,你必须修改 Controller 的代码,才能替换:
* private UserServiceImpl userService = new UserServiceV2Impl();
* 同理Dao也是这个问题,
*
* 理想的低耦合做法
* Controller 应该只依赖 接口(UserService),而不是具体实现(UserServiceImpl)。
* 哪个实现类要被注入,由 Spring 容器来决定。
* */
//解决:在对应的实现类前加 注解
// @Autowired // 自动注入 Spring 容器中的 NewService 实现
// private newService userService;//如果有多个相同的bean注入时,这个会报错,有以下三种解决方案
/**
* 使用@Primary注解 当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
* 使用@Qualifier注解
* 指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称,名称首字母小写。 @Qualifier注解不能单独使用,必须配合@Autowired使用。
* 使用@Resource注解
* 是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。*/
// @Qualifier("userServiceImpl2")//这里为首的字母要小写
// @Autowired
// private newService userService;
@Resource(name = "userServiceImpl2")
private newService userService;
@RequestMapping("/newList")
public List<Day250928_User> newList(){
System.out.println("newList");
return userService.findAll();
}
}
- 业务逻辑层
public interface newService {
public List<Day250928_User> findAll();
}
@Service // 告诉 Spring 这是一个业务层 Bean,要交给 IOC 容器管理 为了解耦层与层之间的耦合
public class UserServiceImpl2 implements newService{
//private UserDaoImpl userDao = new UserDaoImpl();
@Autowired
private newDao userDao;
@Override
public List<Day250928_User> findAll() {
List<String> lines = userDao.findAll();
List<Day250928_User> list = lines.stream().map(
new Function<String, Day250928_User>() {
@Override
public Day250928_User apply(String s) {
String[] parts = s.split(",");
Integer id = Integer.valueOf(parts[0]);
String username = parts[1];
String password = parts[2];
String name = "new" + parts[3];
String sex = parts[4];
Integer age = Integer.valueOf(parts[5]);
LocalDateTime updateTime = LocalDateTime.parse(parts[6],
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new Day250928_User(id, username, password, name, sex, age, updateTime);
}
}
).collect(Collectors.toList());
return list;
}
}
- 数据访问层
public interface newDao {
public List<String> findAll();
}
@Repository //
public class UserDaoImpl implements newDao {
@Override
public List<String> findAll() {
InputStream in = this.getClass().getClassLoader().getResourceAsStream("static/user.txt");
List<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
return lines;
}
}
其中,如果利用以前的只是,就像控制层中注释中写的那样 private UserServiceImpl userService = new UserServiceImpl(); 需要什么对象,直接new一个对象,如果说我们需要更换实现类,比如由于业务的变更,UserServiceImpl 不能满足现有的业务需求,我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 UserServiceImpl2 的实现new UserServiceImpl2() 。这样会导致层与层之间相互关联,因此有了 @Resource ,@Repository, @Service , @Autowired 等注解,
通过使用注解来代替手动new对象,通过容器来管理要使用的对象
1.2 web后端基础(数据库)
SQL 语句
DDL(Data Definition Language)语句 — 数据定义语言
DDL 用于定义和管理数据库对象,如数据库、表、字段等。
-
数据库操作
操作 SQL 示例 查询所有数据库 SHOW DATABASES;查询当前数据库 SELECT DATABASE();创建数据库 CREATE DATABASE [IF NOT EXISTS] 数据库名 [DEFAULT CHARSET utf8mb4];使用数据库 USE 数据库名;删除数据库 DROP DATABASE [IF EXISTS] 数据库名;
- 表操作
-
创建表
CREATE TABLE 表名 ( 字段1 字段1类型 [约束] [COMMENT '字段1注释'], 字段2 字段2类型 [约束] [COMMENT '字段2注释'], ... 字段n 字段n类型 [约束] [COMMENT '字段n注释'] ) [COMMENT '表注释'];-
常用数据类型(简要)
类型类别 示例 说明 整数类型 INT,BIGINT存储整数 小数类型 DECIMAL(10,2)存储精确小数 字符串类型 CHAR(10),VARCHAR(50)存储字符串 日期时间类型 DATE,DATETIME,TIMESTAMP存储时间日期 布尔类型 BOOLEAN存储真或假(1/0) -
常用约束
约束名 描述 关键字 非空约束 限制该字段值不能为 NULL NOT NULL唯一约束 保证字段值唯一、不重复 UNIQUE主键约束 表中唯一标识每一行数据,且不能为空 PRIMARY KEY默认约束 未指定值时使用默认值 DEFAULT外键约束 建立两表之间关系,保证数据一致性 FOREIGN KEY主键自增 自动生成主键值 AUTO_INCREMENT
-
-
表结构管理
操作 SQL 示例 查询当前数据库所有表 SHOW TABLES;查看表结构 DESC 表名;查看建表语句 SHOW CREATE TABLE 表名;添加字段 ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT '注释'] [约束];修改字段类型 ALTER TABLE 表名 MODIFY 字段名 新类型(长度);修改字段名及类型 ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT '注释'] [约束];删除字段 ALTER TABLE 表名 DROP 字段名;修改表名 RENAME TABLE 旧表名 TO 新表名;删除表 DROP TABLE [IF EXISTS] 表名;
DML(Data Manipulation Language)语句 — 数据操作语言
DML 用于对表中数据进行增、删、改操作。
-
增加数据(INSERT)
类型 SQL 示例 指定字段插入 INSERT INTO 表名 (字段1, 字段2) VALUES (值1, 值2);全字段插入 INSERT INTO 表名 VALUES (值1, 值2, ...);批量插入(指定字段) INSERT INTO 表名 (字段1, 字段2) VALUES (值1, 值2), (值3, 值4);批量插入(全字段) INSERT INTO 表名 VALUES (值1, 值2, ...), (值3, 值4, ...);
-
修改数据(UPDATE)
UPDATE 表名 SET 字段1 = 值1, 字段2 = 值2, ... [WHERE 条件];⚠️ 建议使用
WHERE限定条件,否则会修改全表数据。
-
删除数据(DELETE)
DELETE FROM 表名 [WHERE 条件];
DQL(Data Query Language)语句 — 数据查询语言
用于查询数据,是 SQL 的核心部分。
基本语法结构
SELECT
字段列表
FROM
表名列表
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后条件列表
ORDER BY
排序字段列表
LIMIT
分页参数;
-
基本查询
功能 SQL 示例 查询多个字段 SELECT 字段1, 字段2 FROM 表名;查询所有字段 SELECT * FROM 表名;设置字段别名 SELECT 字段1 AS 别名1, 字段2 AS 别名2 FROM 表名;去除重复记录 SELECT DISTINCT 字段名 FROM 表名;
- 条件查询(WHERE)
SELECT 字段列表
FROM 表名
WHERE 条件列表;
-
比较运算符
运算符 功能 >大于 >=大于等于 <小于 <=小于等于 =等于 <>或!=不等于 BETWEEN ... AND ...在指定范围内(含边界) IN (...)在指定集合中 LIKE模糊匹配( _单个字符,%任意字符)IS NULL判断是否为空 -
逻辑运算符
运算符 功能 AND/&&并且(多个条件同时成立) OR/ `或者(任意一个成立) NOT/!取反(条件不成立)
-
聚合函数(Aggregate Functions)
函数 作用 COUNT(字段)统计行数(忽略 NULL) SUM(字段)求和 MAX(字段)最大值 MIN(字段)最小值 AVG(字段)平均值 💡 聚合函数通常与
GROUP BY搭配使用。
-
分组查询(GROUP BY)
SELECT 分组字段, 聚合函数(...) FROM 表名 [WHERE 条件] GROUP BY 分组字段 [HAVING 分组后条件];说明:
WHERE在分组前过滤;HAVING在分组后过滤;- 通常配合聚合函数使用。
-
排序查询(ORDER BY)
SELECT 字段列表 FROM 表名 ORDER BY 字段1 [ASC|DESC], 字段2 [ASC|DESC];排序方式 说明 ASC升序(默认) DESC降序
-
分页查询(LIMIT)
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询条数;示例:
LIMIT 0, 5; -- 从第0条开始,查询5条记录💡 常用于前端分页显示。
JDBC
package com.example;
import org.junit.jupiter.api.Test;
import java.sql.*;
public class jdbcTest {
/**jdbc 入门程序*/
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//准备工作
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/web01";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
//执行sql
int i = stmt.executeUpdate("update user set password = '11111111' where id = 1");//静态sql
System.out.println("sql 语句执行后 影响的 记录数: " + i);
//释放资源
stmt.close();
conn.close();
}
@Test
public void testJdbc() throws SQLException {
//获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web01", "root", "123456");
//常见预编译的 PreparedStatement 对象
PreparedStatement pstmt = conn.prepareStatement("select * from user where username = ? and password = ?");
//预编译sql,更加安全,防止sql注入,性能更高
/**
* 静态sql
* 使用普通的字符串拼接方式:
* String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'";
* Statement stmt = conn.createStatement();
* ResultSet rs = stmt.executeQuery(sql);
*
* 如果攻击者输入:
* username: admin
* password: ' OR '1'='1
*
* 那么拼接后的 SQL 实际是:
* SELECT * FROM user WHERE username = 'admin' AND password = '' OR '1'='1'
* '1'='1' 恒为真 → 查询返回所有用户 → 登录绕过。
*
* SQL 预编译阶段
* 数据库在第一次接收到带问号(?)的 SQL 模板时,会先对 SQL 结构 进行编译、优化、生成执行计划。
* 此时,SQL 的结构是固定的,? 只是参数占位符。
*
* 参数绑定阶段
* 通过 setString() 等方法传入参数时,这些值会被当作数据(Data)绑定到执行计划中,而不会改变 SQL 的语法结构。*/
//设置参数
pstmt.setString(1, "daqiao");//第一个问号对应的参数
pstmt.setString(2, "123456");//第二个问号对应的参数
//执行查询
ResultSet rs = pstmt.executeQuery();
//处理结果集
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("ID: " + id + ", Username: " + username +
", Password: " + password + ", Name: " + name + ", Age: " + age);
}
//关闭资源
rs.close();
pstmt.close();
conn.close();
}
}
mybatis
- 创建springboot工程,并导入 mybatis的起步依赖、mysql的驱动包、lombok。项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖。
- 数据准备:创建用户表user,并创建对应的实体类User。
-
配置Mybatis,在 application.properties 中配置数据库的连接信息。
# 配置查看日志 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # 使用 druid 这个 数据库连接池 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql://localhost:3306/web01 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 # 指定 xml 映射配置文件的位置 mybatis.mapper-locations=classpath:mapper/*.xml - 上一步既可以将配置代码写到上述文件中,也可以直接使用xml文件来进行配置,但要注意xml文件的位置,以及xml文件中包名要正确
<?xml version="1.0" encoding="UTF-8"?> <!--<beans xmlns="http://www.springframework.org/schema/beans"--> <!-- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"--> <!-- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">--> <!--</beans>--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.day251007_springbootmybatisquickstart.mapper.UserMapper"> <!-- 查询操作 --> <select id="findAll" resultType="com.example.day251007_springbootmybatisquickstart.pojc.User"> select * from user </select> </mapper> -
编写Mybatis程序:编写Mybatis的持久层接口,定义SQL语句(注解). 在创建出来的springboot工程中,在引导类所在包下,在创建一个包 mapper 。在 mapper 包下创建一个接口 UserMapper ,这是一个持久层接口(Mybatis的持久层接口规范一般都叫 XxxMapper)。
package com.example.day251007_springbootmybatisquickstart.mapper; import com.example.day251007_springbootmybatisquickstart.pojc.User; import org.apache.ibatis.annotations.*; import java.util.List; // 编写mybatis 的持久层接口 @Mapper //应用程序在运行时,会自动的为该接口创建一个实现类对象(代理对象),并且会自动地将该实现类对象存入IOC容器中 public interface UserMapper { /** * 查询所有用户 注释掉注解来进行测试 xml */ //@Select("select * from user") public List<User> findAll(); /** 删除 的两种方式 */ @Delete("delete from user where id = 5") public Integer deleteById1(); //通过参数占位符号 #{...} 来占位,在调用deleteById方法时,传递的参数值,最终会替换占位符。 @Delete("delete from user where id = #{id}") public Integer deleteById2(Integer id); /** 增 */ @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(String username,String password); } -
单元测试; 在创建出来的SpringBoot工程中,在src下的test目录下,已经自动帮我们创建好了测试类 ,并且在测试类上已经添加了注解 @SpringBootTest,代表该测试类已经与SpringBoot整合。 该测试类在运行时,会自动通过引导类加载Spring的环境(IOC容器)。我们要测试那个bean对象,就可以直接通过@Autowired注解直接将其注入进行,然后就可以测试了。
package com.example.day251007_springbootmybatisquickstart; import com.example.day251007_springbootmybatisquickstart.mapper.UserMapper; import com.example.day251007_springbootmybatisquickstart.pojc.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 Day251007SpringbootMybatisQuickstartApplicationTests { @Autowired private UserMapper userMapper; @Test public void testFindAll(){ List<User> users = userMapper.findAll(); for (User user : users) { System.out.println(user); } } @Test public void testFindById(){ System.out.println(userMapper.deleteById1()); System.out.println(userMapper.deleteById2(1)); } @Test public void testInsert(){ User user = new User(); user.setUsername("lililili"); user.setPassword("55555555"); user.setAge(18); user.setName("李莉莉"); userMapper.insert(user); } @Test public void testUpdate(){ User user = new User(); user.setId(6); user.setUsername("lililili"); user.setPassword("88888888"); user.setAge(18); user.setName("李莉莉"); userMapper.update(user); } @Test public void testSelectByUsernameAndPassword(){ User u = userMapper.findByUsernameAndPassword("lililili","88888888"); System.out.println(u); } }