视频加载失败

JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发

2413 字
12 分钟
JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发

SpringBoot Web 快速入门#

@SpringBootApplication 启动类#

@SpringBootApplication
public class SpringbootWebQuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebQuickstartApplication.class, args);
}
}
  • 组合注解 = @SpringBootConfiguration + @EnableAutoConfiguration(自动配置)+ @ComponentScan(包扫描,扫启动类所在包及其子包)
  • 内嵌 Tomcat:main 一跑,应用就在内嵌服务器上起来了,不用单独装 Tomcat

@RestController + @RequestMapping(HelloController)#

@RestController // = @Controller + @ResponseBody 组合注解
public class HelloController {
@RequestMapping("/hello") // 路径映射:http://localhost:8080/hello
public String sayHello(String name) {
return "Hello, " + name + "!"; // String → 原样写进响应体
}
}
  • @Controller:标记这是 SpringMVC 控制器(内含 @Component,可被扫描当 Bean)
  • @ResponseBody:方法返回值直接写入 HTTP 响应体,不走视图渲染
  • 返回值处理分两种:String → 文本原样返回;对象/List → Jackson 消息转换器自动转 JSON
  • @RequestMapping("/hello"):把 URL 路径映射到方法;?name=xxx 形式的参数 Spring 会自动绑定到方法入参 String name

请求对象 HttpServletRequest(RequestContorller)#

@RequestMapping("/request")
public String request(HttpServletRequest request) {
String method = request.getMethod(); // 1 请求方式 GET/POST
String uri = request.getRequestURI(); // 2 请求路径(不含域名)
String url = request.getRequestURL().toString(); // 完整 URL(含域名)
String protocol = request.getProtocol(); // 3 协议 HTTP/1.1
String name = request.getParameter("name"); // 4 请求参数 ?name=xx
String accept = request.getHeader("Accept"); // 5 请求头
}
  • getParameter() 是万能取参:GET 的 query、POST 的 form 都能取到
  • URI vs URL:URI 是 /request(相对路径),URL 是 http://localhost:8080/request(完整)

响应对象 HttpServletResponse + ResponseEntity(ResponseContorller)#

// 方式一:原生 HttpServletResponse
res.setStatus(401); // 1 状态码
res.setHeader("name", "rk"); // 2 响应头
res.getWriter().write("response"); // 3 响应体
// 方式二:ResponseEntity 链式(Spring 推荐)
return ResponseEntity.status(401).header("name", "rk").body("response");
  • 两种方式完全等价:方式一是命令式,方式二是声明式链式,更优雅、更贴合 RESTful

三层分层 + IOC/DI 解耦#

三层架构职责#

Controller(接收请求/校验/返回) → Service(业务规则) → DAO(数据存取)
UserContorller @RestController UserService(Impl) UserDao(Impl) → 读 user.txt
  • 分层解决三件事:① 职责单一(每层只干一件事)② 可替换/解耦(DAO 换数据源,上层一行不改)③ 复用(业务逻辑多控制器共用)
  • 接口 + 实现分离:UserService(接口)+ UserServiceImpl(实现),上层依赖接口不依赖实现 → 面向接口编程
  • 解耦的代价是「找谁」的问题 → 交给 IoC 容器(见下)

IoC / DI(@Service / @Repository / @Autowired)#

@RestController
public class UserContorller {
@Autowired // 方式一:字段注入
private UserService userService;
// 方式二:构造器注入 @Autowired private final UserService userService; + 构造器赋值
// 方式三:setter 注入 @Autowired public void setXxx(UserService s){...}
@RequestMapping("/list")
public List<User> list() throws Exception {
return userService.findAll();
}
}
@Service // 业务层注解(内含 @Component,注册 bean)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao; // 依赖接口,从容器找实现
}
@Repository // 数据层注解(内含 @Component)
@Primary // 多实现时的优先选择
public class UserDaoImpl implements UserDao { ... }
  • 完整链路:@SpringBootApplication 包扫描 → 发现 @Service/@Repository/@Component → 创建实例放入 IoC 容器 → @Autowired 时按**类型(byType)**从容器取 → 反射(getDeclaredField + setAccessible + set)塞进私有字段
  • 容器里同类型多个实现:报错 / @Primary(优先)/ @Qualifier("名字")(精确指定)
  • 三种注入方式对比:字段注入(简洁,但依赖不可变、难测);构造器注入(官方推荐:final + 保证依赖必填);setter 注入(可选依赖)
  • IoC 注入的底层就是反射:@Autowired 字段注入 = 反射暴力注入私有字段;@Service 能被扫描发现 = 注解 + 反射解析(isAnnotationPresent)

Service 组装数据(UserServiceImpl.findAll)#

List<String> lines = userDao.findAll(); // DAO 返回原始行
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(","); // 按逗号拆
Integer id = Integer.parseInt(parts[0]);
LocalDateTime updateTime = LocalDateTime.parse(parts[5],
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 字符串→时间
return new User(id, name, username, password, age, updateTime);
}).toList();
  • DAL(数据访问层)拿原始数据,Service 层负责组装成业务对象——职责分层的体现
  • 用 Stream + Lambda 处理行数据

手写简易 IoC 容器的要点#

对照 Spring 的 IoC/DI,可以用 @Service、@Repository、@Autowired 三个自定义注解加一个简易容器(注册 → 注入 → 取用)把流程走一遍,几个关键设计:

  1. 单例职责移交容器:类不再自己维护「私有构造器 + getInstance」的饿汉单例——容器保证每类只 new 一次,对应 Spring 中 bean 默认单例由容器保证
  2. 接口也注册进容器:register 时同时以「实现类 + 它实现的所有接口」为 key 存实例,这样 @Autowired 按接口类型才能按类型(byType)找到实现
  3. 运行时可切换的状态依赖不加 @Autowired:它归业务层自己 new——容器只管「稳定协作的依赖」
  4. 字段注入演示暴力反射:@Autowired private XxxRepository 展示反射塞私有字段;生产更推荐构造器注入(依赖不可变、必填)

踩坑记录:

  • 容器按接口注入必须提前注册接口映射:若容器里只有「实现类 → 实例」,按 field.getType()(接口)取值会得到 null → IllegalStateException。解法:register 时把接口也作为 key(putIfAbsent)
  • 构造器注入改字段注入要同步删构造器:原构造器带参时,容器 newInstance() 默认走无参构造会失败;删构造器后由字段注入补上依赖
  • 容器 beans.values() 遍历会重复:同一实例被「实现类 + 接口」双 key 指向,注入逻辑执行两次(无害但日志重复)——用 Set 去重
  • 反射异常是受检异常:newInstance/set/getDeclaredConstructor 抛 Exception,调用方需声明处理(Spring 内部封装成 BeanCreationException)

原生 JDBC#

JDBC 五步(JdbcTest)#

// 1 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2 获取数据库连接
String url = "jdbc:mysql://localhost:3306/web01?serverTimezone=Asia/Shanghai&useSSL=false";
Connection conn = DriverManager.getConnection(url, "root", "123456");
// 3 获取 SQL 执行对象
Statement statement = conn.createStatement();
// 4 执行 SQL
int count = statement.executeUpdate("update user set age = 25 where id = 1");
// 5 释放资源(注意顺序:先 Statement 后 Connection)
statement.close();
conn.close();
  • 五步口诀:注册驱动 → 获取连接 → 获取 SQL 执行对象 → 执行 SQL → 释放资源
  • Class.forName 的本质:触发驱动类的静态代码块去注册驱动(新版 MySQL 驱动 SPI 自动注册可省略,但面试要会讲)
  • executeUpdate 返回受影响行数(增删改用);executeQuery 返回结果集 ResultSet(查用)

Statement vs PreparedStatement#

StatementPreparedStatement
SQL 写法字符串拼接 "where id=" + id? 占位符 + setInt(1, id)
安全性❌ SQL 注入:' or '1'='1 改语义✅ 参数与 SQL 分离,天然防注入
性能每次重编译预编译一次可复用
  • 当日示例代码用了 Statement 拼 SQL(有 SQL 注入隐患),应改用 PreparedStatement(? 占位符 + setInt);MyBatis 底层正是 PreparedStatement

POJO + Lombok(pojo/User)#

@Data @AllArgsConstructor @NoArgsConstructor
public class User {
private Integer id; private String username;
private String password; private String name; private Integer age;
}
  • Lombok 编译期注解处理器生成 getter/setter/构造器,POJO 极简

MyBatis 注解开发#

@Mapper + @Select 接口式 SQL(UserMapper)#

@Mapper // 交给 MyBatis 管理
public interface UserMapper {
@Select("select * from user") // SQL 写在注解里
public List<User> findAll();
}
  • 接口没有实现类,却能直接调用 —— 底层是 JDK 动态代理(Proxy.newProxyInstance):MyBatis 启动时扫描 @Mapper 接口 → 为它生成代理对象 → 调用 findAll() 触发 invoke() 拦截 → 解析 @Select 里的 SQL → 交给 JDBC(PreparedStatement)执行 → 结果集封装成 List<User>(数据库字段自动映射 POJO 属性)
  • 为什么 Mapper 必须是接口:JDK 动态代理要求被代理对象实现接口($Proxy extends Proxy 单继承),接口正是为了让代理”无中生有”地生成实现
  • Proxy.newProxyInstance + 无侵入增强在这里见到了真实生产应用

数据源配置(application.properties)#

spring.datasource.url=jdbc:mysql://localhost:3306/web01?serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
  • Spring Boot 自动配置:配好 spring.datasource.* 就自动装配数据源(默认 Hikari 连接池)
  • mybatis-spring-boot-starter 负责 MyBatis 与 Spring 的桥接

MySQL DDL / DQL#

SQL 四大分类#

分类全称管什么例子
DDL数据定义语言表结构CREATE TABLE / ALTER TABLE / DROP TABLE
DML数据操纵语言数据增删改INSERT / UPDATE / DELETE
DQL数据查询语言数据查SELECT
DCL数据控制语言权限GRANT / REVOKE
  • 易混点:ALTER 改表结构属于 DDL(不是 DML);update 语句属于 DML

DQL 执行顺序#

书写顺序:SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
  • WHERE 在 GROUP BY 前过滤行(不能放聚合函数);HAVING 过滤分组结果(可放聚合函数)
  • SELECT 在 ORDER BY 前 → ORDER BY 能用 SELECT 别名,WHERE 不能
  • LIMIT 最后执行 → 分页语义正确

重点速记#

  • @SpringBootApplication = 自动配置 + 包扫描;内嵌 Tomcat,main 即启动
  • @RestController = @Controller + @ResponseBody:Controller 标记控制器、ResponseBody 让返回值写进响应体(String 原文 / 对象转 JSON)
  • HttpServletRequest:getMethod / getRequestURI / getParameter / getHeader;getParameter 万能取参
  • HttpServletResponse vs ResponseEntity:setStatus/setHeader/getWriter vs 链式 .status().header().body()
  • 三层架构:Controller(请求)→ Service(业务)→ DAO(数据),接口 + 实现解耦,职责单一
  • IoC 容器 + @Autowired:包扫描存 bean → 按类型取 bean → 反射注入私有字段;多实现用 @Primary/@Qualifier;构造器注入最稳
  • JDBC 五步:注册驱动 → 连接 → 执行对象 → 执行 → 释放;Class.forName 是触发驱动静态块注册
  • Statement vs PreparedStatement:? 占位符防 SQL 注入 + 预编译复用(面试红线)
  • MyBatis @Mapper + @Select:接口 → 动态代理生成实现 → invoke 拦截 → SQL → JDBC → 封装 POJO
  • MySQL 四分类:DDL(结构)/ DML(增删改)/ DQL(查)/ DCL(权限)
  • DQL 执行顺序:FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT

代码瑕疵记录#

  1. jdbc-demo/JdbcTest:用 Statement 拼 SQL —— 有 SQL 注入隐患,应改 PreparedStatement(? 占位符 + setInt);资源释放没放 finally,异常时连接泄漏;密码 123456 硬编码
  2. jdbc-demo/pom.xml:junit 3.8.1 和 4.13.1 都加了(3.8.1 多余,应只留 4.13.1)
  3. springboot-web-01/UserContorller:类名拼写错误(少一个 r,应为 UserController);Qualifier import 未使用;@Autowired 字段注入方式 Spring 官方不推荐(应用构造器注入)
  4. springboot-web-01/UserDaoImpl:@Component 被注释掉、@Repository + @Primary 并存(实现只有一个,@Primary 多余但无害)
  5. springbott-mybatis-quickstart/application.properties:serverTimezone=UTC 与 jdbc-demo 的 Asia/Shanghai 不一致(本地无碍,统一更好)
  6. springboot-web-quickstart/ResponseContorller:response2() 声明 throws Exception 但没 checked 异常,可去掉
  7. MyBatis UserMapper 的 findAll 没写测试类(只有 pom 配了 JUnit)——建议补 @SpringBootTest 测试

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发
https://ryuki.icu/posts/javaweb-day01/
作者
RK
发布于
2026-08-10
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
RK
Long may the sunshine
公告
Long may the sunshine
分类
标签
最新动态
站点天气
天气加载中…
站点统计
文章
27
分类
6
标签
59
总字数
39,139
篝火已燃
0 天
最后活动
0 天前
每日一签