第二章:MyBatis 映射器深入 第二章:MyBatis 映射器深入 MyBatis 的核心在于映射器 (Mapper),它负责将 Java 方法调用映射到 SQL 语句,并将 SQL 执行结果映射回 Java 对象。 本章将深入探讨 MyBatis 映射器的各个方面,包括其定义、配置、动态 SQL、缓存以及最佳实践。 2.1 映射器的定义与配置 映射器本质上是一个 XML 文件(或接口),其中定义了 SQL 语句,并将其与 Java 方法关联起来。 2.1.1 XML 映射器 XML 映射器是 MyBatis 中最常用的方式。它包含以下几个关键元素: : 根元素,指定映射器的命名空间 (namespace)。命名空间通常是接口的完整限定名,方便 MyBatis 找到对应的映射器。
MyBatis 的核心在于映射器 (Mapper),它负责将 Java 方法调用映射到 SQL 语句,并将 SQL 执行结果映射回 Java 对象。 本章将深入探讨 MyBatis 映射器的各个方面,包括其定义、配置、动态 SQL、缓存以及最佳实践。
映射器本质上是一个 XML 文件(或接口),其中定义了 SQL 语句,并将其与 Java 方法关联起来。
2.1.1 XML 映射器
XML 映射器是 MyBatis 中最常用的方式。它包含以下几个关键元素:
<mapper>: 根元素,指定映射器的命名空间 (namespace)。命名空间通常是接口的完整限定名,方便 MyBatis 找到对应的映射器。
<select>, <insert>, <update>, <delete>: 分别对应查询、插入、更新和删除操作。 每个元素都包含 id 属性,用于唯一标识该语句,并与接口方法关联。
<sql>: 用于定义可重用的 SQL 代码片段,提高代码复用性。
<parameterMap> (已过时): 用于定义参数映射,在较新版本中已不推荐使用,推荐使用内联参数。
<resultMap>: 用于定义结果集映射,将 SQL 查询结果映射到 Java 对象。
示例 (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.dao.UserMapper"> <select id="getUserById" parameterType="int" resultMap="UserResultMap"> SELECT id, username, email FROM users WHERE id = #{id} </select> <insert id="insertUser" parameterType="com.example.model.User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (username, email) VALUES (#{username}, #{email}) </insert> <update id="updateUser" parameterType="com.example.model.User"> UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id} </update> <delete id="deleteUser" parameterType="int"> DELETE FROM users WHERE id = #{id} </delete> <resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="email" column="email"/> </resultMap> </mapper>
2.1.2 接口映射器
接口映射器允许直接在 Java 接口中使用注解定义 SQL 语句,更加简洁。
示例 (UserMapper.java):
package com.example.dao; import com.example.model.User; import org.apache.ibatis.annotations.*; public interface UserMapper { @Select("SELECT id, username, email FROM users WHERE id = #{id}") @Results(value = { @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "email", column = "email") }) User getUserById(@Param("id") int id); @Insert("INSERT INTO users (username, email) VALUES (#{username}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") int insertUser(User user); @Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}") int updateUser(User user); @Delete("DELETE FROM users WHERE id = #{id}") int deleteUser(int id); }
常用注解:
@Select, @Insert, @Update, @Delete: 用于定义 SQL 语句。
@Param: 用于指定参数名称,在 XML 映射器中可以通过 #{paramName} 引用。
@Results, @Result: 用于定义结果集映射,类似于 XML 映射器中的 <resultMap>.
@Options: 用于配置 MyBatis 的行为,如主键生成策略。
@SelectProvider, @InsertProvider, @UpdateProvider, @DeleteProvider: 用于动态构建SQL。
2.1.3 配置映射器
需要在 MyBatis 的配置文件 (mybatis-config.xml) 中注册映射器,告诉 MyBatis 去哪里找到它们。
<configuration> <environments default="development"> <environment id="development"> <!-- ...数据源配置... --> </environment> </environments> <mappers> <mapper resource="com/example/dao/UserMapper.xml"/> <mapper class="com.example.dao.UserMapper"/> <!-- 使用接口映射器 --> </mappers> </configuration>
resource: 用于指定 XML 映射器的路径。
class: 用于指定接口映射器的完整限定名。
动态 SQL 是 MyBatis 的一个强大特性,允许根据不同的条件动态生成 SQL 语句。这对于构建灵活的查询和更新非常有用。
2.2.1 主要元素
MyBatis 提供了以下几个主要元素来实现动态 SQL:
<if>: 条件判断,只有当条件为真时,才会包含其内部的 SQL 片段。
<choose>, <when>, <otherwise>: 类似于 Java 中的 switch 语句,用于多条件选择。
<trim>, <where>, <set>: 用于处理 SQL 片段的前缀和后缀,以及移除不必要的 AND 或 OR 关键字。
<foreach>: 用于循环迭代集合,生成 IN 子句或批量插入语句。
2.2.2 示例
基于 <if> 的动态查询:
<select id="findUsers" parameterType="com.example.model.User" resultMap="UserResultMap"> SELECT id, username, email FROM users <where> <if test="username != null and username != ''"> username LIKE #{username} </if> <if test="email != null and email != ''"> AND email LIKE #{email} </if> </where> </select>
基于 <choose>, <when>, <otherwise> 的多条件选择:
<select id="findUsersByCondition" parameterType="com.example.model.User" resultMap="UserResultMap"> SELECT id, username, email FROM users <where> <choose> <when test="username != null and username != ''"> username LIKE #{username} </when> <when test="email != null and email != ''"> email LIKE #{email} </when> <otherwise> 1 = 1 <!-- 如果没有条件,则返回所有用户 --> </otherwise> </choose> </where> </select>
使用 <foreach> 批量删除:
<delete id="deleteUsersByIds" parameterType="java.util.List"> DELETE FROM users WHERE id IN <foreach item="id" collection="list" open="(" separator="," close=")"> #{id} </foreach> </delete>
2.2.3 使用 <trim>, <where>, <set>
<where>:自动处理 WHERE 关键字以及移除多余的 AND 或 OR 前缀。
<set>:用于 UPDATE 语句,自动处理 SET 关键字以及移除最后一个逗号。
<trim>:提供更加灵活的前缀、后缀处理能力。
<update id="updateUserSelective" parameterType="com.example.model.User"> UPDATE users <set> <if test="username != null">username = #{username},</if> <if test="email != null">email = #{email},</if> </set> WHERE id = #{id} </update>
这段代码使用<set>标签,它会自动添加SET关键字,并且移除最后一个逗号。
ResultMap 是 MyBatis 中用于定义结果集映射的关键元素,它将 SQL 查询结果的列映射到 Java 对象的属性。
2.3.1 基本用法
<resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="email" column="email"/> </resultMap>
id: 用于指定结果集映射的唯一标识符。
type: 指定映射的 Java 对象的类型。
<id>: 用于映射主键列。
<result>: 用于映射普通列。
property: Java 对象的属性名。
column: SQL 查询结果的列名。
2.3.2 高级映射
ResultMap 还支持更高级的映射,如:
关联 (Association): 用于映射一对一关系。
集合 (Collection): 用于映射一对多关系。
鉴别器 (Discriminator): 用于根据不同的条件选择不同的结果集映射。
示例 (关联映射):
假设 User 对象有一个 Address 属性,表示用户的地址。
public class User { private int id; private String username; private String email; private Address address; // 关联的 Address 对象 // ... getter and setter ... } public class Address { private int id; private String street; private String city; // ... getter and setter ... }
<resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <result property="email" column="email"/> <association property="address" javaType="com.example.model.Address"> <id property="id" column="address_id"/> <result property="street" column="street"/> <result property="city" column="city"/> </association> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserResultMap"> SELECT u.id AS user_id, u.username, u.email, a.id AS address_id, a.street, a.city FROM users u LEFT JOIN addresses a ON u.address_id = a.id WHERE u.id = #{id} </select>
示例 (集合映射):
假设 User 对象有一个 List<Order> 属性,表示用户的订单列表。
public class User { private int id; private String username; private String email; private List<Order> orders; // 关联的 Order 列表 // ... getter and setter ... } public class Order { private int id; private String orderNumber; private double amount; // ... getter and setter ... }
<resultMap id="UserResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <result property="email" column="email"/> <collection property="orders" ofType="com.example.model.Order"> <id property="id" column="order_id"/> <result property="orderNumber" column="order_number"/> <result property="amount" column="amount"/> </collection> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserResultMap"> SELECT u.id AS user_id, u.username, u.email, o.id AS order_id, o.order_number, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id = #{id} </select>
示例 (鉴别器):
假设 Animal 有不同的子类 Dog 和 Cat,根据 type 字段选择不同的映射。
public abstract class Animal { private int id; private String name; private String type; // Getters and setters } public class Dog extends Animal { private String breed; // Getters and setters } public class Cat extends Animal { private String color; // Getters and setters }
<resultMap id="AnimalResultMap" type="Animal"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="type" column="type"/> <discriminator column="type" javaType="string"> <case value="dog" resultMap="DogResultMap"/> <case value="cat" resultMap="CatResultMap"/> </discriminator> </resultMap> <resultMap id="DogResultMap" type="Dog" extends="AnimalResultMap"> <result property="breed" column="breed"/> </resultMap> <resultMap id="CatResultMap" type="Cat" extends="AnimalResultMap"> <result property="color" column="color"/> </resultMap> <select id="getAnimal" resultMap="AnimalResultMap" parameterType="int"> SELECT a.id, a.name, a.type, d.breed, c.color FROM animals a LEFT JOIN dogs d ON a.id = d.animal_id LEFT JOIN cats c ON a.id = c.animal_id WHERE a.id = #{id} </select>
MyBatis 提供了两级缓存:
一级缓存 (本地缓存): SqlSession 级别的缓存,默认开启。当在同一个 SqlSession 中执行相同的查询时,MyBatis 会从缓存中获取结果,避免重复查询数据库。
二级缓存 (全局缓存): SqlSessionFactory 级别的缓存,需要手动配置。多个 SqlSession 可以共享二级缓存,提高缓存命中率。
2.4.1 配置二级缓存
在 mybatis-config.xml 中开启二级缓存。
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在映射器 XML 文件中配置 <cache> 元素。
<mapper namespace="com.example.dao.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- ...SQL 语句... --> </mapper>
eviction: 缓存回收策略,常用的有 LRU (最近最少使用), FIFO (先进先出), SOFT (软引用), WEAK (弱引用)。
flushInterval: 缓存刷新间隔,单位为毫秒。
size: 缓存大小,最多可以存储的对象数量。
readOnly: 是否只读,如果设置为 true,则返回的对象是共享的,不能修改;如果设置为 false,则返回的对象是拷贝的,可以修改。
确保需要缓存的对象实现了 Serializable 接口。
2.4.2 缓存刷新
可以在 <select> 元素中设置 useCache="false" 禁用缓存。
可以在 <insert>, <update>, <delete> 元素中设置 flushCache="true",在执行完这些操作后刷新缓存。
清晰的命名空间: 使用接口的完整限定名作为命名空间,方便管理和维护。
合理的 ResultMap 设计: 根据实际需求设计 ResultMap,避免过度映射或遗漏映射。
谨慎使用动态 SQL: 虽然动态 SQL 很强大,但过度使用会增加代码复杂度,应尽量使用参数化查询代替。
合理配置缓存: 根据业务场景选择合适的缓存策略和大小,避免缓存雪崩或缓存穿透。
使用参数化查询: 使用 #{} 代替 ${},防止 SQL 注入。
保持 SQL 语句简洁: 尽量将复杂的逻辑放在 Java 代码中处理,避免 SQL 语句过于复杂。
本章深入探讨了 MyBatis 映射器的各个方面,包括其定义、配置、动态 SQL、结果集映射和缓存。 掌握这些知识对于使用 MyBatis 构建高效、可维护的应用程序至关重要。 通过合理的设计和配置映射器,可以充分发挥 MyBatis 的优势,提高开发效率和应用程序性能。