第五章 MyBatis加载策略和注解

1. MyBatis加载策略

1.1 延迟加载

概念:实际开发过程中,有的时候只需要加载用户信息,而不用加载其对应的角色身份信息,这时候的加载称之为延迟加载(懒加载)。再通俗一点就是,需要数据的时候在再加载。

1.1.1 局部延迟加载

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策
略。

例子:对获取用户身份信息的时候,局部延迟加载用户角色信息

1.1.1.1 SqlMapperConfig.xml配置

    <settings>
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
    </settings>

1.1.1.2 Mapper.xml映射配置(局部懒加载生效)

    <resultMap id="nestedQueryRoleResultMap" type="com.fuyi.entity.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <!--
        fetchType="lazy"   懒加载
        fetchType="eager"  立即加载
        -->
        <collection property="roleList" ofType="com.fuyi.entity.SysRole" column="id" select="com.fuyi.mapper.SysRoleMapper.findByUid" fetchType="lazy"></collection>
    </resultMap>
    <select id="findAllAndRole" resultMap="nestedQueryRoleResultMap">
        select * from user
    </select>

1.1.1.3 Mapper.xml映射配置(局部懒加载无效)

    <resultMap id="baseResultMap" type="com.fuyi.entity.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <!--
        一对多使用collection             标签进行关联
        property="orderList"           封装到集合的属性名
        ofType="com.fuyi.entity.Order" 封装集合的泛型类型
        -->
        <collection property="orderList" ofType="com.fuyi.entity.Order" fetchType="lazy">
            <id property="id" column="oid"></id>
            <result property="ordertime" column="ordertime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>

    <select id="findAllWithOrder" resultMap="baseResultMap">
        select *, o.id oid from user u left join orders o on u.id = o.uid;
    </select>

1.1.1.4 小结

  • 了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的
    equals、clone、hashCode、toString方法时也会触发关联对象的查询;
  • 延迟加载是基于嵌套查询,所以对于多表关联查询中,配置局部懒加载是不起作用的。

1.1.2 全局延迟加载

1.1.2.1 SqlMapConfig.xml全局配置

    <settings>
        <!-- 全局延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
    </settings>

1.1.2.2 就近原则

    <resultMap id="nestedQueryResultMap" type="com.fuyi.entity.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <collection property="orderList" ofType="com.fuyi.entity.Order" column="id" select="com.fuyi.mapper.OrderMapper.findByUid" fetchType="eager"></collection>
    </resultMap>
    <select id="findAllAndOrder" resultMap="nestedQueryResultMap">
        select * from user
    </select>

开启了全局设置后,如果在局部文件中设置立即加载,则会使用就近原则,局部的加载策略先于全局的加载策略

2. Mybatis缓存

背景:当频繁的发生数据查询的时候,此时会频繁的操作数据库。于是有没有什么办法能够减少对数据库的访问,减少并发所带来的系统性能问题,于是采用了一种机制,对于不频繁改动的数据,只在第一次查询的查询数据库,并将查询出来的数据放入缓存,然后下一次请求查询同样数据的时候,直接进行访问缓存。同样的,Mybatis也提供了这样的机制。

2.1 Mybatis一级缓存

SqlSession级别的缓存,默认开启

2.1.1 机制原理

在参数和sql相同的情况下,使用同一个sqlsession对象调用mapper的方法,往往只会执行一次sql语句,在缓存没有超时的情况下,sqlsession会去取当前缓存数据。

2.1.2 配置SQL语句日志

  • 添加依赖
<dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
</dependency>

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.7</version>
</dependency>
  • 日志打印配置
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=d:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

2.1.3 测试

/**
 * @author raofy
 * @date 2021-05-14 13:56
 * @desc
 */
public class CacheTest {

    private static Logger logger = LoggerFactory.getLogger(CacheTest.class);

    /**
     * 一级缓存测试
     *
     */
    @Test
    public void level1CacheTest() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 获取mapper
        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);

        // 5. 执行查询
        List<Order> result = mapper.findAllAndUser();

        // 6. 打印结果
        logger.info("=====================第一次查询====================");
        result.forEach(System.out::println);

        logger.info("=====================第二次查询====================");
        mapper.findAllAndUser().forEach(System.out::println);
  
        sqlSession.close();

        resourceAsStream.close();
    }
}

2.1.4 分析

当执行sqlsession的增加、更新和删除,或者是调用cleanCache()、commit()和close()都会删除缓存。

  • 清除(cleanCache)

        @Test
        public void level1CacheTest() throws IOException {
            // 1. 加载核心配置文件
            InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    
            // 2. 获取SQLFactory工厂对象
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    
            // 3. 获取SQLSession对象
            SqlSession sqlSession = factory.openSession();
    
            // 4. 获取mapper
            OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    
            // 5. 执行查询
            List<Order> result = mapper.findAllAndUser();
    
            // 6. 打印结果
            logger.info("=====================第一次查询====================");
            result.forEach(System.out::println);
    
            sqlSession.clearCache();
    
            logger.info("=====================第二次查询====================");
            mapper.findAllAndUser().forEach(System.out::println);
    
            sqlSession.close();
    
            resourceAsStream.close();
        }
    

    或者

        <!-- flushCache 表示清除缓存 -->
        <select id="findAllAndUser" resultMap="nestedQueryResultMap" flushCache="true">
            select * from orders
        </select>
    

2.2 Mybatis二级缓存

namespace级别,默认是不开启,MyBatis要求返回的POJO必须是可序列化的。
也就是返回的实体类要求实现Serializable接口

2.2.1 开启二级缓存

2.2.1.1 配置sqlMapConfig.xml全局配置文件

        <!--
        因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。
        为true代表开启二级缓存;为false代表不开启二级缓存。
        -->
        <setting name="cacheEnabled" value="true"/>

2.2.1.2 配置userMapper.xml文件

    <!-- 当前映射文件开启二级缓存文件 -->
    <cache></cache>
    <!--
    <select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存。
    如果不使用二级缓存可以设置为false
    注意:
    针对每次查询都需要最新的数据sql,要设置成useCache="false",禁用二级缓存。
    -->
    <select id="findById" resultType="com.fuyi.entity.User" parameterType="integer" useCache="true">
        select * from user where id = #{id}
    </select>

2.2.1.3 测试

    @Test
    public void level2CacheTest() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 获取mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 5. 执行查询
        User result = mapper.findById(1);

        // 6. 打印结果
        logger.info("=====================第一次查询====================");
        System.out.println(result);

        sqlSession.close();
        SqlSession sqlSession2 = factory.openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

        logger.info("=====================第二次查询====================");
        System.out.println(mapper2.findById(1));

        sqlSession.close();

        resourceAsStream.close();
    }

2.2.1.4 分析

二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个
SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所有insert、update和delete语句会刷新缓存。
  • 由于二级缓存是namepace级别的,所以当嵌套查询的时候,嵌套语句所在的namepace2发生了缓存刷新,在namepace1缓存并没有得到刷新,此时就会出现脏读的问题。(感觉二级缓存很鸡肋)

3. Mybatis注解

3.1 常用注解

  • @Insert:实现新增,等价于<insert></insert>
  • @Delete:实现删除,等价于<delete></delete>
  • @Update:实现更新,等价于<update></update>
  • @Select:实现查询,等价于<select></select>
  • @Result:实现结果集封装,等价于<result></result>
  • @Results:可以与@Result 一起使用,封装多个结果集,等价于<resultMap></resultMap>
  • @One:实现一对一结果集封装,等价于<association></association>
  • @Many:实现一对多结果集封装,等价于<>

3.2 CRUD操作(重点)

3.2.1 查询

  • userMapper
   /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    List<User> annotationFindAll();
  • 测试
    @Test
    public void annotationFindAll() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 5. 查询
        List<User> result = mapper.annotationFindAll();

        // 6. 打印
        result.forEach(System.out::println);

        // 7. 释放资源
        sqlSession.close();
    }

3.2.2 添加

  • userMapper
   /**
     * 添加
     *
     * @param user
     */
    @Insert("INSERT INTO `user`(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
    public void annotationSave(User user);
  • 测试
    @Test
    public void annotationSave() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setUsername("测试");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("广东佛山");


        // 5. 查询
        mapper.annotationSave(user);

        sqlSession.commit();
        // 6. 释放资源
        sqlSession.close();
    }

3.2.3 删除

  • userMapper
   /**
     * 删除
     *
     * @param id
     */
    @Delete("DELETE FROM `user` where id = #{id}")
    public void annotationDelete(Integer id);
  • 测试
    @Test
    public void annotationDelete() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 5. 查询
        mapper.annotationDelete(4);

        sqlSession.commit();
        // 6. 释放资源
        sqlSession.close();
    }

3.2.4 更新

  • userMapper
   /**
     * 更新
     *
     * @param user
     */
    @Update("UPDATE `user` SET username = #{username},birthday = #{birthday},sex= #{sex},address = #{address} WHERE id = #{id}")
    public void annotationUpdate(User user);
  • 测试
    @Test
    public void annotationUpdate() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setId(4);
        user.setUsername("测试2");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("广东肇庆");


        // 5. 查询
        mapper.annotationUpdate(user);

        sqlSession.commit();
        // 6. 释放资源
        sqlSession.close();
    }

3.3 复杂映射

3.3.1 一对一

查询订单和对应的用户信息

  • SQL语句
select * from orders
select * from user where id = #{订单id}
  • OrderMapper
   /**
     * 一对一查询,基于注解
     *
     * @return
     */
    @Select("select * from orders")
    @Results({
           @Result(id = true, column = "id", property = "id"),
           @Result(column = "ordertimes", property = "ordertimes"),
           @Result(column = "total", property = "total"),
           @Result(column = "uid", property = "user", javaType = User.class,
           one = @One(select = "com.fuyi.mapper.UserMapper.annotationFindById", fetchType = FetchType.EAGER))
    })
    List<Order> annotationFindAllWithUser();
  • UserMapper
   /**
     * 基于注解查询
     *
     * @param id
     * @return
     */
    @Insert("select * from user where id = #{id}")
    User annotationFindById(Integer id);
  • 测试
   /**
     * 一对一,多对一测试例子
     */
    @Test
    public void annotationOneToOneTest() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 获取mapper
        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);

        // 5. 执行查询
        List<Order> result = mapper.annotationFindAllWithUser();

        // 6. 打印结果
        result.forEach(System.out::println);

        sqlSession.close();

        resourceAsStream.close();
    }

3.3.2 一对多

查询用户和对应的订单信息

  • sql语句
select * from user
select * from orders where uid = #{用户id}
  • UserMapper
   /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    @Results({
            @Result(id=true, column="id", property="id"),
            @Result(column="brithday", property="brithday"),
            @Result(column="sex", property="sex"),
            @Result(column="address", property="address"),
            @Result(property="orderList", javaType=List.class,column="id" ,
                    many=@Many(select="com.fuyi.mapper.OrderMapper.annotationFindById", fetchType = FetchType.EAGER))
    })
    List<User> annotationFindAll();
  • OrderMapper
   /**
     * 基于注解查询
     *
     * @param id
     * @return
     */
    @Select("select * from orders where id = #{id}")
    List<Order> annotationFindById(Integer id);
  • 测试
    @Test
    public void annotationFindAll() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 5. 查询
        List<User> result = mapper.annotationFindAll();

        // 6. 打印
        result.forEach(System.out::println);

        // 7. 释放资源
        sqlSession.close();
    }

3.3.3 多对多

查询用户和对应的角色信息

  • sql语句
# 查询所有用户
select * from user
# 根据用户id查询用户的角色列表
select * from sys_role sr left join sys_user_role sur on sur.roleid = sr.id where sur.userid = id
  • UserMapper
   /**
     * 基于注解
     *
     * @return
     */
    @Select("select * from user")
    @Results({
            @Result(id = true, property = "id", column = "id"),
            @Result(column="brithday", property="brithday"),
            @Result(column="sex", property="sex"),
            @Result(column="address", property="address"),
            @Result(property="roleList", javaType=List.class,column="id" ,
                    many=@Many(select="com.fuyi.mapper.SysRoleMapper.annotationFindByUid", fetchType = FetchType.EAGER))
    })
    List<User> annotationFindAllWithRole();
  • RoleMapper
   /**
     * 基于注解根据用户id查询对应的角色身份
     *
     * @param uid
     * @return
     */
    @Select("select * from sys_role sr left join sys_user_role sur on sur.roleid = sr.id where sur.userid = id")
    List<SysRole> annotationFindByUid(Integer uid);
  • 测试
    @Test
    public void annotationFindAllWithRoleTest() throws IOException {
        // 1. 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 获取SQLFactory工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3. 获取SQLSession对象
        SqlSession sqlSession = factory.openSession();

        // 4. 执行SQL参数
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 5. 查询
        List<User> users = mapper.annotationFindAllWithRole();
        users.forEach(System.out::println);

        // 6. 释放资源
        sqlSession.close();
    }

3.4 基于注解的二级缓存

  • 首先在SQLMapConfig.xml进行配置
<setting name="cacheEnabled" value="true"/>
  • UserMapper使用注解
@CacheNamespace
public interface UserMapper {}

3.5 基于注解的延迟加载

fetchType = FetchType.LAZY        表示懒加载
fetchType = FetchType.EAGER       表示立即加载
fetchType = FetchType.DEFAULT     表示使用全局配置

3.6 小结

1.注解开发和xml配置相比,从开发效率来说,注解编写更简单,效率更高。

2.从可维护性来说,注解如果要修改,必须修改源码,会导致维护成本增加。xml维护性更强。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容