视频加载失败

JavaWeb Day02:MyBatis XML 开发与 tlias 部门管理

1922 字
10 分钟
JavaWeb Day02:MyBatis XML 开发与 tlias 部门管理

MyBatis XML 方式开发#

从注解式到 XML 式(UserMapper.java + UserMapper.xml)#

Day01 的 SQL 写在 @Select/@Insert/@Update/@Delete 注解里,现在把它们全部注释掉,SQL 迁移到 UserMapper.xml,接口只留方法签名:

@Mapper
public interface UserMapper {
List<User> findAll(); // 接口只留方法签名
Integer deleteById(int id);
Integer add(User user);
Integer update(User user);
User findByUsernameAndPassword(@Param("username") String username,
@Param("password") String password);
}
<mapper namespace="com.ryuki.mapper.UserMapper"> <!-- namespace = 接口全限定名 -->
<select id="findAll" resultType="com.ryuki.pojo.User">
select id, username, password, name, age from user
</select>
<delete id="deleteById" parameterType="int">
delete from user where id = #{id}
</delete>
<insert id="add" parameterType="com.ryuki.pojo.User">
insert into user(username, password, name, age)
values(#{username}, #{password}, #{name}, #{age})
</insert>
...
</mapper>
  • XML ↔ 接口绑定三要素:namespace = 接口全限定名、id = 接口方法名、#{参数} = 方法入参 → 调用 userMapper.findAll() 时 MyBatis 用「全限定名 + 方法名」作 key 唯一找到对应 MappedStatement(每个标签编译成一个 MappedStatement)
  • 接口依然没有实现类——底层仍是 Day01 的 JDK 动态代理,只是 SQL 来源从注解变成 XML
  • parameterType = 入参类型(单个 POJO/基本类型);resultType = 返回 POJO 类型

#{} vs ${}#

#{}${}
本质预编译参数占位符原样文本替换
底层替换成 ?,PreparedStatement.setXxx 按序设参直接拼进 SQL 字符串
安全性✅ 输入被当「值」,改不了 SQL 结构❌ 输入能改 SQL 结构 → SQL 注入
用途值(where 条件/插入值)动态列名/排序 order by ${orderCols}(占位符不能换列名表名)
  • 铁律:用户输入绝不能直接进 ${},必须过服务端白名单/枚举(排序字段映射成固定片段)
  • #{} 就是 MyBatis 版的 ? 占位符,底层是同一个 PreparedStatement 预编译机制

@Param 多参数绑定(findByUsernameAndPassword)#

User findByUsernameAndPassword(@Param("username") String username,
@Param("password") String password);
<select id="findByUsernameAndPassword" resultType="com.ryuki.pojo.User">
select ... where username = #{username} and password = #{password}
</select>
  • 多参数时 XML 里 #{参数名} 必须与 @Param("名字") 对应——否则 MyBatis 不知道 #{} 里写什么名字(会报绑定错误)
  • 单对象参数(add(User user))直接用 #{属性名},不需要 @Param

数据源 + 映射配置(application.yml)#

spring:
datasource:
url: jdbc:mysql://localhost:3306/web01?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL
mapper-locations: classpath:com/ryuki/mapper/*.xml # 指定 XML 位置
  • mapper-locations:告诉 MyBatis 去哪找 XML 映射文件(默认在接口同包同目录可省)
  • log-impl: StdOutImpl:把每条 SQL + 参数打印到控制台,调试神器

XML 映射文件的常用标签#

除增删改查四个标签外,XML 映射文件还有这些常用标签:

  • <resultMap>:显式定义结果映射
  • <sql> + <include>:抽取并复用 SQL 片段
  • <selectKey>
  • 动态 SQL 标签:if / where / set / trim / foreach / choose / when / otherwise / bind

注解式 vs XML 式 对比#

注解式XML 式
SQL 位置写在 @Select 等注解里写在 *.xml(namespace + id 绑定)
优点简单,适合简单 SQL复杂 SQL/动态 SQL 可读性好;集中管理
缺点复杂 SQL 难维护、字符串里拼 SQL 易错需要配 mapper-locations
项目tlias本工程

tlias 部门管理完整 CRUD#

三层架构完整落地#

DeptController(@RestController) → DeptService/DeptServiceImpl → DeptMapper(@Mapper 注解式)
接收请求/返回Result 业务:补 createTime/updateTime SQL 数据访问

RESTful 方法映射 + 参数绑定(DeptController)#

@RestController
@RequestMapping("/depts")
public class DeptController {
@GetMapping // GET /depts → 查询全部
public Result list() { ... }
@DeleteMapping // DELETE /depts?id=1 → 按 id 删除
public Result deleteById(Integer id) { ... }
@PostMapping // POST /depts body → 新增
public Result insert(@RequestBody Dept dept) { ... }
@GetMapping("/{id}") // GET /depts/5 → 按 id 查询
public Result findById(@PathVariable("id") Integer id) { ... }
@PutMapping // PUT /depts body → 修改
public Result update(@RequestBody Dept dept) { ... }
}
  • RESTful 风格:同一 URL /depts,靠 HTTP 方法区分动作(GET 查 / POST 增 / PUT 改 / DELETE 删)
  • @PathVariable("id"):取 URL 路径 {id} 那一段(/depts/5 → id=5)
  • @RequestBody:JSON 请求体 → 反序列化成 Java 对象(@RequestBody Dept dept = 前端 JSON 变 Dept);注意别和 @ResponseBody(对象→JSON 返回)搞混——@RestController = Controller + ResponseBody
  • 简单类型参数 Integer id 直接靠参数名绑定(Spring 用 -parameters 编译参数名,可省 @RequestParam)

Result 统一返回(pojo/Result)#

@Data
public class Result {
private Integer code; // 1 成功 / 0 失败
private String msg; // 提示信息
private Object data; // 业务数据
public static Result success() { ... } // 无数据成功
public static Result success(Object object) { ... } // 带数据成功
public static Result error(String msg) { ... } // 失败
}
  • 统一返回格式:所有接口返回同一结构 {code, msg, data},前端好统一处理(判断 code 再取 data)
  • 静态工厂方法:Result.success(deptList) 取代手动 new——调用方写起来干净(工具类思路的复用)

驼峰映射(application.yml)#

mybatis:
configuration:
map-underscore-to-camel-case: true # 下划线 → 驼峰 自动映射
  • 数据库 create_time → 实体属性 createTime 自动对应,不用再手动写列别名 create_time as createTime,一个开关搞定
  • 底层是 MyBatis 结果封装机制的一部分:结果封装有三种形式——<resultMap> 显式映射、SQL 列别名、忽略大小写自动匹配;最终都是反射创建对象 + 反射赋值,驼峰映射就是这套机制的自动化版本

Service 层补基础字段(DeptServiceImpl)#

public void insert(Dept dept) {
dept.setCreateTime(LocalDateTime.now()); // 新增:两个时间都补
dept.setUpdateTime(LocalDateTime.now());
deptMapper.insert(dept);
}
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now()); // 修改:只补 updateTime
deptMapper.update(dept);
}
  • 数据库时间字段由业务层(Service)补全,不让前端传——前端不可信
  • Mapper 注解式 SQL(@Select/@Insert/@Update/@Delete)复用 Day01 学的注解开发

POJO 极简(Dept)#

@Data @NoArgsConstructor @AllArgsConstructor
public class Dept {
private Integer id;
private String name;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

SLF4J 日志(LogTest + logback.xml)#

@Slf4j + 日志级别#

@Slf4j // Lombok 自动生成: private static final Logger log = LoggerFactory.getLogger(DeptController.class);
public class DeptController {
log.info("查询全部的部门数据");
log.debug("开始计算");
}
  • 日志级别:debug < info < warn < error(配了哪个级别,就输出它及更高级别)
  • 为什么用日志而不是 System.out.println:级别可控(生产关 debug)、能落文件、带时间/线程/类名,利于排查

logback.xml(控制台 + 文件滚动)#

<appender name="STDOUT" class="ConsoleAppender"> <!-- 控制台 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</appender>
<appender name="FILE" class="RollingFileAppender"> <!-- 滚动文件 -->
<rollingPolicy class="SizeAndTimeBasedRollingPolicy">
<FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern> <!-- 按日期分文件 + %i 序号 -->
<MaxHistory>30</MaxHistory> <!-- 只留最近 30 天 -->
<maxFileSize>10MB</maxFileSize> <!-- 单文件超 10MB 滚动 -->
</rollingPolicy>
</appender>
<root level="ALL"><appender-ref ref="STDOUT"/><appender-ref ref="FILE"/></root>
  • RollingFileAppender + SizeAndTimeBasedRollingPolicy = 日志按日期分文件 + 单文件超 10MB 滚动分卷(%i 序号)→ 解决单文件无限膨胀
  • pattern:%d 日期 / %thread 线程 / %-5level 级别 / %logger{50} 记录器名 / %msg 消息

重点速记#

  • MyBatis XML 方式:namespace = 接口全限定名、id = 方法名、#{参数} = 入参;接口仍无实现类(动态代理),SQL 从注解换成 XML
  • #{} = 预编译占位符(防注入) vs ${} = 原样文本替换(可注入,只用于动态列名/排序且须白名单)
  • @Param("名字"):多参数时 XML 里 #{名字} 的绑定依据;单对象参数直接 #{属性名}
  • mapper-locations: classpath:com/ryuki/mapper/*.xml:指定 XML 位置
  • 结果封装三形式:<resultMap> 显式映射 / SQL 列别名 / 忽略大小写自动匹配;最后反射创建对象 + 反射赋值;map-underscore-to-camel-case 是自动化版本
  • XML 标签全家桶:select/insert/update/delete + resultMap/sql+include/selectKey + 动态 SQL 九标签(if/where/set/trim/foreach/choose/when/otherwise/bind)
  • RESTful:@GetMapping/@PostMapping/@PutMapping/@DeleteMapping 同 URL 方法定动作;@PathVariable 取路径参数;@RequestBody JSON→对象
  • Result 统一返回:{code, msg, data} + 静态工厂 success()/error()
  • 驼峰映射:map-underscore-to-camel-case: true 让 create_time → createTime
  • 三层 CRUD 分工:Controller 收请求 → Service 补业务字段(时间不让前端传)→ Mapper 存取
  • 日志:@Slf4j 自动生成 logger;级别 debug<info<warn<error;logback 按日期+大小滚动、MaxHistory 30

代码瑕疵记录#

  1. springbott-mybatis-quickstart/:工程名拼写错误「springbott」少个 o(应为 springboot);UserMapper.java 注释掉的注解式 SQL 残留可清理
  2. tlias-web-management/application.yml:数据库密码 123456 硬编码;serverTimezone=UTC 与本地时区不一致
  3. logback.xml:<root level="ALL"> 全量输出,生产应 info 或按环境调;日志写死绝对路径 D:/tlias-*.log,部署性差(应相对路径/配置化)
  4. DeptServiceImpl.deleteById:删除不存在的 id 返回 success 无感知(幂等但无提示);findById 未做 null 处理直接进 Result
  5. DeptController.deleteById(Integer id):靠参数名绑定省略 @RequestParam,依赖 -parameters 编译,显式写更稳(但 Spring Boot 默认已开,属规范建议非 bug)

支持与分享

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

打赏
JavaWeb Day02:MyBatis XML 开发与 tlias 部门管理
https://ryuki.icu/posts/javaweb-day02/
作者
RK
发布于
2026-08-12
许可协议
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 天前
每日一签