第五章 MyBatis加载策略和注解

1. MyBatis加载策略

1.1 延迟加载

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

1.1.1 局部延迟加载

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

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

1.1.1.1 SqlMapperConfig.xml配置

    
        
    

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

    
        
        
        
        
        
        
        
    
    

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

    
        
        
        
        
        
        
        
            
            
            
        
    

    

1.1.1.4 小结

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

1.1.2 全局延迟加载

1.1.2.1 SqlMapConfig.xml全局配置

    
        
        
        
    

1.1.2.2 就近原则

    
        
        
        
        
        
        
    
    

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

2. Mybatis缓存

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

2.1 Mybatis一级缓存

SqlSession级别的缓存,默认开启

2.1.1 机制原理

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

2.1.2 配置SQL语句日志

  • 添加依赖

     log4j
     log4j
     1.2.17



      org.slf4j
      slf4j-log4j12
      1.7.7

  • 日志打印配置
### 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 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 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();
        }
    

    或者

        
        
    

2.2 Mybatis二级缓存

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

2.2.1 开启二级缓存

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

        
        

2.2.1.2 配置userMapper.xml文件

    
    
    
    

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:实现新增,等价于
  • @Delete:实现删除,等价于
  • @Update:实现更新,等价于
  • @Select:实现查询,等价于
  • @Result:实现结果集封装,等价于
  • @Results:可以与@Result 一起使用,封装多个结果集,等价于
  • @One:实现一对一结果集封装,等价于
  • @Many:实现一对多结果集封装,等价于<>

3.2 CRUD操作(重点)

3.2.1 查询

  • userMapper
   /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    List 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 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 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 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 annotationFindAll();
  • OrderMapper
   /**
     * 基于注解查询
     *
     * @param id
     * @return
     */
    @Select("select * from orders where id = #{id}")
    List 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 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 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 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 users = mapper.annotationFindAllWithRole();
        users.forEach(System.out::println);

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

3.4 基于注解的二级缓存

  • 首先在SQLMapConfig.xml进行配置

  • UserMapper使用注解
@CacheNamespace
public interface UserMapper {}

3.5 基于注解的延迟加载

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

3.6 小结

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

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

你可能感兴趣的:(第五章 MyBatis加载策略和注解)