MyBatis 持久层框架:SQL 映射与动态查询
MyBatis 持久层框架教程:掌握 SQL 映射与动态查询
1. 什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及参数设置和结果集检索的工作。它通过简单的 XML 或注解,将 Java 接口和 POJO(Plain Old Java Objects)映射成数据库中的记录。
核心优势:
- 精准的 SQL 控制:你可以直接编写最优化性能的 SQL,不会被框架生成的低效 SQL 所束缚。
- 动态 SQL:根据条件灵活拼接 SQL,避免了复杂的字符串拼接和容易出错的逻辑判断。
- 简洁的映射:通过 XML 或注解配置,自动将查询结果映射到 Java 对象,支持复杂关系映射(一对一、一对多)。
- 轻量级集成:与 Spring、Spring Boot 无缝结合,易于引入和使用。
2. 环境准备与基本配置
在开始之前,确保你的项目已引入 MyBatis 依赖(Maven 示例):
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- 若与 Spring 集成,还需 mybatis-spring -->
核心配置文件 mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
3. SQL 映射文件详解
SQL 映射文件是 MyBatis 的核心,它将 SQL 语句与 Mapper 接口的方法绑定。
3.1 基础映射示例
定义一个 User 实体:
public class User {
private Integer id;
private String username;
private String email;
// getters & setters...
}
创建 Mapper 接口:
public interface UserMapper {
User selectUserById(Integer id);
void insertUser(User user);
}
编写 UserMapper.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">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 定义 resultMap 解决列名与属性名不一致的情况 -->
<resultMap id="userResultMap" type="com.example.entity.User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="email" column="user_email"/>
</resultMap>
<!-- 根据 ID 查询 -->
<select id="selectUserById" parameterType="int" resultMap="userResultMap">
SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
</select>
<!-- 插入用户,并获取自增主键 -->
<insert id="insertUser" parameterType="com.example.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (user_name, user_email) VALUES (#{username}, #{email})
</insert>
</mapper>
关键点:
namespace必须与接口的全限定名一致。#{属性名}会使用预编译占位符?,安全防止 SQL 注入。- 使用
resultMap可以定制复杂的映射关系。
4. 动态 SQL:让查询更智能
动态 SQL 是 MyBatis 最强大的特性之一,允许你根据传入参数的条件,动态生成 SQL 片段。
4.1 <if> 条件判断
<select id="findUsersByCondition" resultMap="userResultMap">
SELECT * FROM users
WHERE 1=1
<if test="username != null and username != ''">
AND user_name LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND user_email = #{email}
</if>
</select>
test 属性内使用 OGNL 表达式,可以访问元素的属性值。
4.2 <where> 优雅处理开头 AND/OR
上面的 WHERE 1=1 并不优雅,可用 <where> 标签自动移除第一个条件开头的 AND 或 OR。
<select id="findUsersByCondition" resultMap="userResultMap">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND user_name LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND user_email = #{email}
</if>
</where>
</select>
4.3 <choose>, <when>, <otherwise> 多条件选择
类似 Java 的 switch 语句,只选择第一个匹配的条件。
<select id="findUserByPriority" resultMap="userResultMap">
SELECT * FROM users
<where>
<choose>
<when test="id != null">
user_id = #{id}
</when>
<当 test="username != null">
user_name = #{username}
</when>
<otherwise>
user_email IS NOT NULL
</otherwise>
</choose>
</where>
</select>
4.4 <set> 动态更新
更新时自动处理多余的逗号。
<update id="updateUserSelective">
UPDATE users
<set>
<if test="username != null">user_name = #{username},</if>
<if test="email != null">user_email = #{email},</if>
</set>
WHERE user_id = #{id}
</update>
4.5 <foreach> 遍历集合
用于构建 IN 条件或批量插入。
<select id="findUsersByIds" resultMap="userResultMap">
SELECT * FROM users WHERE user_id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
collection:指定集合参数名,若为 List 可写list,数组写array。item:每次迭代的元素。open,separator,close:拼接的符号。
5. MyBatis 与 Spring Boot 快速集成
在现代开发中,几乎不再单独使用 MyBatis,而是与 Spring Boot 整合。
添加依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
application.yml 配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true # 自动驼峰命名转换
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
定义 Mapper 接口时,使用 @Mapper 注解或在配置类上加 @MapperScan。
@Mapper
public interface UserMapper {
User selectUserById(Integer id);
List<User> findUsersByCondition(@Param("username") String username,
@Param("email") String email);
}
6. 进阶映射:一对一、一对多
6.1 一对一关联
假设一个用户对应一个扩展信息表 user_detail。
UserDetail 实体包含 address, phone 等字段。在 User 中添加 private UserDetail detail;
映射文件使用 <association> 完成一对一映射:
<resultMap id="userWithDetail" type="User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<association property="detail" javaType="UserDetail">
<id property="id" column="detail_id"/>
<result property="address" column="address"/>
<result property="phone" column="phone"/>
</association>
</resultMap>
<select id="selectUserWithDetail" resultMap="userWithDetail">
SELECT u.user_id, u.user_name, d.id AS detail_id, d.address, d.phone
FROM users u LEFT JOIN user_detail d ON u.user_id = d.user_id
WHERE u.user_id = #{id}
</select>
6.2 一对多关联
一个用户有多条订单记录。User 中添加 private List<Order> orders;。
使用 <collection> 标签:
<resultMap id="userWithOrders" type="User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<collection property="orders" ofType="Order">
<id property="orderId" column="order_id"/>
<result property="orderName" column="order_name"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrders">
SELECT u.user_id, u.user_name, o.order_id, o.order_name
FROM users u LEFT JOIN orders o ON u.user_id = o.user_id
WHERE u.user_id = #{id}
</select>
注意避免 N+1 查询问题:尽量使用一条 SQL 联表查询所有数据,而不是先查用户再循环查订单。
7. 常用技巧与最佳实践
- 日志输出:在
mybatis-config.xml中配置日志实现,方便调试 SQL。 - 字段映射驼峰命名:开启
map-underscore-to-camel-case,数据库user_name自动映射到username。 - 使用
@Param传递多参数:接口方法有多个普通参数时,用@Param指定名称,以在 XML 中使用#{paramName}。 - 批量操作:利用
foreach进行批量插入或更新,合理设置事务。 - XML 存放路径:通常将 Mapper 文件放在
resources/mapper/下,与接口类包名对应。 - 避免 SQL 注入:始终使用
#{}语法,除非需要动态表名或列名等无法预编译的场景,才谨慎使用${}。
8. 总结
MyBatis 通过清晰的 SQL 映射和强大的动态 SQL,让你在灵活性和效率之间取得完美平衡。它不隐藏 SQL,反而让你专注于 SQL 优化,同时通过自动化映射极大减少重复劳动。掌握 MyBatis,你将拥有构建高性能数据访问层的有力工具。
开始你的第一个 MyBatis 项目吧,从编写一个干净的 Mapper XML 和动态查询开始!