个人技术分享


  我们学习了MyBatis框架的基础知识,搭建基于MyBatis框架的开发环境并初步掌握了其他使用方法,同时对MyBatis框架的核心对象和核心配置文件有了一定的了解。(如果没有了解可以去我主页看看第一章 初识MyBatis框架(2023版本IEDA)来学习)。本章继续学习MyBatis框架基本要素中的SQL映射文件,实现增、删、改及更复杂的查询操作。

2.1 SQL映射文件

  在MyBatis框架中,SQL映射文件(通常是以.xml为扩展名的文件)是定义SQL语句与Java对象之间映射关系的关键部分。这些文件允许你编写SQL语句,并指定如何将结果集映射到Java对象

  • SQL映射文件中的几个顶级元素介绍如下:
    • mapper: SQL映射文件的根元素。只有一个属性namespace,用于区分不同的mapper,必须全局唯一。

    • cache: 为给定命名空间配置缓存。

    • cache-ref: 引用其他命名空间中的缓存配置。

    • resultMap: 用来描述查询结果集中的字段和Java实体类属性的对应关系。

    • sql: 定义可重用的SQL语句块,可以在其他语句映射中引用,提高编写和维护SQL语句的效率。

    • insert: 映射insert语句。

    • update: 映射update语句。

    • delete: 映射delete语句。

    • select: 映射select语句。

注意
  MyBatis框架支持面向接口的SQL映射编程,这种情况下,SQL映射文 件的开发需要注意以下规则。
(1)习惯上,SQL映射文件与该Mapper接口同名(实体类名+Mapper),并放置在同一包路径下。
(2)以要映射的Mapper接口的完全限定名(即包含包名的完整名称)作为namespace属性的值。
(3)接口中的方法名与映射文件中SQL语句映射的 ID 一 一对应。MyBatis框架通过namespace+ID确定和接口方法绑定的SQL语句。
(4)在不同的SQL映射文件中,子元素的ID可以相同。

2.2MyBatis框架的条件查询

  实际项目中的查询操作通常伴随着各种条件,那么在MyBatis框架下如何为SQL语句中的查询条件赋值呢?

2.2.1实现单一条件查询

  在MyBatis中实现单一条件查询通常涉及编写一个Mapper接口方法、对应的SQL映射文件(XML)中的<select>标签以及Java实体类。下面是一个简单的示例,说明如何实现单一条件查询

1. Java实体类(User.java)
首先,你需要一个Java实体类来映射数据库中的表。例如:对于一个用户表(user),你可能有一个User类:

package com.example.domain;  
  
public class User {  
    private int id;  
    private String name;  
    private String email;  
    // ... getter, setter, toString等方法  
}

2. Mapper接口(UserMapper.java)
接下来,你需要创建一个Mapper接口来定义查询方法。例如,你可以创建一个UserMapper接口,并定义一个根据用户名查询用户的方法:

package com.example.mapper;  
  
import com.example.domain.User;  
  
import java.util.List;  
  
public interface UserMapper {  
    // 根据用户名查询用户  
    User selectUserByName(String name);  
    // ... 其他方法  
}

3. SQL映射文件(UserMapper.xml)
然后,你需要创建一个与Mapper接口相对应的SQL映射文件。在这个文件中,你将编写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.mapper.UserMapper">  
  
    <!-- 根据用户名查询用户 -->  
    <select id="selectUserByName" parameterType="string" resultType="com.example.domain.User">  
        SELECT * FROM users WHERE name = #{name}  
    </select>  
  
    <!-- ... 其他SQL语句 -->  
  
</mapper>

在上面的<select>标签中,id属性与Mapper接口中的方法名相对应,parameterType指定了传递给SQL语句的参数类型(这里是字符串),而resultType则指定了查询结果的映射类型(这里是User类的完全限定名)

4. 配置MyBatis
最后,你需要在MyBatis的配置文件(如mybatis-config.xml)中指定SQL映射文件的位置,或者如果你使用的是Spring框架,你可以在Spring的配置文件中进行配置。以下是在MyBatis配置文件中指定映射文件的一个示例:

<configuration>  
    <!-- ... 其他配置 ... -->  
  
    <mappers>  
        <mapper resource="com/example/mapper/UserMapper.xml"/>  
        <!-- ... 其他映射文件 ... -->  
    </mappers>  
  
    <!-- ... 其他配置 ... -->  
</configuration>

MyBatis框架内建的部分别名与Java数据类型的映射关系

别名 映射的类型 别名 映射的类型
boolean Boolean string String
byte Byte bigdecimal或decimal BigDecimal
long Long date Date
short Short map Map
int或integer Integer hashmap HashMap
double Double list List
float Float arraylist ArrayList

注意
  有关MyBatis框架内建别名的更多内容,可以参考MyBatis框架用户手册的“3.1.3 typeAliases”一节。

2.2.2实现多条件查询

  在实际应用中,数据查询经常会综合多种条件,对于多条件查询,MyBatis框架提供了多种方法实现条件赋值。它允许你直接使用 XML 文件或注解来配置和映射原生 SQL、存储过程以及高级映射。下面是如何使用 MyBatis 实现多条件查询的一个示例:
  首先,假设你有一个 User 表,并且你想根据用户名(username)、邮箱(email)和年龄(age)等字段进行查询

  1. 定义 Mapper 接口
    在你的 Mapper 接口中,你可以定义一个方法来执行多条件查询。
public interface UserMapper {  
    List<User> selectUsersByConditions(@Param("username") String username,  
                                       @Param("email") String email,  
                                       @Param("age") Integer age);  
}

注意这里使用了 @Param 注解来指定参数名,这样你就可以在 XML 映射文件中引用这些参数了

  1. 编写 XML 映射文件
    在 MyBatis 的 XML 映射文件中,你可以使用动态 SQL(<if> 标签)来实现多条件查询
<mapper namespace="com.example.mapper.UserMapper">  
  
    <select id="selectUsersByConditions" resultType="com.example.domain.User">  
        SELECT * FROM user  
        WHERE 1 = 1  
        <if test="username != null and username != ''">  
            AND username = #{username}  
        </if>  
        <if test="email != null and email != ''">  
            AND email = #{email}  
        </if>  
        <if test="age != null">  
            AND age = #{age}  
        </if>  
    </select>  
  
</mapper>

注意这里使用了 WHERE 1 = 1 作为条件查询的起始点,这样即使所有的 <if> 条件都不满足,查询也不会因为缺少 WHERE 子句而报错

  1. 在Service 或 Controller 中调用 Mapper 方法
    现在你可以在你的 Service 或 Controller 中调用 UserMapper 的 selectUsersByConditions 方法来执行多条件查询了
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public List<User> getUsersByConditions(String username, String email, Integer age) {  
        return userMapper.selectUsersByConditions(username, email, age);  
    }  
}

  然后你就可以在你的 Controller 或其他需要的地方调用 UserService 的 getUsersByConditions 方法了。
  这样,你就可以使用 MyBatis 实现多条件查询了。根据具体需求,你可以进一步扩展和优化这个查询

1.将查询条件封装成Java对象作为入参

  在MyBatis中,你可以将查询条件封装成一个Java对象,然后将其作为Mapper方法的参数。这样做的好处是,你可以更方便地管理多个查询条件,并且可以让代码更加清晰和易于维护
  我来举一个例子:

  1. 定义查询条件对象:
    首先,定义一个包含查询条件的Java对象(DTO,Data Transfer Object)
public class UserQueryDTO {  
    private String username;  
    private String email;  
    private Integer age;  
  
    // Getters方法和setters方法
    public String getUsername() {  
        return username;  
    }  
  
    public void setUsername(String username) {  
        this.username = username;  
    }  
  
    public String getEmail() {  
        return email;  
    }  
  
    public void setEmail(String email) {  
        this.email = email;  
    }  
  
    public Integer getAge() {  
        return age;  
    }  
  
    public void setAge(Integer age) {  
        this.age = age;  
    }  
}
  1. 修改Mapper接口
    在Mapper接口中,将方法的参数更改为你的查询条件对象
public interface UserMapper {  
    List<User> selectUsersByConditions(UserQueryDTO queryDTO);  
}
  1. 编写XML映射文件
    在MyBatis的XML映射文件中,你可以像之前一样使用动态SQL,但是这次你将从queryDTO对象中引用参数
<mapper namespace="com.example.mapper.UserMapper">  
  
    <select id="selectUsersByConditions" resultType="com.example.domain.User">  
        SELECT * FROM user  
        <where>  
            <if test="queryDTO.username != null and queryDTO.username != ''">  
                AND username = #{queryDTO.username}  
            </if>  
            <if test="queryDTO.email != null and queryDTO.email != ''">  
                AND email = #{queryDTO.email}  
            </if>  
            <if test="queryDTO.age != null">  
                AND age = #{queryDTO.age}  
            </if>  
        </where>  
    </select>  
  
</mapper>

注意这里使用了<where>元素,它可以智能地处理开头的AND或OR,使得在没有任何条件被添加时不会包含额外的WHERE子句

  1. 在Service或Controller中调用Mapper方法
    现在,你可以在你的Service或Controller中创建一个UserQueryDTO对象,设置查询条件,然后调用Mapper方法
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public List<User> getUsersByConditions(UserQueryDTO queryDTO) {  
        return userMapper.selectUsersByConditions(queryDTO);  
    }  
}

现在,你的代码更加清晰,并且查询条件被封装在一个单独的Java对象中,这使得它们更容易被管理和使用

2.将查询条件封装成Map对象作为入参

  在MyBatis中,除了将查询条件封装成Java对象(DTO)作为入参外,你还可以选择使用Map对象来传递查询条件。这样做在某些场景下可能更加灵活,尤其是当你不需要为每一个查询条件都创建一个DTO类。

以下使用Map对象作为查询条件入参:

  1. 在Mapper接口中定义方法
    Mapper接口的方法现在将接收一个Map<String, Object>类型的参数
public interface UserMapper {  
    List<User> selectUsersByConditions(Map<String, Object> params);  
}
  1. 编写XML映射文件
    在XML映射文件中,你可以直接通过#{paramName}的形式来引用Map中的参数。
<mapper namespace="com.example.mapper.UserMapper">  
  
    <select id="selectUsersByConditions" resultType="com.example.domain.User">  
        SELECT * FROM user  
        <where>  
            <if test="params.username != null and params.username != ''">  
                AND username = #{params.username}  
            </if>  
            <if test="params.email != null and params.email != ''">  
                AND email = #{params.email}  
            </if>  
            <if test="params.age != null">  
                AND age = #{params.age}  
            </if>  
        </where>  
    </select>  
  
</mapper>
  1. 在Service或Controller中调用Mapper方法
    现在,在你的Service或Controller中,你可以创建一个Map对象,设置查询条件,然后调用Mapper方法
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public List<User> getUsersByConditions(String username, String email, Integer age) {  
        Map<String, Object> params = new HashMap<>();  
        params.put("username", username);  
        params.put("email", email);  
        params.put("age", age);  
        return userMapper.selectUsersByConditions(params);  
    }  
}

  使用Map作为查询条件的入参可以为你提供更大的灵活性,因为你不需要为每个查询都创建一个新的DTO类。但是,它也可能导致代码的可读性降低,因为你需要通过字符串键来访问参数。在决定是否使用Map作为参数时,请根据你的具体需求和项目风格进行权衡

3.使用@Param注解实现多参数入参

  在MyBatis中,如果你需要在Mapper的接口方法中使用多个参数,但又不希望将它们封装到一个DTO对象中,你可以使用@Param注解来标记每个参数,并在XML映射文件中通过指定的名称来引用它们

  1. 在Mapper接口中定义方法
    在Mapper接口的方法中,使用@Param注解为每个参数命名
public interface UserMapper {  
    List<User> selectUsersByConditions(@Param("username") String username, @Param("email") String email, @Param("age") Integer age);  
}
  1. 编写XML映射文件
    在XML映射文件中,你可以直接使用@Param注解中指定的名称来引用参数
<mapper namespace="com.example.mapper.UserMapper">  
  
    <select id="selectUsersByConditions" resultType="com.example.domain.User">  
        SELECT * FROM user  
        <where>  
            <if test="username != null and username != ''">  
                AND username = #{username}  
            </if>  
            <if test="email != null and email != ''">  
                AND email = #{email}  
            </if>  
            <if test="age != null">  
                AND age = #{age}  
            </if>  
        </where>  
    </select>  
  
</mapper>

注意,在XML映射文件中,我们不再需要添加params.前缀来引用参数,因为我们已经通过@Param注解为它们指定了名称

  1. 在Service或Controller中调用Mapper方法
    现在,在你的Service或Controller中,你可以直接传递多个参数给Mapper方法
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public List<User> getUsersByConditions(String username, String email, Integer age) {  
        return userMapper.selectUsersByConditions(username, email, age);  
    }  
}

使用@Param注解可以清晰地表明每个参数在Mapper方法中的用途,并且在XML映射文件中也更容易引用它们。这种方式适用于参数数量较少,且不希望创建DTO对象的场景。当参数数量较多或者参数之间存在复杂的逻辑关系时,通常推荐将参数封装到一个DTO对象中

2.3MyBatis框架的结果映射

  MyBatis框架中的结果映射(Result Mapping)是一个强大的功能,它允许你定制如何从数据库结果集中加载对象。当你查询数据库并期望返回的结果与你的Java对象不完全匹配时,结果映射就显得尤为重要。

  MyBatis提供了多种结果映射的方式,包括使用<resultMap>标签在XML映射文件中进行显式映射,或者使用注解在Mapper接口中进行映射

2.3.1使用resultMap元素自定义结果映射

  在MyBatis中,<resultMap>元素用于自定义结果映射,它允许你精确地控制如何从数据库结果集(ResultSet)中加载数据到Java对象。<resultMap>可以处理复杂的映射关系,包括关联(association)和集合(collection)

  以下是一个使用<resultMap>元素自定义结果映射的示例:

  1. 定义Java对象
      首先,你需要定义你的Java对象,包括实体类(Entity)和任何可能涉及的DTO(Data Transfer Object)或VO(View Object)等
public class User {  
    private Integer id;  
    private String name;  
    private List<Order> orders; // 假设一个用户有多个订单  
    // getters and setters  
}  
  
public class Order {  
    private Integer id;  
    private Integer userId; // 外键,关联用户ID  
    private String productName;  
    // getters and setters  
}
  1. 编写XML映射文件
    在MyBatis的XML映射文件中,你可以使用<resultMap>元素来定义如何从数据库结果集中加载User和Order对象
<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 订单结果映射 -->  
    <resultMap id="orderResultMap" type="com.example.domain.Order">  
        <id property="id" column="order_id"/>  
        <result property="userId" column="user_id"/>  
        <result property="productName" column="product_name"/>  
    </resultMap>  
  
    <!-- 用户结果映射,包含订单集合 -->  
    <resultMap id="userResultMap" type="com.example.domain.User">  
        <id property="id" column="user_id"/>  
        <result property="name" column="user_name"/>  
        <!-- 关联订单集合 -->  
        <collection property="orders" column="user_id" foreignColumn="user_id" ofType="com.example.domain.Order" resultMap="orderResultMap"/>  
    </resultMap>  
  
    <!-- 查询用户及其订单 -->  
    <select id="selectUserWithOrders" resultMap="userResultMap">  
        SELECT u.user_id, u.user_name, o.order_id, o.user_id as order_user_id, o.product_name  
        FROM users u  
        LEFT JOIN orders o ON u.user_id = o.user_id  
        WHERE u.user_id = #{userId}  
    </select>  
 
</mapper>

在上面的示例中,我们定义了两个<resultMap>

orderResultMap:用于映射Order对象。
userResultMap:用于映射User对象,并包含一个<collection>元素来映射用户的订单集合。<collection>元素的property属性指定了Java对象中的集合属性名,column属性指定了传递给集合的列名(通常用于延迟加载),foreignColumn属性指定了关联的外键列名(在这个例子中与column相同,但在某些情况下可能不同),ofType属性指定了集合中元素的类型,resultMap属性引用了用于映射集合中元素的<resultMap>

  1. 在Mapper接口中定义方法
    在Mapper接口中,你可以定义一个方法来调用上面定义的SQL查询,并指定使用哪个<resultMap>
public interface UserMapper {  
    User selectUserWithOrders(Integer userId);  
}
  1. 调用Mapper方法
    最后,在你的服务层或控制器中,你可以通过调用Mapper方法来执行查询并获取结果
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public User getUserWithOrders(Integer userId) {  
        return userMapper.selectUserWithOrders(userId);  
    }  
}

现在,当你调用getUserWithOrders方法并传入一个用户ID时,MyBatis将执行相应的SQL查询,并使用你定义的<resultMap>来映射结果集到User对象,包括用户的订单集合

2.3.2嵌套结果映射

  嵌套结果映射(Nested Result Mapping)在MyBatis中是一个强大的功能,它允许你在单个查询中处理复杂的关联关系,并将结果映射到Java对象的嵌套结构中。
以下是关于嵌套结果映射的详细解释和示例:

  1. 嵌套结果映射的概念
      定义:嵌套结果映射是一种在单个查询中处理多个表关联,并将结果映射到具有嵌套关系的Java对象中的技术。
      适用场景:当你需要查询的数据来自多个表,并且这些表之间存在关联关系时,可以使用嵌套结果映射。

  2. 如何使用嵌套结果映射

2.1 定义Java对象

首先,你需要定义与数据库表结构相对应的Java对象,并建立它们之间的关联关系。例如,一个User对象可能包含一个或多个Order对象。

//User实体类
public class User {  
    private Integer id;  
    private String name;  
    private List<Order> orders;  
    // getters方法和setters方法 
}  
  //Order 实体类
public class Order {  
    private Integer id;  
    private Integer userId;  
    private String productName;  
    // getters方法和setters方法  
}
2.2 编写XML映射文件

  在MyBatis的XML映射文件中,你可以使用<resultMap>元素来定义嵌套结果映射。在<resultMap>中,你可以使用<association>元素来处理一对一的关系,使用<collection>元素来处理一对多的关系

<resultMap id="userResultMap" type="User">  
    <id property="id" column="user_id"/>  
    <result property="name" column="user_name"/>  
    <collection property="orders" ofType="Order" foreignColumn="user_id" column="user_id" resultMap="orderResultMap"/>  
</resultMap>  
  
<resultMap id="orderResultMap" type="Order">  
    <id property="id" column="order_id"/>  
    <result property="userId" column="user_id"/>  
    <result property="productName" column="product_name"/>  
</resultMap>

在userResultMap中,<collection>元素定义了User对象中的orders属性,它包含了一个Order对象的列表。
foreignColumn和column属性通常设置为相同的值,以表示关联的外键列。
resultMap属性引用了orderResultMap,用于映射Order对象。
orderResultMap定义了如何映射Order对象

2.3 编写SQL查询

在映射文件中,你还需要编写一个SQL查询来执行实际的数据库操作。这个查询应该包含所有必要的表连接和字段选择,以便能够填充你的Java对象。

<select id="selectUserWithOrders" resultMap="userResultMap">  
    SELECT u.user_id, u.user_name, o.order_id, o.user_id, o.product_name  
    FROM users u  
    LEFT JOIN orders o ON u.user_id = o.user_id  
    WHERE u.user_id = #{userId}  
</select>
  1. 总结
      嵌套结果映射是MyBatis中处理复杂关联关系的一种强大工具。通过定义Java对象、编写XML映射文件和SQL查询,你可以轻松地将数据库结果映射到具有嵌套关系的Java对象中。这不仅可以减少数据库访问次数,提高性能,还可以使你的代码更加清晰和易于维护

1. association元素

  在MyBatis中,<association>元素用于处理一对一(one-to-one)的关联关系。当你有一个对象包含另一个对象作为它的属性时,你可以使用<association>来映射这种关系。以下是如何使用<association>元素进行嵌套结果映射的详细步骤:

  1. 定义Java对象
    首先,定义包含一对一关系的Java对象。例如,一个User对象可能包含一个与之关联的Profile对象
public class User {  
    private Integer id;  
    private String name;  
    private Profile profile; // 一对一关联  
    // getters and setters  
}  
  
public class Profile {  
    private Integer id;  
    private Integer userId; // 关联的外键  
    private String email;  
    // getters and setters  
}
  1. 编写XML映射文件
    在MyBatis的XML映射文件中,你需要定义<resultMap>来处理这个一对一的关系。使用<association>元素来指定User对象中的profile属性如何映射到Profile对象
<resultMap id="userResultMap" type="User">  
    <id property="id" column="user_id"/>  
    <result property="name" column="user_name"/>  
    <association property="profile" javaType="Profile" column="user_id" foreignColumn="userId" resultMap="profileResultMap"/>  
</resultMap>  
  
<resultMap id="profileResultMap" type="Profile">  
    <id property="id" column="profile_id"/>  
    <result property="userId" column="user_id"/>  
    <result property="email" column="email"/>  
</resultMap>

在userResultMap中,<association>元素定义了User对象中的profile属性。
property属性指定了Java对象中的属性名,javaType属性指定了关联对象的类型,column属性指定了传递给关联对象的列名(通常用于延迟加载),
foreignColumn属性指定了关联的外键列名(在这里假设Profile表的userId列是外键)。
resultMap属性引用了另一个<resultMap>,该<resultMap>定义了如何映射Profile对象。
profileResultMap定义了如何映射Profile对象

  1. 编写SQL查询
    接下来,你需要编写一个SQL查询来从数据库中检索数据。这个查询应该包含必要的表连接和字段选择,以便能够填充你的Java对象
<select id="selectUserWithProfile" resultMap="userResultMap">  
    SELECT u.user_id, u.user_name, p.profile_id, p.user_id, p.email  
    FROM users u  
    LEFT JOIN profiles p ON u.user_id = p.user_id  
    WHERE u.user_id = #{userId}  
</select>
  1. 在Mapper接口中定义方法
    最后,在你的Mapper接口中定义一个方法来调用上面定义的SQL查询
public interface UserMapper {  
    User selectUserWithProfile(Integer userId);  
}
  1. 调用Mapper方法
    现在,你可以在你的服务层或控制器中通过调用Mapper方法来获取具有关联Profile的User对象
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public User getUserWithProfile(Integer userId) {  
        return userMapper.selectUserWithProfile(userId);  
    }  
}

注意事项
  确保你的SQL查询中的列名与<resultMap>中定义的列名相匹配。
  如果你的关联关系是延迟加载的(即,你只想在需要时才加载关联对象),你可以使用<association>元素的fetchType属性,并将其设置为lazy(默认值)或eager。但是,请注意,MyBatis的延迟加载是基于Java代理的,因此它可能不适用于所有的用例。
  如果你在XML映射文件中遇到任何错误或警告,确保你已经检查了所有必要的属性和配置,并参考了MyBatis的官方文档以获取更多帮助

association元素的主要的属性如下:
property:实体类中用来映射查询结果子集的属性。
javaType :property指定的属性的数据类型,可以使用Java完全限定类名或别名。
如果property指定的属性是一个javaBean,则MyBatis框架通常能够自行检测出其类型;
如果property指定的是一个HashMap,则应该通过JavaType属性明确指定其数据类型,以确保所需行为。
id。
result。
它们在association中的含义和用法与在resultMap元素中相同,这里不再赘述。

注意
(1)因为结果映射的需要,要确保所有列名都是唯一的。
(2)id子元素在嵌套结果映射中扮演了非常重要的角色。虽然不使用id子元素MyBatis框架也能工作,但是这样会导致严重的性能开销,所以,最好选择尽量少的属性(主键或联合主键均可)来唯一标识结果。

2. collection元素

  collection元素和association元素作用非常类似,只不过表达的是"一对多"关系,即实体类内部嵌套的是一个集合类型的属性。
  在MyBatis中,<collection>元素用于处理一对多(one-to-many)的关联关系。当你有一个对象包含另一个对象的列表作为它的属性时,你会使用<collection>来映射这种关系。
以下是如何使用<collection>元素进行嵌套结果映射的详细步骤:

  1. 定义Java对象
    首先,定义包含一对多关系的Java对象。例如,一个User对象可能包含一个与之关联的Order对象的列表。
public class User {  
    private Integer id;  
    private String name;  
    private List<Order> orders; // 一对多关联  
    // getters方法和setters方法 
}  
  
public class Order {  
    private Integer id;  
    private Integer userId; // 关联的外键  
    private String productName;  
    // getters方法和setters方法  
}
  1. 编写XML映射文件
    在MyBatis的XML映射文件中,你需要定义<resultMap>来处理这个一对多的关系。使用<collection>元素来指定User对象中的orders属性如何映射到Order对象的列表
<resultMap id="userResultMap" type="User">  
    <id property="id" column="user_id"/>  
    <result property="name" column="user_name"/>  
    <collection property="orders" ofType="Order" foreignColumn="user_id" column="user_id" select="selectOrdersByUserId" fetchType="lazy"/>  
</resultMap>  
  
<!-- 注意:这里使用了select属性来指定一个单独的查询来加载Order列表,这是一种常用的延迟加载策略 -->  
<select id="selectOrdersByUserId" resultType="Order">  
    SELECT * FROM orders WHERE user_id = #{userId}  
</select>  
  
<!-- 如果你想要在一个查询中完成所有的加载(即立即加载),你可以使用join来代替单独的查询 -->  
<select id="selectUserWithOrders" resultMap="userResultMap">  
    SELECT u.user_id, u.user_name, o.order_id, o.user_id, o.product_name  
    FROM users u  
    LEFT JOIN orders o ON u.user_id = o.user_id  
    WHERE u.user_id = #{userId}  
</select>

在userResultMap中,<collection>元素定义了User对象中的orders属性。
property属性指定了Java对象中的属性名,ofType属性指定了集合中元素的类型。
foreignColumn和column属性通常用于指定关联的外键列,但在使用select属性进行延迟加载时,它们通常不需要。
select属性指定了一个单独的查询来加载关联对象列表,而fetchType属性(可选)指定了加载策略(默认为lazy,即延迟加载)

  1. 编写Mapper接口
    在你的Mapper接口中,你需要定义方法来调用上面定义的SQL查询。
public interface UserMapper {  
    // 立即加载User及其Order列表  
    User selectUserWithOrders(Integer userId);  
      
    // 延迟加载Order列表(当访问User的orders属性时触发)  
    List<Order> selectOrdersByUserId(Integer userId);  
}
  1. 调用Mapper方法
    现在,你可以在你的服务层或控制器中通过调用Mapper方法来获取具有关联Order列表的User对象
@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public User getUserWithOrders(Integer userId) {  
        return userMapper.selectUserWithOrders(userId);  
    }  
}

注意事项
  使用<collection>的select属性进行延迟加载时,MyBatis会为每个User对象执行一个单独的查询来获取其Order列表。这可能会导致N+1查询问题,特别是在处理大量数据时。你可以通过配置缓存或使用JOIN查询来优化性能。
  如果你选择使用JOIN查询来立即加载所有关联数据,请确保你的SQL查询和<resultMap>配置能够正确映射结果集。
确保你的SQL查询中的列名与<resultMap>中定义的列名相匹配

collection元素有如下常用属性。
  property:实体类中用来映射查询结果子集的集合属性。
  ofType:property指定的集合属性中的元素的数据类型,可以使用Java完全限定类名或别名。
collection元素的子元素与association元素及resultMap元素的基本一致,不再赘述。

我们来说说association元素和collection元素的区别
  association元素和collection元素在MyBatis中用于处理不同的对象关联关系,它们之间的主要区别如下:

  1. 关联关系类型:
      association元素用于处理一对一(one-to-one) 的关联关系。它表示一个对象拥有另一个对象作为其成员变量,通常用于实现对象之间的继承或聚合关系。
    Java类定义
public class User {  
    private Integer id;  
    private String username;  
    private Profile profile; // 一对一关联  
    // getters方法和setters方法 
}  
  
public class Profile {  
    private Integer id;  
    private String bio;  
    // getters方法和setters方法 
}

Mapper XML

<resultMap id="userResultMap" type="User">  
    <id property="id" column="user_id" />  
    <result property="username" column="username" />  
    <association property="profile" javaType="Profile" column="profile_id" foreignColumn="id" resultMap="profileResultMap" />  
</resultMap>  
  
<resultMap id="profileResultMap" type="Profile">  
    <id property="id" column="profile_id" />  
    <result property="bio" column="bio" />  
</resultMap>  
  
<select id="selectUserWithProfile" resultMap="userResultMap">  
    SELECT u.id as user_id, u.username, p.id as profile_id, p.bio  
    FROM user u  
    LEFT JOIN profile p ON u.profile_id = p.id  
    WHERE u.id = #{userId}  
</select>

  collection元素用于处理一对多(one-to-many)或多对多(many-to-many) 的关联关系。它用于表示一个对象包含多个子对象或关联对象的集合。

Java类定义

public class User {  
    private Integer id;  
    private String username;  
    private List<Order> orders; // 一对多关联  
    // getters方法和setters方法
}  
  
public class Order {  
    private Integer id;  
    private Integer userId;  
    private String orderNumber;  
    // getters方法和setters方法 
}

Mapper XML

<resultMap id="userOrdersResultMap" type="User">  
    <id property="id" column="user_id" />  
    <result property="username" column="username" />  
    <collection property="orders" ofType="Order" foreignColumn="user_id" column="user_id" resultMap="orderResultMap" />  
</resultMap>  
  
<resultMap id="orderResultMap" type="Order">  
    <id property="id" column="order_id" />  
    <result property="userId" column="user_id" />  
    <result property="orderNumber" column="order_number" />  
</resultMap>  
  
<select id="selectUserWithOrders" resultMap="userOrdersResultMap">  
    SELECT u.id as user_id, u.username, o.id as order_id, o.user_id, o.order_number  
    FROM user u  
    LEFT JOIN order o ON u.id = o.user_id  
    WHERE u.id = #{userId}  
</select>
  1. 目标对象类型:
      association用于表示关联属性,即一个属性对应一个关联对象。
      collection用于表示集合属性,即一个属性对应多个关联对象。
  2. 关联关系处理:
      association通常会在关联对象的查询语句中使用join操作,将关联对象的数据与当前对象的数据一起查询出来。
      collection通常需要执行额外的SQL查询来获取关联对象的数据,通常需要使用select子句指定查询语句。
  3. SQL查询方式:
      association倾向于在单个查询中通过join操作获取所有数据。
      collection可能需要多个查询,特别是当使用select属性指定额外的查询时。
  4. 映射方式:
      association通常在结果映射中使用<result>标签定义,以映射关联对象的属性。
      collection通常在结果映射中使用嵌套的<resultMap>定义,以映射关联对象的集合属性。
  5. 使用场景:
      当一个对象包含另一个单一对象作为属性时,使用association。
      当一个对象包含多个对象作为属性(如列表或集合)时,使用collection。
  6. 配置位置:
      两者通常都在<resultMap>中使用,用于定义结果映射规则。
  7. 性能考虑:
      过度使用collection的select属性可能导致N+1查询问题,影响性能。
      在可能的情况下,使用join查询以减少数据库访问次数,提高性能。

  总结来说,association和collection的主要区别在于它们处理的关联关系类型、目标对象类型、关联关系处理方式、SQL查询方式、映射方式、使用场景和性能考虑。正确选择和使用这两个元素对于优化MyBatis的查询性能和代码可读性至关重要。

2.3.4resultType于resultMap小结

使用select元素映射查询语句时,对于返回结果的定义可以使用resultType属性,也可以使用resultMap属性。那么,二者有何区别呢?

注意
  使用resultType属性或resultMap属性封装查询结果本质上是一样的,均基于Map数据结构。但是,在select元素中使用时需要注意,二者不能同时使用。

resultType
public class User {  
    private Integer id;  
    private String name;  
    private String email;  
    // getters方法和setters方法 
}

对应的Mapper XML文件中的查询可以直接使用resultType:

<select id="findUserById" resultType="com.example.User">  
    SELECT id, name, email FROM user WHERE id = #{id}  
</select>
  1. 使用场景
      适用于字段名和属性名一致的简单查询,当查询结果较少且结构简单时比较方便。
      用于将查询结果映射成简单的POJO(普通Java对象)或基本数据类型(如String、Integer等)。
  2. 优点
      简单快速:自动映射,无需额外配置。
      直观易懂:通过指定返回类型,MyBatis自动将查询结果映射到Java对象的属性上。
  3. 缺点
      灵活性差:当字段名和属性名不一致时,需要额外的配置或使用ResultMap。
      不支持复杂关联:对于一对多、多对一等复杂关联关系,需要使用ResultMap。
public class UserDetail {  
    private User user;  
    // 其他属性和getters方法和setters方法
}  
 
public class User {  
    private Integer id;  
    private String username; // 注意这里属性名和数据库列名不一致  
    // getters方法和setters方法 
}

在Mapper XML文件中,我们需要定义一个resultMap来处理这种不一致的映射关系:

<resultMap id="userResultMap" type="com.example.User">  
    <id property="id" column="user_id"/>  
    <result property="username" column="user_name"/> <!-- 列名与属性名不一致 -->  
</resultMap>  
  
<resultMap id="userDetailResultMap" type="com.example.UserDetail">  
    <association property="user" javaType="com.example.User" resultMap="userResultMap"/>  
</resultMap>  
  
<select id="findUserDetailById" resultMap="userDetailResultMap">  
    SELECT u.id AS user_id, u.name AS user_name FROM user_detail_table u WHERE u.id = #{id}  
</select>
resultMap
  1. 使用场景
      适用于字段名和属性名不一致、一对多映射、多对一映射等复杂情况。
      通过配置映射规则,将查询结果映射到复杂的Java对象中。
  2. 优点
      灵活性强:可以自定义映射规则,支持字段名和属性名不一致的情况。
      支持复杂关联:可以轻松处理一对多、多对一等复杂关联关系。
      清晰明了:通过配置resultMap,可以清晰地看到查询结果与Java对象之间的映射关系。
  3. 缺点
      配置繁琐:相对于resultType,resultMap需要更多的配置工作。
      学习成本高:对于初学者来说,学习和掌握resultMap的配置和使用需要一定的时间和经验。

注意事项
  当字段名和属性名不一致时,可以在MyBatis配置文件中开启驼峰映射下划线的功能(<setting name="mapUnderscoreToCamelCase" value="true"/>),但这只适用于简单的字段名转换。
  如果字段名和属性名之间存在复杂的转换关系,或者需要处理复杂的关联关系,建议使用resultMap进行自定义映射。
  resultType和resultMap都是MyBatis中处理查询结果映射的重要工具,应根据实际需求和场景选择合适的映射方式。

小结
  从数据库表关系的角度来讲,association元素用于映射一对一或多对一关系,而collection元素用于映射一对多关系。

2.3.5resultMap的自动映射行为

  MyBatis的resultMap提供了一种机制,允许开发者自定义如何将数据库查询结果映射到Java对象的属性上。尽管resultMap主要被用于处理复杂的映射场景,如字段名和属性名不一致、关联查询等,但MyBatis也支持在resultMap中启用自动映射功能。

  1. 自动映射的概念
      自动映射是MyBatis的一个特性,当查询结果的列名与Java对象的属性名相同时(不区分大小写),MyBatis会自动将列值设置到相应的属性上,而无需在resultMap中明确指定映射关系。

  2. 启用自动映射
      在resultMap中,可以通过设置autoMapping属性为true来启用自动映射。例如:

<resultMap id="userResultMap" type="com.example.User" autoMapping="true">  
    <!-- 其他手动配置的映射关系 -->  
</resultMap>

可以得出resultMap的自动映射的三种行为
  NONE:禁用自动映射,仅为手工映射的属性赋值。
  PARTIAL:这是默认行为,对于没有嵌套映射的resultMap使用自动映射;而对于有嵌套映射的resultMap不使用自动映射,仅为手工映射的属性赋值。
  FULL:全部使用自动映射,即使有嵌套映射的resultMap也会使用自动映射。

实体类

public class User {  
    private Integer id;  
    private String username;  
    private UserDetail userDetail; 
    // getters方法和setters方法
}   

public class UserDetail {  
    private Integer detailId;  
    private String address;  
    // getters方法和setters方法
}

在 MyBatis 的 Mapper XML 文件中,我们可以定义一个 resultMap 来处理这种关系,同时使用自动映射来处理基本字段的映射:

<resultMap id="userResultMap" type="com.example.User">  
    <!-- 这里的 id 和 username 将会自动映射,因为列名和属性名一致 -->  
      
    <!-- association 用于处理 User 内部的 UserDetail 对象 -->  
    <association property="userDetail" javaType="com.example.UserDetail" resultMap="userDetailResultMap"/>  
</resultMap>  
  
<resultMap id="userDetailResultMap" type="com.example.UserDetail">  
    <!-- detailId 和 address 需要明确指定,因为可能列名和属性名不一致 -->  
    <id property="detailId" column="detail_id"/>  
    <result property="address" column="user_address"/>  
</resultMap>  
  
<select id="selectUserWithDetail" resultMap="userResultMap">  
    SELECT   
        u.id,   
        u.username,   
        ud.id AS detail_id,   
        ud.address AS user_address   
    FROM   
        user u   
    LEFT JOIN   
        user_detail ud ON u.id = ud.user_id   
    WHERE   
        u.id = #{userId}  
</select>

  在上面的 resultMap 定义中,userResultMap 允许自动映射 id 和 username 属性,因为它们与 SQL 查询结果集中的列名相匹配。但是,对于 UserDetail 类型的 userDetail 属性,我们需要一个嵌套的 <association> 元素,并指定一个额外的 resultMap(在这个例子中是 userDetailResultMap)来处理 UserDetail 对象的映射。
  在 userDetailResultMap 中,我们明确指定了 detailId 和 address 属性的映射关系,因为假设列名与属性名不一致(如 detail_id 和 user_address)。
  最后,selectUserWithDetail 查询使用 userResultMap 作为其 resultMap 属性,这样 MyBatis 就知道如何映射查询结果到 User 对象。

  1. 自动映射的行为
      当启用自动映射后,MyBatis会首先检查查询结果的列名是否与Java对象的属性名相同。
      如果列名和属性名相同,MyBatis会自动将该列的值设置到相应的属性上。
      对于那些没有在resultMap中明确指定映射关系的属性,如果它们的名称与查询结果的列名相匹配,也会被自动映射。
      需要注意的是,自动映射只会处理名称匹配的属性,而不会处理那些通过嵌套查询或关联查询得到的复杂属性。
      此外,如果开发者在resultMap中明确指定了某个属性的映射关系(使用<id><result>标签),那么这个属性的映射将按照开发者的配置进行,而不是依赖于自动映射。

  2. 注意事项
      虽然自动映射可以简化配置,但在某些情况下可能会导致意外的行为。
      例如,如果查询结果的列名与Java对象的某个属性名相似但不完全相同(例如,由于大小写或下划线的使用),自动映射可能会错误地将值设置到不相关的属性上。
      因此,在使用自动映射时,开发者需要确保查询结果的列名与Java对象的属性名严格匹配。
      对于复杂的查询结果和映射关系,建议明确指定映射关系,以避免潜在的错误和不确定性。

2.4 MyBatis框架的增、删、改操作

了解了如何在MyBatis框架中执行查询和封装结果后,下面学习如何在MyBatis框架中执行增、删、改操作。

2.4.1 执行insert语句

  在MyBatis中,执行INSERT语句通常是通过在Mapper XML文件中定义一个标签来完成的。
下面来展示了如何在MyBatis中定义和执行一个INSERT语句:

public class User {  
    private Integer id;  
    private String username;  
    private String password;  
    // getters方法和setters方法
}

然后,在Mapper XML文件中,我们可以定义一个标签来插入一个新的User对象到数据库中:

<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 定义插入User的SQL语句 -->  
    <insert id="insertUser" parameterType="com.example.model.User" useGeneratedKeys="true" keyProperty="id">  
        INSERT INTO user (username, password)  
        VALUES (#{username}, #{password})  
    </insert>  
  
</mapper>

在这个<insert>标签中:
  id属性是这个插入语句的唯一标识符,在Mapper接口中会使用这个ID来引用这个SQL语句。
  parameterType属性指定了传入参数的类型,这里是User类的全限定名。
  useGeneratedKeys和keyProperty属性一起使用,用于从数据库返回自动生成的键(通常是主键)。在这个例子中,我们假设数据库会自动为id字段生成一个值。

接下来,在Mapper接口中,我们需要定义一个与XML文件中<insert>标签的id属性相对应的方法:

package com.example.mapper;  
  
import com.example.model.User;  
  
public interface UserMapper {  
    // 插入User的方法  
    int insertUser(User user);  
}

最后,在你的服务层或DAO层中,你可以调用这个Mapper接口的方法来执行插入操作:

@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public void addUser(User user) {  
        int result = userMapper.insertUser(user);  
        if (result > 0) {  
            // 插入成功  
            System.out.println("User inserted successfully.");  
        } else {  
            // 插入失败  
            System.out.println("Failed to insert user.");  
        }  
    }  
}

注意
  上面的代码假设你已经在Spring框架中配置了MyBatis,并且已经设置了Mapper的扫描路径。如果你没有使用Spring,你需要以不同的方式配置MyBatis和Mapper接口。

2.4.2 执行update语句

  在MyBatis中执行UPDATE语句通常与执行INSERT语句类似,也是通过在Mapper XML文件中定义一个<update>标签来完成的。下面是一个简单的示例,展示了如何在MyBatis中定义和执行一个UPDATE语句

public class User {  
    private Integer id;  
    private String username;  
    private String password;  
  
    // getters方法和setters方法
}

在Mapper XML文件中,我们可以定义一个<update>标签来更新User对象:

<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 定义更新User的SQL语句 -->  
    <update id="updateUserPassword" parameterType="map">  
        UPDATE user  
        SET password = #{password}  
        WHERE id = #{id}  
    </update>  
  
</mapper>

在这个<update>标签中:
  id属性是这个更新语句的唯一标识符,在Mapper接口中会使用这个ID来引用这个SQL语句。
  parameterType属性指定了传入参数的类型,这里我们使用了map类型,因为我们只需要传递id和password两个字段,而不是整个User对象。但在实际开发中,你也可以传递整个User对象,并通过#{user.id}和#{user.password}来引用其属性。

接下来,在Mapper接口中,我们需要定义一个与XML文件中<update>标签的id属性相对应的方法:

package com.example.mapper;  
public interface UserMapper {  
    // 更新用户密码的方法  
    int updateUserPassword(Map<String, Object> parameters);  
}

或者,如果你想要传递整个User对象,方法可以定义为:

public interface UserMapper {  
    // 更新用户密码的方法(使用User对象)  
    int updateUserPassword(User user);  
}

并在XML中使用#{user.id}和#{user.password}来引用属性。
最后,在你的服务层或DAO层中,你可以调用这个Mapper接口的方法来执行更新操作:
使用Map作为参数:

@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public void updateUserPassword(int userId, String newPassword) {  
        Map<String, Object> parameters = new HashMap<>();  
        parameters.put("id", userId);  
        parameters.put("password", newPassword);  
        int result = userMapper.updateUserPassword(parameters);  
        if (result > 0) {  
            // 更新成功  
            System.out.println("User password updated successfully.");  
        } else {  
            // 更新失败  
            System.out.println("Failed to update user password.");  
        }  
    }  
}

或者使用User对象作为参数(如果你这样定义了Mapper接口的方法):

@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public void updateUserPassword(User user) {  
        int result = userMapper.updateUserPassword(user);  
        if (result > 0) {  
            // 更新成功  
            System.out.println("User password updated successfully.");  
        } else {  
            // 更新失败  
            System.out.println("Failed to update user password.");  
        }  
    }  
}

2.4.3 执行delete语句

  在MyBatis中执行DELETE语句也是通过在Mapper XML文件中定义一个<delete>标签来完成的。以下是一个简单的示例,展示了如何在MyBatis中定义和执行一个DELETE语句。

首先,在Mapper XML文件中,我们可以定义一个<delete>标签来删除用户:

<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 定义删除User的SQL语句 -->  
    <delete id="deleteUserById" parameterType="int">  
        DELETE FROM user  
        WHERE id = #{id}  
    </delete>  
  
</mapper>

在这个<delete>标签中:
  id属性是这个删除语句的唯一标识符,在Mapper接口中会使用这个ID来引用这个SQL语句。
  parameterType属性指定了传入参数的类型,这里是int类型,因为我们只需要传递用户的ID来作为删除条件。

接下来,在Mapper接口中,我们需要定义一个与XML文件中<delete>标签的id属性相对应的方法:

package com.example.mapper;  
  
public interface UserMapper {  
    // 根据ID删除用户的方法  
    int deleteUserById(int id);  
}

最后,在你的服务层或DAO层中,你可以调用这个Mapper接口的方法来执行删除操作:

@Service  
public class UserService {  
  
    @Autowired  
    private UserMapper userMapper;  
  
    public void deleteUser(int userId) {  
        int result = userMapper.deleteUserById(userId);  
        if (result > 0) {  
            // 删除成功  
            System.out.println("User deleted successfully.");  
        } else {  
            // 删除失败(可能用户不存在)  
            System.out.println("Failed to delete user.");  
        }  
    }  
}

在这个例子中,UserService类中的deleteUser方法接收一个用户ID作为参数,并调用UserMapper接口中的deleteUserById方法来执行删除操作。
根据返回的结果(受影响的行数),可以确定是否成功删除了用户。
如果返回结果大于0,则表示至少删除了一个用户(通常情况下ID是唯一的,所以结果应该是1);
如果返回结果为0,则表示没有找到要删除的用户。

2.5MyBatis框架的缓存

正如大多数持久化框架一样,MyBatis框架也提供了对缓存的支持。

2.5.1 MyBatis框架的缓存分类

MyBatis框架的缓存分为两个级别。

1.一级缓存

  MyBatis框架的一级缓存是基于PerptualCache的HashMap本地缓存,默认是SqlSession级别的缓存,在SqlSession的一个生命周期内有效。当SqlSession关闭后,该SqlSession中所有的一级缓存会被清空。MyBatis框架的一级缓存默认是开启的。

一级缓存
默认情况下,一级缓存是开启的,不需要额外配置
一级缓存是基于SqlSession的缓存,每个SqlSession都有一个一级缓存。这意味着不同的SqlSession之间的缓存数据是不共享的
当一个SqlSession执行查询操作时,MyBatis会首先在一级缓存中查找是否有相应的数据。如果找到,则直接返回缓存中的数据,避免了对数据库的再次查询,从而提高了性能
一级缓存的生命周期与SqlSession相同。当SqlSession关闭或提交事务时,一级缓存中的数据会被清空
如果在同一个SqlSession中执行了增删改操作(INSERT、UPDATE、DELETE),那么一级缓存中的数据会被清空,因为增删改操作可能会改变数据库中的数据,所以必须刷新缓存

一个Mapper接口和一个对应的XML文件,执行两次相同的查询:

// 假设你有一个SqlSession对象 sqlSession  
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);  
  
// 第一次查询  
User user1 = userMapper.selectUserById(1);  
  
// ... 此处不关闭SqlSession,也不执行任何增删改操作  
  
// 第二次查询相同的ID  
User user2 = userMapper.selectUserById(1);  
  
// 因为一级缓存的存在,user1和user2实际上是同一个对象(地址相同)  
System.out.println(user1 == user2); // 输出 true  
  
// 关闭SqlSession,一级缓存随之消失  
sqlSession.close();

在上面的代码中,由于两次查询是在同一个SqlSession中进行的,并且没有执行任何增删改操作,所以第二次查询直接从一级缓存中返回了结果,因此user1和user2引用的是同一个对象

2.二级缓存

  二级缓存是SqlSessionFactory级别的,其作用域超出了一个SqlSession的范围,缓存中的数据可以被所有SqlSession共享。MyBatis框架的二级缓存默认是关闭的,使用时需要在MyBatis框架的核心配置文件中设置开启。

二级缓存
二级缓存默认是关闭的,需要手动开启和配置
二级缓存是基于Mapper的缓存,不同的SqlSession可以访问同一个Mapper的二级缓存中的数据。因此,二级缓存的范围比一级缓存更大
当用户查询数据时,MyBatis会首先去二级缓存中查找。如果二级缓存中没有找到,则再去一级缓存中查找。如果一级缓存中也没有找到,则去数据库中查询,并将查询结果写入一级和二级缓存中
所有的增删改操作(INSERT、UPDATE、DELETE)都会触发二级缓存的刷新,因为增删改操作可能会改变数据库中的数据,所以必须刷新缓存。因此,二级缓存适合在读多写少的场景中开启
为了提高扩展性,MyBatis定义了缓存接口Cache。开发者可以通过实现Cache接口来自定义二级缓存

mybatis-config.xml

<configuration>  
    <!-- ... 其他配置 ... -->  
  
    <!-- 启用全局二级缓存 -->  
    <settings>  
        <setting name="cacheEnabled" value="true"/>  
    </settings>  
  
    <!-- ... 其他配置 ... -->  
</configuration>

Mapper XML

<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 启用Mapper的二级缓存 -->  
    <cache/>  
  
    <!-- ... 其他SQL语句 ... -->  
  
    <select id="selectUserById" resultType="com.example.model.User">  
        SELECT * FROM user WHERE id = #{id}  
    </select>  
  
    <!-- ... 其他SQL语句 ... -->  
  
</mapper>

在上面的配置中,<cache/>标签启用了UserMapper的二级缓存。之后,当你从两个不同的SqlSession中查询相同的用户时,第二次查询可能会从二级缓存中返回结果(如果二级缓存中已有该结果)。

注意:
  二级缓存是基于Mapper的,而不是基于整个应用的。因此,不同的Mapper之间不会共享二级缓存。另外,二级缓存默认使用PerpetualCache实现,但你可以通过实现org.apache.ibatis.cache.Cache接口来提供自定义的缓存实现。

总的来说,MyBatis的一级缓存和二级缓存分别基于SqlSession和Mapper,它们在缓存范围、生命周期、使用场景等方面都有所不同。开发者可以根据实际需求选择是否开启和使用这些缓存

2.5.1 二级缓存的使用方法

  在MyBatis中,二级缓存的使用涉及几个关键步骤:首先,在MyBatis的全局配置文件中启用二级缓存;然后,在特定的Mapper XML文件中启用二级缓存;最后,在代码中执行查询操作以观察二级缓存的效果。

以下是一个简单的示例,展示了如何配置和使用MyBatis的二级缓存

  1. 配置全局二级缓存
    在mybatis-config.xml中启用全局二级缓存:
<configuration>  
    <!-- ... 其他配置 ... -->  
  
    <!-- 启用全局二级缓存 -->  
    <settings>  
        <setting name="cacheEnabled" value="true"/>  
    </settings>  
  
    <!-- ... 其他配置 ... -->  
</configuration>
  1. 在Mapper XML中启用二级缓存
    在Mapper XML文件中,通过<cache/>元素启用二级缓存。例如,在UserMapper.xml中:
<mapper namespace="com.example.mapper.UserMapper">  
  
    <!-- 启用Mapper的二级缓存 -->  
    <cache/>  
  
    <!-- 定义查询语句 -->  
    <select id="selectUserById" resultType="com.example.model.User">  
        SELECT * FROM user WHERE id = #{id}  
    </select>  
  
    <!-- ... 其他SQL语句 ... -->  
  
</mapper>

cache元素中各种属性的作用如下:
  eviction: 选择缓存回收策略,主要包括以下几种策略。
   LRU: 这是默认选项,最近最少回收,移除最长时间不被使用的缓存对象。
   FIFO: 先进先出,按照对象进入缓存的顺序来移除它们。
   SOFT: 软引用,移除基于垃圾回收器状态和软引用规则的对象。
   WEAK: 弱引用,更积极地移除基于垃圾回收器和弱引用规则的对象。
  flushInterval: 设定缓存刷新间隔,以毫秒(ms)为单位设定缓存多长时间自动刷新一次,默认不自动刷新。
  size: 设定缓存中最多存放多少个对象,默认是1024。
  readOnly: 设定缓存数据是否只读。默认是false,表示缓存数据会用于读写操作,MyBatis框架会返回缓存对象的副本以避免脏读;true表示缓存数据只用于读操作,MyBatis框架会为所有从缓存中获取数据的操作返回缓存对象的相同实例,以获得更好的性能。

  1. 在代码中执行查询操作
    在Java代码中,你可以通过两个不同的SqlSession对象来执行相同的查询,并观察二级缓存的效果
import com.example.mapper.UserMapper;  
import com.example.model.User;  
import org.apache.ibatis.session.SqlSession;  
import org.apache.ibatis.session.SqlSessionFactory;  
  
// ... 假设你已经有了SqlSessionFactory的实例 ...  
  
try (SqlSession session1 = sqlSessionFactory.openSession();  
     SqlSession session2 = sqlSessionFactory.openSession()) {  
  
    UserMapper userMapper1 = session1.getMapper(UserMapper.class);  
    UserMapper userMapper2 = session2.getMapper(UserMapper.class);  
  
    // 第一次查询,结果会加载到二级缓存中(如果二级缓存之前没有这个数据)  
    User user1 = userMapper1.selectUserById(1);  
    System.out.println("User from session1: " + user1);  
  
    // 提交session1,确保数据写入二级缓存(如果需要的话)  
    session1.commit();  
  
    // 第二次查询,这次是从另一个SqlSession中查询,但会尝试从二级缓存中获取结果  
    User user2 = userMapper2.selectUserById(1);  
    System.out.println("User from session2: " + user2);  
  
    // 如果二级缓存正常工作,user1和user2将是相同的对象实例  
    System.out.println("Are users the same instance? " + (user1 == user2));  
  
    // 关闭两个session  
    session1.close();  
    session2.close();  
}

  在这个示例中,我们创建了两个SqlSession对象,并使用它们从UserMapper中查询相同的用户。由于启用了二级缓存,并且我们没有在查询之间执行任何修改操作(如INSERT、UPDATE或DELETE),因此第二个查询应该能够直接从二级缓存中获取结果,而不是从数据库中查询。

注意
  二级缓存的行为可能受到多个因素的影响,包括你的数据库事务隔离级别、MyBatis的配置以及你使用的缓存实现(MyBatis默认提供了一个简单的内存缓存实现,但你也可以实现自己的缓存)。因此,在实际应用中,你可能需要根据你的具体需求和环境来调整和优化二级缓存的配置和使用。