Mybatis入门、操作数据、配置xml映射、数据封装
Mybatis入门、操作数据、配置xml映射、数据封装
- 1. MyBatis介绍、作用、相比JDBC的优势
- 1.1 什么是MyBatis?
- 1.2 MyBatis的核心作用
- 1.3 相比JDBC的优势(用"做饭"类比)
- 2. MyBatis入门实战
- 2.1 准备工作(以"图书馆借阅系统"为例)
- 2.1.1 数据库连接配置(application.properties)
- 2.1.2 创建实体类(Book.java)
- 2.2 创建Mapper接口(BookMapper.java)
- 2.3 实现查询方法(两种方式)
- 方式一:注解方式(简单SQL)
- 方式二:XML配置方式(复杂SQL)
- 2.4 IDEA配置SQL识别(避免红色警告)
- 2.5 日志输出配置
- 3. 数据库连接池详解
- 3.1 什么是连接池?(用"奶茶店"类比)
- 3.2 主流连接池对比
- 3.3 切换为Druid连接池(以"换用更智能的奶茶机"为例)
- 3.3.1 添加依赖(pom.xml)
- 3.3.2 修改配置(application.properties)
- 4. 删除数据操作
- 4.1 基础删除案例(删除过期优惠券)
- 4.2 #{}与${}的区别(用"快递签收"类比)
- 4.3 动态条件删除(用"清理衣柜"举例)
- 5. 新增和更新数据操作
- 5.1 新增数据(以"添加新会员"为例)
- 5.1.1 封装对象入参
- 5.1.2 获取自增ID(像给新会员自动分配卡号)
- 5.2 更新数据(以"修改收货地址"为例)
- 5.2.1 全字段更新(替换整个地址)
- 5.2.2 选择性更新(只改部分字段)
- 6. 查询数据操作与@Param注解
- 6.1 传递多个参数的三种方式
- 方式一:使用@Param注解(推荐)
- 方式二:使用Map传递参数(适合参数多的场景)
- 方式三:封装对象(适合参数有逻辑关联的场景)
- 6.2 @Param注解的强制使用场景
- 场景一:非Spring Boot官方骨架(如阿里云Spring Boot)
- 场景二:纯MyBatis项目(未集成Spring)
- 7. MyBatis-XML映射配置详解
- 7.1 XML映射三大规范(用"钥匙开锁"类比)
- 7.2 XML映射的使用场景
- 7.3 自定义XML文件存放路径(解决"文件乱放找不到"问题)
- 问题场景
- 解决方法:配置mapper-locations
- 7.4 高级案例:多表关联查询(以"查询订单及商品信息"为例)
- 7.4.1 定义结果映射(ResultMap)
- 7.4.2 编写关联查询SQL
- 7.5 常见问题解决:XML文件找不到的排查步骤
- 8. 数据自动封装与类型不一致解决方案
- 8.1 自动封装的默认规则("钥匙开锁"原理)
- 8.2 三种解决方案(用"快递分拣"类比)
- 8.2.1 方式一:@Results手动映射("人工分拣")
- 8.2.2 方式二:SQL别名映射("贴标签分拣")
- 8.2.3 方式三:开启驼峰命名("自动翻译机")
- 8.3 三种方式对比与选择建议
- 8.4 实战案例:混合使用三种方式
1. MyBatis介绍、作用、相比JDBC的优势
1.1 什么是MyBatis?
MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。可以将Java对象中的数据自动映射到数据库表中,也能将查询结果转换为Java对象,就像快递柜系统——帮你管理数据的"存取",无需手动处理复杂的数据库操作。
1.2 MyBatis的核心作用
- 简化数据库操作:不用手动编写JDBC代码(加载驱动、创建连接、处理结果集等)
- SQL与代码分离:SQL语句写在配置文件中,便于维护和优化
- 自动映射:Java对象与数据库表字段自动对应,减少重复代码
1.3 相比JDBC的优势(用"做饭"类比)
场景 | JDBC方式(自己做饭) | MyBatis方式(点外卖) |
---|---|---|
代码量 | 需要写10行代码(买菜、洗菜、烹饪) | 只需1行代码(下单) |
维护性 | 修改SQL需改Java代码(改菜谱需重学) | SQL单独存放(换菜品直接备注) |
安全性 | 需手动处理SQL注入(自己防骗) | 内置参数预编译(平台自动安检) |
性能优化 | 需手动实现连接池(自己买车库) | 内置连接池管理(外卖小哥调度系统) |
2. MyBatis入门实战
2.1 准备工作(以"图书馆借阅系统"为例)
需求:查询读者借阅的图书信息
技术栈:Spring Boot + MyBatis + MySQL
2.1.1 数据库连接配置(application.properties)
# 数据库连接信息(像图书馆的地址和门禁密码)
spring.datasource.url=jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=UTC
spring.datasource.username=root # 数据库用户名(图书管理员账号)
spring.datasource.password=123456 # 数据库密码(管理员密码)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# MyBatis配置
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # 日志输出(监控借阅记录)
2.1.2 创建实体类(Book.java)
public class Book {private Integer id; // 图书ID(像图书的条形码)private String name; // 书名("Java编程思想")private String author; // 作者("Bruce Eckel")private Integer borrowDays; // 借阅天数(7天)// Getter和Setter方法(相当于图书信息的"查看"和"修改"功能)public Integer getId() { return id; }public void setId(Integer id) { this.id = id; }// 省略其他getter/setter...
}
2.2 创建Mapper接口(BookMapper.java)
@Mapper // 告诉MyBatis这是数据访问接口(图书管理员工作台)
public interface BookMapper {// 查询指定读者的借阅图书(根据读者ID查图书)List<Book> getBooksByReaderId(@Param("readerId") Integer readerId);
}
2.3 实现查询方法(两种方式)
方式一:注解方式(简单SQL)
@Select("SELECT * FROM book WHERE reader_id = #{readerId}") // SQL语句(查询命令)
List<Book> getBooksByReaderId(@Param("readerId") Integer readerId);
方式二:XML配置方式(复杂SQL)
创建BookMapper.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace必须和Mapper接口全类名一致 -->
<mapper namespace="com.example.library.mapper.BookMapper"><!-- id必须和接口方法名一致 --><select id="getBooksByReaderId" resultType="com.example.library.entity.Book">SELECT id, name, author, borrow_days FROM book WHERE reader_id = #{readerId} <!-- #{readerId}是安全的参数占位符 --></select>
</mapper>
2.4 IDEA配置SQL识别(避免红色警告)
- 打开IDEA设置
File > Settings > Plugins
- 安装
MyBatisX
插件(像给IDE装"图书分类识别系统") - 右键Mapper接口 >
MyBatis > Generate Mapper XML
自动生成XML文件
2.5 日志输出配置
在application.properties
中添加:
# 打印SQL执行日志(相当于图书馆的监控录像)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
效果:控制台会显示执行的SQL语句和参数,例如:
==> Preparing: SELECT * FROM book WHERE reader_id = ?
==> Parameters: 1001(Integer)
<== Columns: id, name, author, borrow_days
<== Row: 1, Java编程思想, Bruce Eckel, 7
3. 数据库连接池详解
3.1 什么是连接池?(用"奶茶店"类比)
- 传统方式:来一个顾客开一个新锅煮奶茶(每次请求创建新数据库连接)→ 效率低
- 连接池方式:提前煮好10锅奶茶(创建10个连接)→ 顾客直接取用,喝完放回(连接复用)
3.2 主流连接池对比
连接池 | 特点(用汽车类比) | 适用场景 |
---|---|---|
HikariCP | 速度快(赛车),Spring Boot默认 | 高并发系统(外卖高峰期) |
Druid | 功能全(SUV),有监控和防SQL注入 | 企业级应用(物流调度中心) |
3.3 切换为Druid连接池(以"换用更智能的奶茶机"为例)
3.3.1 添加依赖(pom.xml)
<!-- Druid连接池依赖(相当于买新奶茶机) -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>
3.3.2 修改配置(application.properties)
# 切换为Druid连接池(指定使用新奶茶机)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource# Druid特有配置(奶茶机参数)
spring.datasource.druid.initial-size=5 # 初始连接数(开机预热5锅)
spring.datasource.druid.max-active=20 # 最大连接数(最多同时煮20锅)
spring.datasource.druid.min-idle=5 # 最小空闲连接(保底5锅)
spring.datasource.druid.test-on-borrow=true # 借连接时检测可用性(出杯前尝一口)
4. 删除数据操作
4.1 基础删除案例(删除过期优惠券)
需求:删除超过30天未使用的优惠券
Mapper接口:
public interface CouponMapper {// 无返回值方式(像扔垃圾,不需要知道扔了多少)void deleteExpiredCoupons(@Param("days") Integer days);// 有返回值方式(像清点垃圾,返回删除数量)int deleteExpiredCouponsWithCount(@Param("days") Integer days);
}
XML映射:
<!-- 无返回值删除 -->
<delete id="deleteExpiredCoupons">DELETE FROM coupon WHERE create_time < DATE_SUB(NOW(), INTERVAL #{days} DAY)
</delete><!-- 有返回值删除(返回影响行数) -->
<delete id="deleteExpiredCouponsWithCount">DELETE FROM coupon WHERE create_time < DATE_SUB(NOW(), INTERVAL #{days} DAY)
</delete>
4.2 #{}与${}的区别(用"快递签收"类比)
占位符 | 原理(签收方式) | 安全性 | 使用场景举例 |
---|---|---|---|
#{} | 预编译处理(快递柜代收,验货后签收) | 安全(防SQL注入) | 传递参数(用户ID、订单号) |
${} | 字符串拼接(直接交给收件人,不验货) | 不安全(可能收到炸弹) | 动态表名(SELECT * FROM ${tableName} ) |
危险示例:使用${}接收用户输入
// 用户输入:1; DROP TABLE user; (恶意代码)
String sql = "SELECT * FROM user WHERE id = ${id}";
// 拼接后变成:SELECT * FROM user WHERE id = 1; DROP TABLE user; (删库!)
4.3 动态条件删除(用"清理衣柜"举例)
需求:根据季节和颜色删除旧衣服
<delete id="deleteClothes">DELETE FROM clothes WHERE 1=1 <!-- 恒真条件,方便拼接AND --><if test="season != null">AND season = #{season}</if> <!-- 如:season = 'summer' --><if test="color != null">AND color = #{color}</if> <!-- 如:color = 'red' -->
</delete>
调用方式:删除所有红色夏装
clothesMapper.deleteClothes("summer", "red");
5. 新增和更新数据操作
5.1 新增数据(以"添加新会员"为例)
5.1.1 封装对象入参
实体类:
public class Member {private String name; // 姓名("张三")private Integer age; // 年龄(25)private String phone; // 电话("13800138000")// Getter/Setter省略
}
Mapper接口:
// 添加会员(参数是Member对象)
int addMember(Member member);
XML配置:
<insert id="addMember">INSERT INTO member(name, age, phone) VALUES(#{name}, #{age}, #{phone}) <!-- 直接使用对象属性名 -->
</insert>
调用方式:
Member newMember = new Member();
newMember.setName("张三");
newMember.setAge(25);
newMember.setPhone("13800138000");
memberMapper.addMember(newMember); // 像把填好的会员表交给前台
5.1.2 获取自增ID(像给新会员自动分配卡号)
<insert id="addMember" useGeneratedKeys="true" keyProperty="id"><!-- useGeneratedKeys:使用自增主键 --><!-- keyProperty:将生成的ID赋值给对象的id属性 -->INSERT INTO member(name, age) VALUES(#{name}, #{age})
</insert>
调用后获取ID:
memberMapper.addMember(newMember);
System.out.println(newMember.getId()); // 输出:10086(自动生成的ID)
5.2 更新数据(以"修改收货地址"为例)
5.2.1 全字段更新(替换整个地址)
<update id="updateAddress">UPDATE user SET province=#{province}, city=#{city}, street=#{street}WHERE id=#{userId}
</update>
5.2.2 选择性更新(只改部分字段)
<update id="updateAddressSelective">UPDATE user <set> <!-- set标签自动处理逗号 --><if test="province != null">province=#{province},</if><if test="city != null">city=#{city},</if><if test="street != null">street=#{street}</if></set>WHERE id=#{userId}
</update>
优势:只更新不为null的字段(改城市时不影响省份和街道)
6. 查询数据操作与@Param注解
6.1 传递多个参数的三种方式
方式一:使用@Param注解(推荐)
// 查询"张三"在"2023年"的订单(两个独立参数)
List<Order> getOrders(@Param("username") String username, // 显式命名参数@Param("year") String year
);
<select id="getOrders" resultType="Order">SELECT * FROM order WHERE username = #{username} AND create_time LIKE CONCAT(#{year}, '%') <!-- 如:2023% -->
</select>
方式二:使用Map传递参数(适合参数多的场景)
Map<String, Object> params = new HashMap<>();
params.put("username", "张三");
params.put("year", "2023");
List<Order> orders = orderMapper.getOrdersByMap(params);
方式三:封装对象(适合参数有逻辑关联的场景)
public class OrderQuery {private String username;private String year;// Getter/Setter
}
List<Order> getOrdersByObject(OrderQuery query);
6.2 @Param注解的强制使用场景
场景一:非Spring Boot官方骨架(如阿里云Spring Boot)
- 原因:这些骨架可能没有配置MyBatis的参数自动命名功能,导致多参数无法识别
- 解决:必须加@Param注解明确参数名
场景二:纯MyBatis项目(未集成Spring)
- 原因:MyBatis原生不支持参数名自动映射,需要通过@Param指定
- 示例:
// 纯MyBatis环境必须加@Param
List<User> findUsers(@Param("name") String name, @Param("age") Integer age);
7. MyBatis-XML映射配置详解
7.1 XML映射三大规范(用"钥匙开锁"类比)
-
同包同名:Mapper接口和XML文件放在同一个包,文件名相同
→ 例:com.example.mapper.UserMapper.java
和com.example.mapper.UserMapper.xml
-
namespace一致:XML的namespace必须等于Mapper接口的全类名
<!-- 正确 --> <mapper namespace="com.example.mapper.UserMapper"> <!-- 错误:namespace与接口名不符 --> <mapper namespace="com.example.dao.UserDao">
-
SQL id与方法名一致:XML中SQL语句的id必须等于接口中的方法名
<!-- 正确:对应UserMapper接口的getUserById方法 --> <select id="getUserById" resultType="User"> <!-- 错误:id与方法名不符 --> <select id="selectUser" resultType="User">
7.2 XML映射的使用场景
场景 | 适合方式 | 原因分析 |
---|---|---|
简单SQL(单表CRUD) | 注解方式 | 代码集中,直观简洁 |
复杂SQL(多表关联、动态SQL) | XML方式 | SQL与Java代码分离,便于维护 |
需要团队协作编写SQL | XML方式 | DBA可直接修改XML文件 |
7.3 自定义XML文件存放路径(解决"文件乱放找不到"问题)
问题场景
如果不按规范放(如XML文件想放resources/mapper
目录下),MyBatis会找不到XML文件
解决方法:配置mapper-locations
在application.properties中添加:
# 指定XML文件存放路径(classpath表示resources目录)
mybatis.mapper-locations=classpath:mapper/*.xml
目录结构:
src/main/resources/
└── mapper/├── UserMapper.xml└── OrderMapper.xml
7.4 高级案例:多表关联查询(以"查询订单及商品信息"为例)
7.4.1 定义结果映射(ResultMap)
<resultMap id="OrderAndProductMap" type="OrderVO"><!-- 订单表字段 --><id property="orderId" column="order_id"/><result property="orderTime" column="order_time"/><!-- 关联商品表(一对一) --><association property="product" javaType="Product"><id property="productId" column="product_id"/><result property="productName" column="product_name"/></association>
</resultMap>
7.4.2 编写关联查询SQL
<select id="getOrderWithProduct" resultMap="OrderAndProductMap">SELECT o.id as order_id, o.time as order_time,p.id as product_id, p.name as product_nameFROM `order` oLEFT JOIN product p ON o.product_id = p.idWHERE o.id = #{orderId}
</select>
7.5 常见问题解决:XML文件找不到的排查步骤
- 检查文件名:确保XML文件名与接口名完全一致(含大小写)
- 检查路径:确认XML文件在resources目录下,且与配置的mapper-locations匹配
- Maven打包检查:在pom.xml中添加资源文件打包配置(防止XML被过滤)
<build><resources><resource><directory>src/main/resources</directory><includes><include>**/*.xml</include> <!-- 确保XML被打包 --></includes></resource></resources>
</build>
8. 数据自动封装与类型不一致解决方案
8.1 自动封装的默认规则("钥匙开锁"原理)
MyBatis的数据封装就像钥匙匹配锁芯——数据库列名必须与实体类属性名完全一致才能自动映射。
示例:
- 数据库表字段:
user_name
(下划线命名) - 实体类属性:
userName
(驼峰命名)
→ 默认不匹配:MyBatis找不到对应的属性,封装结果为null
8.2 三种解决方案(用"快递分拣"类比)
8.2.1 方式一:@Results手动映射(“人工分拣”)
适用场景:字段名与属性名差异大(如u_id
→userId
)
实现步骤:
- 在Mapper接口方法上添加
@Results
注解 - 用
@Result
指定列名与属性名的对应关系
代码示例(查询用户信息):
@Select("SELECT u_id, u_name, u_age FROM t_user WHERE u_id = #{id}")
@Results({@Result(column = "u_id", property = "userId"), // 列u_id → 属性userId@Result(column = "u_name", property = "userName"),// 列u_name → 属性userName@Result(column = "u_age", property = "userAge") // 列u_age → 属性userAge
})
User getUserById(Integer id);
优点:灵活性高,可自定义映射规则
缺点:代码冗余,每个方法需单独配置
8.2.2 方式二:SQL别名映射(“贴标签分拣”)
适用场景:少量字段不一致,临时调整
实现步骤:在SQL中用AS
关键字为列名起别名,使其与属性名一致
代码示例:
<select id="getUser" resultType="User">SELECT u_id AS userId, <!-- 别名userId匹配实体类属性 -->u_name AS userName,u_age AS userAgeFROM t_user WHERE u_id = #{id}
</select>
生活类比:给快递包裹贴上新标签(别名),确保快递员(MyBatis)能正确识别目的地
8.2.3 方式三:开启驼峰命名(“自动翻译机”)
适用场景:数据库列名用下划线(如user_name),实体类用驼峰命名(userName)的统一规则场景
实现步骤:在application.properties
中添加全局配置
# 开启下划线转驼峰命名(如user_name → userName)
mybatis.configuration.map-underscore-to-camel-case=true
效果:自动映射以下场景
- 数据库列:
order_id
→ 实体类属性:orderId
- 数据库列:
create_time
→ 实体类属性:createTime
注意:需保证列名和属性名的核心部分一致(如order_id
和orderId
的"order"部分匹配)
8.3 三种方式对比与选择建议
解决方式 | 配置复杂度 | 适用场景 | 维护成本 |
---|---|---|---|
手动映射 | 高 | 字段名与属性名差异大(如u_id→userId) | 高(每个方法单独维护) |
SQL别名 | 低 | 少量字段不一致,临时调整 | 中(SQL中维护别名) |
驼峰命名 | 极低 | 统一使用下划线/驼峰命名规则 | 低(全局配置) |
选择建议:
- 新项目:优先使用驼峰命名(一劳永逸)
- 老项目:少量字段用SQL别名,大量不匹配用手动映射
- 特殊场景(如列名含缩写):必须用手动映射
8.4 实战案例:混合使用三种方式
需求:查询订单信息,包含以下字段映射
o_id
→orderId
(手动映射)order_sn
→orderSn
(驼峰命名,需开启配置)total_amt
→totalAmount
(SQL别名)
实现步骤:
- 开启驼峰命名配置
- Mapper接口方法:
@Select("SELECT o_id, order_sn, total_amt AS totalAmount FROM t_order WHERE o_id = #{id}")
@Results({@Result(column = "o_id", property = "orderId") // 手动映射特殊字段
})
Order getOrderById(Integer id);
解析:
o_id
→orderId
:手动映射(因"o_"前缀无法通过驼峰转换)order_sn
→orderSn
:驼峰命名自动转换(o_sn→orderSn)total_amt AS totalAmount
:SQL别名映射
通过这种混合方式,灵活处理各种映射场景。