Mybatis学习笔记 - 04

Mybatis系列文章


1. Mybatis 中的延迟加载

1.1 延迟加载和立即加载

  • 在上一篇笔记的学习中,我们用到了用户和账户这个一对多的例子。假如此时我们有 1 个用户,该用户拥有 100 个账户,那么问题来了:
    • 当我们查询该用户的时候,要不要把关联的账户也一起查询出来?
    • 当我们查询某个账户的时候,要不要把关联的用户一起查询出来?
  • 答案很简单。在查询用户的时候,用户所拥有的账户信息应该是需要使用的时候才去查询,不然每次查询该用户的时候,都要查询他拥有的账户,那么开销无疑是比较大的;而在查询账户的时候,由于每个账户对应一个用户,所以应该让用户信息随账户信息一并查询出来,否则别人不知道该账户属于谁。
  • 延迟加载
    • 延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载。
    • 一对多多对多的表关系中,通常情况下我们都是采用延迟加载。
  • 立即加载
    • 立即加载就是不管是否需要数据,只要一进行查询,就会把相关联的数据一并查询出来
    • 多对一一对一的表关系中,通常情况下我们都是采用立即加载。

1.2 一对一实现延迟加载

  • 实现以下需求:
    • 当查询账户信息时使用延迟加载。也就是说,如果不需要使用用户信息的话,那么只查询账户信息;只有当需要使用用户信息时,才去关联查询
  • 首先在 Mybatis 的配置文件中开启延迟加载
 <!-- 开启延迟加载 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • 注意,在编写 Mybatis 的配置文件时,文档结构一定不可以随便写,一定要按照官方文档所要求的顺序,比如说:<settings></settings> 标签不可以写在 <environments></environments> 下方。具体文档结构见下图:
    配置文件文档结构
  • 具体说明参考官方文档 : Mybatis 的 XML 配置
  • 修改上一篇笔记编写好的账户映射文件 AccountMapper.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="cn.ykf.mapper.AccountMapper">
    <!-- 定义可以封装带有User的Account的 resultMap -->
    <resultMap id="AccountWithUserMap" type="cn.ykf.pojo.Account">
        <id property="id" column="id"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
        <!-- 关联 User 对象 -->
        <association property="user" javaType="cn.ykf.pojo.User" column="uid" select="cn.ykf.mapper.UserMapper.getUserById"></association>
    </resultMap>
    <!-- 配置查询所有账户,延迟加载用户信息 -->
    <select id="listAllAccounts" resultMap="AccountWithUserMap">
        SELECT * FROM account
    </select>
</mapper>
  • <association></association> 标签中的 select 属性表示我们要调用的映射语句的 ID,它会column 属性指定的列中检索数据,作为参数传递给目标 select 语句
  • column 属性指定传递给我们要调用的映射语句的参数
  • cn.ykf.mapper.UserMapper.getUserById 的映射文件如下:
<mapper namespace="cn.ykf.mapper.UserMapper">
	<!-- 查询单个用户 -->
	<select id="getUserById" parameterType="java.lang.Integer" resultType="cn.ykf.pojo.User">
	    SELECT * FROM user WHERE id = #{uid}
	</select>
</mapper>
  • 执行上篇笔记的测试代码,运行结果如下
    一对一延迟加载
  • 可以发现,现在查询账户的话,并不会和之前一样,把所有的账户信息连同用户信息一并查询出来。而是由于我们在测试代码中打印了 Account 对象,其中调用了 User 对象,才会进行查询。

1.3 一对多实现立即加载

  • 实现以下需求:
  • 当查询用户信息时使用延迟加载。也就是说,如果不需要使用账户信息的话,那么只查询用户信息;只有当需要使用账户信息时,才去关联查询
  • 还是一样先修改 账户 的映射文件,不同的是现在是使用 <collection></collection> 进行延迟加载
<?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="cn.ykf.mapper.UserMapper">
    <!-- 配置 resultMap ,完成实体类与数据库表的映射 -->
    <resultMap id="UserWithAccountsMap" type="cn.ykf.pojo.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
        <result property="address" column="address"/>
        <!-- 配置user对象中accounts集合的映射 -->
        <collection property="accounts" ofType="cn.ykf.pojo.Account" column="id" select="cn.ykf.mapper.AccountMapper.getAccountByUid"></collection>
    </resultMap>
    <!-- 配置查询所有用户,延迟加载账户信息 -->
    <select id="listAllUsers" resultMap="UserWithAccountsMap">
        SELECT * FROM user
    </select>
</mapper>
  • 在账户实体类对应的接口层及配置文件中添加对应的方法
/**
 * 根据用户id查询账户
 *
 * @param uid 用户id
 * @return
 */
Account getAccountByUid(Integer uid);
<mapper namespace="cn.ykf.mapper.AccountMapper">
    <!-- 根据用户id查询账户 -->
    <select id="getAccountByUid" parameterType="int" resultType="cn.ykf.pojo.Account">
        SELECT * FROM account WHERE uid = #{uid}
    </select>
</mapper>
  • 运行结果
    一对多延迟加载述

2. Mybatis 中的缓存

  • 什么是缓存?
    • 缓存就是存在于内存中的临时数据
  • 为什么要使用缓存?
    • 为了减少和数据库交互的次数,提高执行效率
  • 适用于缓存的数据
    • 经常查询并且不经常改变的数据。
    • 数据的正确与否对最终结果影响不大的
  • 不适用于缓存的数据
    • 经常改变的数据。
    • 数据的正确与否对最终结果影响很大的。例如:商品的库存、银行的汇率、股市的牌价等。

2.1 Mybatis 的一级缓存

  • 一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flushclose,它就会存在。当调用 SqlSession 的修改、添加、删除、commit()、close()、clearCache() 等方法时,就会清空一级缓存。
  • 一级缓存流程如下图
    一级缓存分析
    • 第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
    • 得到用户信息,将用户信息存储到一级缓存中
    • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读
    • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
  • Mybatis 默认就是使用一次缓存的,不需要配置。
  • 一级缓存中存放的是对象。(一级缓存其实就是 Map 结构,直接存放对象)

2.2 Mybatis 的二级缓存

  • 二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 SQL 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的
  • 二级缓存流程如下图
    二级缓存分析
    • sqlSession1 去查询用户信息的时候,Mybatis 会将查询数据存储到二级缓存中。
    • 如果 sqlSession3 去执行相同 Mapper 映射下的 SQL 语句,并且执行 commit 提交,那么 Mybatis 将会清空该 Mapper 映射下的二级缓存区域的数据。
    • sqlSession2 去查询与 sqlSession1 相同的用户信息,Mybatis 首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
  • 如果想使用 Mybatis 的二级缓存,那么应该做以下配置
    • 首先在 Mybatis 配置文件中添加配置 (这一步其实可以忽略,因为默认值为 true)
    <settings>
        <!-- 开启缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings> 
    
    • 接着在映射文件中配置
    <mapper namespace="cn.ykf.mapper.UserMapper">
        <!-- 使用缓存 -->
        <cache/>
    </mapper>
    
    • 最后在需要使用二级缓存的操作上配置 (针对每次查询都需要最新数据的操作,要设置成 useCache="false",禁用二级缓存)
    <select id="listAllUsers" resultMap="UserWithAccountsMap" useCache="true">
        SELECT * FROM user
    </select>
    
  • 当我们使用二级缓存的时候,所缓存的类一定要实现 java.io.Serializable 接口,这样才可以使用序列化的方式来保存对象。
  • 由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象。

3. Mybatis 中的注解开发

  • 在 Mybatis 的注解开发中,常用的注解如下表所示:
注解作用
@Intsert实现新增
@Update实现更新
@Delete实现删除
@Select实现查询
@Results实现结果集封装
@ResultMap实现引用 @Results 定义的封装
@One实现一对一结果集封装
@Many实现一对多结果集封装
@SelectProvider实现动态 SQL 映射
@CacheNamespace实现注解二级缓存的使用

3.1 Mybatis 使用注解实现单表 CURD

  • 用户实体类接口层 UserMapper 代码如下
public interface UserMapper {
    /**
     * 查询所有用户
     *
     * @return
     */
    @Select("SELECT * FROM user")
    List<User> listAllUsers();

    /**
     * 添加用户
     *
     * @param user
     * @return 成功返回1,失败返回0
     */
    @Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
    @SelectKey(keyProperty = "id", keyColumn = "id", statement = "SELECT LAST_INSERT_ID()", resultType = Integer.class, before = false)
    int saveUser(User user);

    /**
     * 根据id删除用户
     *
     * @param userId
     * @return 成功返回1,失败返回0
     */
    @Delete("DELETE FROM user WHERE id = #{id}")
    int removeUserById(Integer userId);

    /**
     * 修改用户
     *
     * @param user
     * @return 成功返回1,失败返回0
     */
    @Update("UPDATE user SET username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address} WHERE id = #{id}")
    int updateUser(User user);

    /**
     * 根据id查询单个用户
     *
     * @param userId
     * @return
     */
    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserById(Integer userId);

    /**
     * 根据姓名模糊查询多个用户
     *
     * @param username
     * @return
     */
    @Select("SELECT * FROM user WHERE username LIKE CONCAT('%',#{username},'%')")
    List<User> listUsersByName(String username);

    /**
     * 查询用户总数
     *
     * @return
     */
    @Select("SELECT COUNT(id) FROM user")
    int countUser();
}
  • 注意,如果此时实体类的属性与数据库表列名不一致,那么我们应该使用@Results、@Result、@ResultMap 等注解,如下
public interface UserMapper {
    /**
     * 查询所有用户
     *
     * @return
     */
    @Select("SELECT * FROM user")
    @Results(id = "UserMap",value = {
            @Result(id = true,property = "userId",column = "id"),
            @Result(property = "userName",column = "username"),
            @Result(property = "userBirthday",column = "birthday"),
            @Result(property = "userSex",column = "sex"),
            @Result(property = "userAddress",column = "address"),
    })
    List<User> listAllUsers();

    /**
     * 添加用户
     *
     * @param user
     * @return 成功返回1,失败返回0
     */
    @Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
    @ResultMap("UserMap")
    int saveUser(User user);
  • @Results 注解用于定义映射结果集,相当于标签 <resultMap></resultMap>
    • 其中,id 属性为唯一标识。
    • value 属性用于接收 @Result[] 注解类型的数组。
  • @Result 注解用于定义映射关系,相当于标签 <id /><result />
    • 其中,id 属性指定主键。
    • property 属性指定实体类的属性名,column 属性指定数据库表中对应的列。
  • @ResultMap 注解用于引用 @Results 定义的映射结果集,避免了重复定义映射结果集。

3.2 Mybatis 使用注解实现多对一(一对一)

  • 还是以上一篇笔记的用户和账户为例子。一个用户可以有多个账户,而一个账户只能对应一个用户。
  • 在 Mybatis 中,多对一是作为一对一来进行处理的。也就是说,虽然多个账户可以属于同一个用户(多对一),但是在实体类中,我们是在账户类中添加一个用户类的对象引用,以此来表明所属用户。(此时就相当于一对一)
  • 账户实体类和用户实体类见上篇笔记,接口层代码如下
public interface AccountMapper {
	/**
     * 查询所有账户,并查询所属用户,采用立即加载
     *
     * @return
     */
    @Select("SELECT * FROM account")
    @Results(id = "AccountMap", value = {
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "uid", column = "uid"),
            @Result(property = "user", column = "uid",
                    one = @One(select = "cn.ykf.mapper.UserMapper.getUserById", fetchType = FetchType.EAGER))
    })
    List<Account> listAllAccounts();
}
public interface UserMapper {
    /**
     * 根据id查询单个用户
     *
     * @param userId
     * @return
     */
    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserById(Integer userId);
}
  • @One 注解相当于标签 <association></association>,是多表查询的关键,在注解中用来指定子查询返回单一对象
    • 其中,select 属性指定用于查询的接口方法,fetchType 属性用于指定立即加载或延迟加载,分别对应 FetchType.EAGERFetchType.LAZY
  • 在包含 @one 注解的 @Result 中,column 属性用于指定将要作为参数进行查询的数据库表列。

3.3 Mybatis 使用注解实现一对多

  • 为用户实体类添加包含账户实体类的集合引用,具体代码见上篇笔记。接口层代码如下
public interface UserMapper {
    /**
     * 查询所有用户,并且查询拥有账户,采用延迟加载
     *
     * @return
     */
    @Select("SELECT * FROM user")
    @Results(id = "UserMap", value = {
            @Result(id = true, property = "userId", column = "id"),
            @Result(property = "userName", column = "username"),
            @Result(property = "userBirthday", column = "birthday"),
            @Result(property = "userSex", column = "sex"),
            @Result(property = "userAddress", column = "address"),
            @Result(property = "accounts", column = "id",
                    many = @Many(select = "cn.ykf.mapper.AccountMapper.getAccountByUid", fetchType = FetchType.LAZY))
    })
    List<User> listAllUsers();
public interface AccountMapper {
    /**
     * 根据用户id查询账户列表
     *
     * @param uid
     * @return
     */
    @Select("SELECT * FROM account WHERE uid = #{uid}")
    List<Account> listAccountsByUid(Integer uid);
}
  • @Many 注解相当于标签 <collection></collection>,是多表查询的关键,在注解中用来指定子查询返回对象集合
    • 其中,select 属性指定用于查询的接口方法,fetchType 属性用于指定立即加载或延迟加载,分别对应 FetchType.EAGERFetchType.LAZY
  • 在包含 @Many 注解的 @Result 中,column 属性用于指定将要作为参数进行查询的数据库表列。

3.4 Mybatis 使用注解实现二级缓存

  • 如果使用注解时想开启二级缓存,那么首先应该在 Mybatis 配置文件中开启全局配置
<settings>
    <!-- 开启缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 接着在持久层接口中使用注解即可
@CacheNamespace(blocking = true)
public interface UserMapper {
	// .....
}
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值