Mybatis入门操作数据配置xml映射数据封装
Mybatis入门、操作数据、配置xml映射、数据封装
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, 73. 数据库连接池详解
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.xml7.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_name
    FROM `order` o
    LEFT JOIN product p ON o.product_id = p.id
    WHERE 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 userAge
    FROM 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别名映射
通过这种混合方式,灵活处理各种映射场景。