Mybatis入门、操作数据、配置xml映射、数据封装

发布于:2025-09-07 ⋅ 阅读:(14) ⋅ 点赞:(0)

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识别(避免红色警告)

  1. 打开IDEA设置 File > Settings > Plugins
  2. 安装MyBatisX插件(像给IDE装"图书分类识别系统")
  3. 右键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映射三大规范(用"钥匙开锁"类比)

  1. 同包同名:Mapper接口和XML文件放在同一个包,文件名相同
    → 例:com.example.mapper.UserMapper.javacom.example.mapper.UserMapper.xml

  2. namespace一致:XML的namespace必须等于Mapper接口的全类名

    <!-- 正确 -->
    <mapper namespace="com.example.mapper.UserMapper">
    <!-- 错误:namespace与接口名不符 -->
    <mapper namespace="com.example.dao.UserDao">
    
  3. 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_name
    FROM `order` o
    LEFT JOIN product p ON o.product_id = p.id
    WHERE o.id = #{orderId}
</select>

7.5 常见问题解决:XML文件找不到的排查步骤

  1. 检查文件名:确保XML文件名与接口名完全一致(含大小写)
  2. 检查路径:确认XML文件在resources目录下,且与配置的mapper-locations匹配
  3. 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_iduserId
实现步骤

  1. 在Mapper接口方法上添加@Results注解
  2. @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_idorderId的"order"部分匹配)

8.3 三种方式对比与选择建议

解决方式 配置复杂度 适用场景 维护成本
手动映射 字段名与属性名差异大(如u_id→userId) 高(每个方法单独维护)
SQL别名 少量字段不一致,临时调整 中(SQL中维护别名)
驼峰命名 极低 统一使用下划线/驼峰命名规则 低(全局配置)

选择建议

  • 新项目:优先使用驼峰命名(一劳永逸)
  • 老项目:少量字段用SQL别名,大量不匹配用手动映射
  • 特殊场景(如列名含缩写):必须用手动映射

8.4 实战案例:混合使用三种方式

需求:查询订单信息,包含以下字段映射

  • o_idorderId(手动映射)
  • order_snorderSn(驼峰命名,需开启配置)
  • total_amttotalAmount(SQL别名)

实现步骤

  1. 开启驼峰命名配置
  2. 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_idorderId:手动映射(因"o_"前缀无法通过驼峰转换)
  • order_snorderSn:驼峰命名自动转换(o_sn→orderSn)
  • total_amt AS totalAmount:SQL别名映射

通过这种混合方式,灵活处理各种映射场景。


网站公告

今日签到

点亮在社区的每一天
去签到