04-MyBatis 与动态 SQL
对应原始资料:
数据库阶段_day07~08、映射文件&动态SQL.md
一、MyBatis 概述
1. 是什么
MyBatis 是一款优秀的半自动化 ORM 持久层框架。
- JDBC:繁琐,需要手写参数设置、结果集映射、关资源。
- MyBatis:把 SQL 写在 XML/注解里,参数和结果集自动映射,开发者专注 SQL。
2. ORM 思想
对象 关系 映射:表 ↔ 类,字段 ↔ 属性,记录 ↔ 对象。
- MyBatis 是半自动(手写 SQL),Hibernate/JPA 是全自动(自动生成 SQL)。
二、快速入门
1. 依赖
xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>2. 核心配置 mybatis-config.xml
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="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///db1"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/itheima/mapper/UserMapper.xml"/>
</mappers>
</configuration>3. Mapper 接口 + XML
java
public interface UserMapper {
List<User> findAll();
User findById(Integer id);
int insert(User u);
}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.itheima.mapper.UserMapper">
<select id="findAll" resultType="com.itheima.entity.User">
SELECT * FROM user
</select>
<select id="findById" parameterType="int" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insert" parameterType="User">
INSERT INTO user(name, age) VALUES (#{name}, #{age})
</insert>
</mapper>4. 执行
java
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession sqlSession = factory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findAll();
// 增删改需手动提交(除非 openSession(true))
sqlSession.commit();
}三、核心配置项
<properties>:引入 properties 文件。<settings>:如mapUnderscoreToCamelCase(下划线转驼峰)、cacheEnabled。<typeAliases>:给类起别名(User代替全限定名)。<typeHandlers>:类型转换器。<environments>:数据源、事务管理。<mappers>:注册 Mapper。
四、Mapper 代理开发规范(推荐)
- Mapper 接口全限定名 == XML 的
namespace。 - 方法名 == SQL 的
id。 - 参数类型 ==
parameterType。 - 返回类型 ==
resultType。 满足后即可用sqlSession.getMapper(XxxMapper.class)拿到代理对象。
五、输入/输出映射
1. 参数
- 简单类型:
#{id}。 - 对象:
#{name}#{age}。 - 多参数:用
@Param("name")或对象封装,SQL 中#{name}。 - Map:
#{key}。
2. #{} vs ${}
#{}预编译(PreparedStatement,?占位),防注入,绝大多数情况用它。${}字符串拼接,有注入风险,仅用于动态表名/列名(如排序字段)。
3. resultMap(解决字段名与属性名不一致)
xml
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM user
</select>六、动态 SQL(重点)
1. <if>
xml
<select id="search" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
</where>
</select>
<where>自动去掉首个多余的 AND/OR,并在有条件时加 WHERE。
2. <foreach> —— 批量
xml
<select id="findByIds" resultType="User">
SELECT * FROM user
<where>
<foreach collection="ids" item="id" open="id IN (" separator="," close=")">
#{id}
</foreach>
</where>
</select>
<insert id="batchInsert">
INSERT INTO user(name, age) VALUES
<foreach collection="list" item="u" separator=",">
(#{u.name}, #{u.age})
</foreach>
</insert>3. <choose>/<when>/<otherwise>
类似 switch-case。
xml
<choose>
<when test="type == 'name'"> AND name = #{kw} </when>
<when test="type == 'age'"> AND age = #{kw} </when>
<otherwise> AND 1 = 1 </otherwise>
</choose>4. <set> —— 动态更新
xml
<update>
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</set>
WHERE id = #{id}
</update>自动去掉末尾多余的逗号。
5. <sql> 抽取片段
xml
<sql id="userCols">id, name, age, email</sql>
<select>SELECT <include refid="userCols"/> FROM user</select>七、多表查询
1. 一对一(resultMap + association)
xml
<resultMap id="orderUser" type="Order">
<id property="id" column="id"/>
<result property="no" column="no"/>
<association property="user" javaType="User">
<id property="id" column="uid"/>
<result property="name" column="uname"/>
</association>
</resultMap>2. 一对多(collection)
xml
<resultMap id="deptWithEmps" type="Dept">
<id property="id" column="id"/>
<result property="name" column="dname"/>
<collection property="emps" ofType="Emp">
<id property="id" column="eid"/>
<result property="name" column="ename"/>
</collection>
</resultMap>3. 多对多
本质是两个一对多,中间表用 collection 处理。
八、注解开发
简单的 CRUD 可用注解,无需 XML:
java
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Integer id);
@Insert("INSERT INTO user(name, age) VALUES (#{name}, #{age})")
int insert(User u);
@Update("UPDATE user SET name = #{name} WHERE id = #{id}")
int update(User u);
@Delete("DELETE FROM user WHERE id = #{id}")
int delete(Integer id);
// 动态 SQL 仍需用 @SelectProvider 或 SQL 构造器
}取舍:简单 SQL 用注解方便;复杂 SQL/动态 SQL 用 XML 更清晰。
九、MyBatis 高级特性(预告)
| 特性 | 说明 |
|---|---|
| 一级缓存 | SqlSession 级别,默认开启 |
| 二级缓存 | Mapper 级别,<cache/> 开启,跨 session 共享 |
| 延迟加载 | association/collection 按需查询,提升性能 |
| 插件 | 分页(PageHelper)、SQL 打印、逻辑删除 |
这些内容在 SpringBoot 阶段会结合实战讲解;进阶的 MyBatis-Plus 见中间件模块。
练习建议
- 用 MyBatis 完成用户表 CRUD(XML 版)。
- 写一个多条件查询(
<where>+<if>)。 - 用
<foreach>实现IN查询和批量插入。 - 实现「部门-员工」一对多查询(collection)。
- 完成资料中的动态 SQL、多表查询、注解开发练习。