2.4.4 Mybatis 局部/全局延迟加载, 一/二级缓存, 使用Mybatis注解, 增删改查, 一对一/多, 多对多, 多表查询, 注解二级缓存, 注解延迟加载

目录

Mybatis 加载策略及注解开发

一 MyBatis加载策略

1.1 什么是延迟加载?

1.2 实现

1.2.1 局部延迟加载

1.2.2 设置触发延迟加载的方法

1.2.3 全局延迟加载

二 MyBatis缓存

2.1 为什么使用缓存?

2.2 一级缓存

2.2.1 介绍

2.2.2 验证

2.2.3 分析

2.2.4 清除

2.3 二级缓存

2.3.1 介绍

2.3.2 验证

2.3.3 分析

2.3.4 注意问题(脏读)

2.4 小结

三 MyBatis注解

3.1 MyBatis常用注解

3.2 MyBatis注解的增删改查【重点】

3.2.1 创建UserMapper接口

3.2.2 编写核心配置文件

3.2.3 测试代码

3.3 使用注解实现复杂映射开发

​3.4 一对一查询

3.4.1 介绍

3.4.2 代码实现

3.5 一对多查询

3.5.1 介绍

3.5.2 代码实现

3.6 多对多查询

3.6.1 介绍

3.6.2 代码实现

3.7 基于注解的二级缓存

3.7.1 配置SqlMapConfig.xml文件开启二级缓存的支持

3.7.2 在Mapper接口中使用注解配置二级缓存

3.8 注解延迟加载

3.9 小结


Mybatis 加载策略及注解开发

一 MyBatis加载策略

1.1 什么是延迟加载?

问题
通过前面的学习,我们已经掌握了Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。

实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。

举个栗子

延迟加载

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。


1.2 实现

1.2.1 局部延迟加载

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

<!--一对多嵌套查询:查询所有的用户,同时还要查询出每个用户所关联的订单信息-->

    <resultMap id="userOrderMap" type="com.lagou.domain.User">
        <id property="id" column="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="ordersList" ofType="com.lagou.domain.Orders" column="id"
                    select="com.lagou.mapper.OrderMapper.findByUid" fetchType="lazy"></collection>
    </resultMap>

    <select id="findAllWithOrder2" resultMap="userOrderMap">
        SELECT * FROM USER
    </select>

1.2.2 设置触发延迟加载的方法

大家在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。
我们可以在配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。

<!--  配置在configuration下面  -->
    <settings>

        <!--toString()相关方法会延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
        
    </settings>

1.2.3 全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。

<!--  配置在configuration下面  -->
    <settings>
        <!--开启全局延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--所有方法都会延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
        
    </settings>

注意
局部的加载策略优先级高于全局的加载策略。

<resultMap id="orderMap2" type="com.lagou.domain.Orders">
        <id property="id" column="id"/>
        <result property="ordertime" column="ordertime"/>
        <result property="total" column="total"/>
        <result property="uid" column="uid"/>

        <!--问题:1.怎么去执行第二条sql , 2.如何执行第二条sql的时候,把uid作为参数进行传递
                 下面的select用来调用第二条sql
                 column 是要传递的参数,    -->
        <association property="user" javaType="com.lagou.domain.User"
                     select="com.lagou.mapper.UserMapper.findById" column="uid" fetchType="eager"/>

    </resultMap>

    <!--一对一嵌套查询-->
    <select id="findAllWithUser2" resultMap="orderMap2">
        SELECT * FROM orders
    </select>

二 MyBatis缓存

2.1 为什么使用缓存?

当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。

减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。

一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。

像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。

2.2 一级缓存

2.2.1 介绍

一级缓存是SqlSession级别的缓存,是默认开启的(面试可能会问)
所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

2.2.2 验证

使用日志配置文件查看sql执行情况

### 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=c:/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

        <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>
   /*
        验证mybatis中的一级缓存
     */
    @Test
    public void testOneCache() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // 根据id查询用户信息
        // 第一次查询,查询的数据库
        User user1 = userMapper.findById(1);
        System.out.println(user1);

        // clearCache: 手动清空缓存
        //sqlSession.clearCache();

        // 第二次查询,查询的是一级缓存
        User user2 = userMapper.findById(1);
        System.out.println(user2);

        sqlSession.close();
    }

我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为1的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。

2.2.3 分析

一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存。

2.2.4 清除

    <!--根据id查询用户
        每次查询时,都会自动清除缓存
    -->
    <select id="findById" resultType="com.lagou.domain.User" parameterType="int"  flushCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>

2.3 二级缓存

2.3.1 介绍

二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的
二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。
也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置 <cache/> 就可以开启二级缓存了。


2.3.2 验证

a)配置核心配置文件

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

b)配置UserMapper.xml映射

<mapper namespace="com.lagou.mapper.UserMapper">

    <!--当前映射开启二级缓存-->
    <cache></cache>

    <!--根据id查询用户
        useCache="true" 代表当前这个statement是使用二级缓存
    -->
    <select id="findById" resultType="com.lagou.domain.User" parameterType="int" useCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>

</mapper>

c)序列化User实体

// 序列化
public class User  implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 表示多方关系:集合 : 代表了当前用户所具有的订单列表 collection
    private List<Orders> ordersList;

    // 表示多方关系:集合 : 代表了当前用户所具有的角色列表 collection
    private List<Role> roleList;

}

d)测试结果

    /*
       验证mybatis中的二级缓存
    */
    @Test
    public void testTwoCache() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

        // 第一次查询
        User user = userMapper1.findById(1);

        System.out.println(user);

        // 只有执行sqlSession.commit或者sqlSession.close,那么一级缓存中内容才会刷新到二级缓存
        sqlSession1.close();


        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

        User user2 = userMapper2.findById(1);

        System.out.println(user2);

        sqlSession2.close();
        
    }

2.3.3 分析

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


2.3.4 注意问题(脏读)

mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题

2.4 小结

三 MyBatis注解

3.1 MyBatis常用注解

这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作. 

3.2 MyBatis注解的增删改查【重点】

3.2.1 创建UserMapper接口

    /*
        查询用户
     */
    @Select("select * from user")
    public List<User> findAll();

    /*
        添加用户
     */
    @Insert("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})")
    public void save(User user);

    /*
        更新用户
     */
    @Update("update user set username = #{username},birthday=#{birthday} where id = #{id}")
    public void update(User user);

    /*
        删除用户
     */
    @Delete("delete from user where id = #{id}")
    public void delete(Integer id);

3.2.2 编写核心配置文件

<!--我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可-->
<mappers>
    <!--扫描使用注解的Mapper类-->
    <mapper class="com.lagou.mapper.UserMapper"></mapper>
</mappers>
<!--或者指定扫描包含映射关系的接口所在的包也可以-->
<mappers>
    <!--扫描使用注解的Mapper类所在的包-->
    <package name="com.lagou.mapper"></package>
</mappers>

3.2.3 测试代码

public class MybatisTest {
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;


    // 下面的注解要在 @Test方法标注的方法执行之前来执行
    @Before
    public void before() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

         sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

         sqlSession = sqlSessionFactory.openSession();
    }

    // 下面的注解要在 @Test方法标注的方法执行之后来执行
    @After
    public void after(){

        sqlSession.commit();
        sqlSession.close();
    }



    /*
        测试查询方法
     */
    @Test
    public void testSelect() throws IOException {

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> all = mapper.findAll();

        for (User user : all) {
            System.out.println(user);
        }
    }


    /*
        测试添加方法
     */
    @Test
    public void testInsert(){

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setUsername("汤唯");
        user.setSex("女");
        user.setBirthday(new Date());
        user.setAddress("北京");

        mapper.save(user);
    }


    @Test
    public void testUpdate(){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setUsername("柳岩真美");
        user.setBirthday(new Date());
        user.setId(9);

        mapper.update(user);
    }


    @Test
    public void testDelete(){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        mapper.delete(9);
    }
}

3.3 使用注解实现复杂映射开发

之前我们在映射文件中通过配置 <resultMap>、<association>、<collection> 来实现复杂关系映射。

使用注解开发后,我们可以使用 @Results、@Result,@One、@Many 注解组合完成复杂关系的配置。


3.4 一对一查询

3.4.1 介绍

需求:查询一个订单,与此同时查询出该订单所属的用户

一对一查询语句

SELECT * FROM orders;
SELECT * FROM `user` WHERE id = #{订单的uid};

3.4.2 代码实现

a)OrderMapper接口

    /*
        查询所有订单,同时查询订单所属的用户信息
     */

    @Select("select * from orders")
    @Results({  // 代替的就是resultMap标签 id标签  result标签
          @Result(property = "id",column = "id",id = true), // id = true 表示当前是主键
          @Result(property = "ordertime",column = "ordertime"),
          @Result(property = "total",column = "total"),
          @Result(property = "uid",column = "uid"),
            @Result(property = "user",javaType = User.class,column = "uid",
                    one = @One(select = "com.lagou.mapper.UserMapper.findById",fetchType = FetchType.EAGER))
            // 上面如果不设置fetchType, 查询结果中结果为null, 因为当前开了全局延迟加载
    })
    public List<Orders> findAllWithUser();

b)UserMapper接口

    /*
        根据id查询用户
     */
    @Select("select * from user where id = #{uid}")
    public User findById(Integer uid);

c)测试代码

@Before
@After

    /*
        一对一查询(注解方式)
     */
    @Test
    public void testOneToOne(){

        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);

        List<Orders> allWithUser = mapper.findAllWithUser();

        for (Orders orders : allWithUser) {
            System.out.println(orders);
        }
    }

3.5 一对多查询

3.5.1 介绍

需求:查询一个用户,与此同时查询出该用户具有的订单
一对多查询语句

SELECT * FROM `user`;
SELECT * FROM orders where uid = #{用户id};

3.5.2 代码实现

a)UserMapper接口

    /*
        查询所有用户,及关联的订单信息
     */

    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id",id = true),
            @Result(property = "username",column = "username"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "address",column = "address"),
            @Result(property = "ordersList",javaType = List.class,column = "id",
                many = @Many(select = "com.lagou.mapper.OrderMapper.findOrderByUid",fetchType = FetchType.LAZY))
    })
    public List<User> findAllWithOrder();

b)OrderMapper接口

    /*
        根据传递过来的用户id,查询该用户所具有的订单信息
     */
    @Select("select * from orders where uid = #{uid}")
    public List<Orders> findOrderByUid(Integer uid);

c)测试代码

    /*
       一对多查询(注解方式)
    */
    @Test
    public void testOneToMany(){

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> allWithOrder = mapper.findAllWithOrder();

        for (User user : allWithOrder) {
            System.out.println(user);
            // 不加下面的语句, 订单信息为空, 因为开启了延迟加载
            System.out.println(user.getOrdersList());
        }
    }

3.6 多对多查询

3.6.1 介绍

需求:查询所有用户,同时查询出该用户的所有角色

多对多查询语句

SELECT * FROM `user`;
SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid`
WHERE ur.`uid` = #{用户id};

3.6.2 代码实现

a)UserMapper接口

    /*
        查询所有用户及关联的角色信息
     */

    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id",id = true),
            @Result(property = "username",column = "username"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "address",column = "address"),
            @Result(property = "roleList",javaType = List.class,column = "id",
                many = @Many(select = "com.lagou.mapper.RoleMapper.findAllByUid")),
    })
    public List<User> findAllWithRole();


b)RoleMapper接口

    /*
        根据传递过来的用户id,查询该用户所具有的角色信息
     */
    @Select("SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON ur.roleid = r.id WHERE ur.userid = #{uid}")
    public List<Role> findAllByUid(Integer uid);


c)测试代码

    /*
   多对多查询(注解方式)
*/
    @Test
    public void testManyToMany(){

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> allWithRole = mapper.findAllWithRole();

        for (User user : allWithRole) {
            System.out.println(user);
            System.out.println(user.getRoleList());
        }

    }


3.7 基于注解的二级缓存

3.7.1 配置SqlMapConfig.xml文件开启二级缓存的支持

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

3.7.2 在Mapper接口中使用注解配置二级缓存

@CacheNamespace // 配置了二级缓存
public interface UserMapper { ... }
    /*
        测试注解实现二级缓存
     */
    @Test
    public void cacheTest(){

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();


        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);


        User user1 = userMapper1.findById(1);

        System.out.println(user1);

        // 才能将内容从一级缓存刷新到二级缓存
        sqlSession1.close();

        User user2 = userMapper2.findById(1);

        System.out.println(user2);

        sqlSession2.close();
    }

3.8 注解延迟加载

不管是一对一还是一对多 ,在注解配置中都有fetchType的属性


3.9 小结

猜你喜欢

转载自blog.csdn.net/chengh1993/article/details/110389100