JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发
2413 字
12 分钟
JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发
SpringBoot Web 快速入门
@SpringBootApplication 启动类
@SpringBootApplicationpublic 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)
// 方式一:原生 HttpServletResponseres.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)
@RestControllerpublic 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 三个自定义注解加一个简易容器(注册 → 注入 → 取用)把流程走一遍,几个关键设计:
- 单例职责移交容器:类不再自己维护「私有构造器 + getInstance」的饿汉单例——容器保证每类只 new 一次,对应 Spring 中 bean 默认单例由容器保证
- 接口也注册进容器:register 时同时以「实现类 + 它实现的所有接口」为 key 存实例,这样
@Autowired按接口类型才能按类型(byType)找到实现 - 运行时可切换的状态依赖不加 @Autowired:它归业务层自己 new——容器只管「稳定协作的依赖」
- 字段注入演示暴力反射:
@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 执行 SQLint 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
| Statement | PreparedStatement | |
|---|---|---|
| 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 @NoArgsConstructorpublic 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=UTCspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.username=rootspring.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 → LIMITWHERE在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
代码瑕疵记录
jdbc-demo/JdbcTest:用Statement拼 SQL —— 有 SQL 注入隐患,应改PreparedStatement(?占位符 + setInt);资源释放没放 finally,异常时连接泄漏;密码 123456 硬编码jdbc-demo/pom.xml:junit 3.8.1 和 4.13.1 都加了(3.8.1 多余,应只留 4.13.1)springboot-web-01/UserContorller:类名拼写错误(少一个 r,应为UserController);Qualifierimport 未使用;@Autowired字段注入方式 Spring 官方不推荐(应用构造器注入)springboot-web-01/UserDaoImpl:@Component被注释掉、@Repository+@Primary并存(实现只有一个,@Primary 多余但无害)springbott-mybatis-quickstart/application.properties:serverTimezone=UTC与 jdbc-demo 的Asia/Shanghai不一致(本地无碍,统一更好)springboot-web-quickstart/ResponseContorller:response2()声明throws Exception但没 checked 异常,可去掉- MyBatis
UserMapper的findAll没写测试类(只有 pom 配了 JUnit)——建议补@SpringBootTest测试
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
JavaWeb Day01:SpringBoot 入门与 MyBatis 注解开发
https://ryuki.icu/posts/javaweb-day01/相关文章智能推荐
1
JavaWeb Day02:MyBatis XML 开发与 tlias 部门管理
JavaWebMyBatis XML 方式开发、tlias 部门管理三层 CRUD 实战与 SLF4J 日志配置。
2
JavaWeb Day03:Tlias 员工管理之分页查询
JavaWeb员工分页查询接口实现:LIMIT 物理分页、PageResult 泛型封装与 left join 关联部门名
3
JavaWeb Day07:项目部署与 Docker 基础
JavaWebDocker 镜像、容器、仓库与分层存储概念,常用命令、数据卷、Dockerfile 与 Compose,以及三容器项目部署链路
4
JavaWeb Day05:全局异常、AOP 日志与管理模块
JavaWeb全局异常处理器与 AOP 操作日志切面,以及班级管理、学员管理和报表统计的业务实现
5
JavaWeb Day04:条件分页、事务新增与本地上传
JavaWeb条件查询改为 POJO 对象收参并以 PageHelper 分页;员工新增覆盖事务回滚、批量插入、主键回填、操作日志与本地上传
随机文章随机推荐





















