SSM框架系列学习总结7之Mybatis中的延迟加载和缓存

Mybatis的延迟加载

比如在查询订单信息,并且关联查询该订单对应的用户信息。
注意两个问题:
1.要是用延迟加载必须使用ResultMap作为输出映射方式
2.Mybatis默认没有延迟加载, 所以需要在全局配置文件中加入配置
在sqlMapConfig.xml加上如下配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!-- 加载数据库配置信息的属性文件, 跟spring整合以后就没了 -->
    <properties resource="dbConfig.properties">
        <!--<property name="user" value="root1"/>-->
    </properties>

    <settings>

        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载取消 -->
        <setting name="aggressiveLazyLoading" value="false"/>

    </settings>

    <!-- 配置自定义类型别名
     type: 本来的类型
     alias: 别名
     <package name=""/>: 批量扫描某一个包, 如果是批量配置别名, 那么
     别名就是类名, 并且首字母大写和小写都可以
     -->
    <typeAliases>
        <!-- 配置单个别名 -->
        <!--<typeAlias type="com.wtu.entity.User" alias="user"/>-->
        <package name="com.wtu.pojo"/>
    </typeAliases>


    <!-- environments 里面的内容在mybatis和spring整合以后, 就全部没了 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driverClass}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 通过mapper接口来加载映射文件
            1. 映射文件的文件名必须和接口名相同
            2. 映射文件必须和Mapper接口处于同一个目录下
            3. 这种方式只适合Mapper代理开发模式 -->
        <!--<mapper class="com.wtu.mapper.UserMapper"/>-->

        <!-- 批量加载映射文件, 自动去扫描某一个包, 将该包下的所有映射文件加载 -->
        <package name="com.wtu.mapper"/>
    </mappers>
</configuration>

实体类:

public class Orders {
    private Integer id;
    private Integer user_id;
    private Integer number;
    private Date createtime;
    private String note;

    // 封装用户信息
    private User user;
    // ...
}

映射文件的编写:

    <!-- 定义延迟加载用户信息的resultMap -->
    <resultMap id="lazyLoadingUser" type="orders">
        <id column="id" property="id"/>
        <result column="user_id" property="user_id"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
        <!-- 将用户的信息映射到Orders类中user属性
            select: 表示延迟加载的时候, 需要去查询哪一条SQL
            加入该SQL在其它的映射文件中的时候, 那么需要在前面加上namespace
            column: 在延迟加载查询的时候, 需要传递的字段的值 -->
        <association property="user" javaType="user" select="findUserById"
            column="user_id">
        </association>
    </resultMap>

    <!-- 查询订单并且关联查询用户信息, 延迟加载用户信息 -->
    <select id="findOrdersLazyLoadingUser" resultMap="lazyLoadingUser">
        select * from orders
    </select>

    <select id="findUserById" parameterType="int" resultType="user">
        select * from user where id = #{value}
    </select>
public interface OrdersMapper {
    // 查询订单并且关联查询用户信息, 延迟加载用户信息
    List<Orders> findOrdersLazyLoadingUser();

    User findUserById(Integer id);
}

测试代码:

public class OrdersMapperTest {
    private SqlSessionFactory factory;

    @Before
    public void getFactory() throws IOException {
        this.factory = new SqlSessionFactoryBuilder().build(
                Resources.getResourceAsStream("mybatis/sqlMapConfig.xml")
        );
    }

    @Test
    // 查询订单并且关联查询用户信息, 延迟加载用户信息
    public void findOrdersLazyLoadingUser() {
        SqlSession session = factory.openSession();
        OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);

        List<Orders> ordersList = ordersMapper.findOrdersLazyLoadingUser();
        System.out.println(ordersList);

        // 在此处获取用户的信息
        for (Orders orders : ordersList) {
            User user = orders.getUser();
            System.out.println(user);
        }
    }

日志运行截图:
image.png

Mybatis的缓存

Mybatis提供了一级缓存和二级缓存:

一级缓存

一级缓存是SqlSession级别的缓存
image.png

使用sqlSession去第一次查询数据的时候,会将查询到数据保存到sqlSession中,依赖着一个Map对象,当第二次使用同一个sqlSession对象去查询相同的数据的时候,不会再向数据库发送sql语句。直接从缓存中获取数据。但是如果在这两次查询之间有执行增,删,改的操作。那么session还需要commit()操作,那么这个时候会清除掉SqlSession中的缓存。这是为了保存缓存中的数据始终和数据库中的数据保持一致。一级缓存在mybatis中默认已经开启。

测试一级缓存的存在

    @Test
    // 测试一级缓存
    public void findUserById() {
        SqlSession session = factory.openSession();
        OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);

        User user1 = ordersMapper.findUserById(28);
        System.out.println(user1);
        user1.setUsername("study");
        // 修改用户信息, 会清除掉SqlSession中的缓存, 第二次查询的时候, 会再次从数据库中查询
        ordersMapper.updateUser(user1);
        session.commit();

        User user2 = ordersMapper.findUserById(28);
        System.out.println(user2);
    }

二级缓存

当第一个SqlSession查询数据库的时候,会将查询出来的数据放到二级缓存中,然后另外的SqlSession再次查询同样的数据的时候,会先查询二级缓存中的数据。同样在两次查询之间如果有执行commit()操作 那么也会清除二级缓存中的数据
注意: 二级缓存默认是没有开启的,所以必须配置中的参数,然后在映射文件中添加表示开启二级缓存。因为二级缓存的存储介质不一定是内存可能是是存储在硬盘上面,那么需要我们的pojo去实现序列化接口。
实体类实现序列化接口:

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // ...(getter和setter方法省略)
}

在mybatis全局配置文件中开启二级缓存:

<settings>
        <!-- 在控制台输出日志信息 -->
        <setting name="logImpl" value="LOG4J"/>

        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载取消 -->
        <setting name="aggressiveLazyLoading" value="false"/>

        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

在映射文件中开启二级缓存:

<!--
如果采用的mapper代理开发模式, 那么映射文件的命名空间必须是
Mapper接口的全路径
-->
<mapper namespace="com.wtu.mapper.OrdersMapper">
    <!-- 开启二级缓存 -->
    <cache/>

    <!-- 根据id查询用户信息
    如果想要某一条查询语句禁用二级缓存, 那么需要加上
        useCache="false" -->
    <select id="findUserById" parameterType="int" resultType="user" useCache="true">
        select * from user where id = #{value}
    </select>
</mapper>

如果想要在执行commit()操作的时候, 不去刷新二级缓存, 那么只需要添加如下代码
image.png

但是一般不会这样,这样会导致二级缓存中的数据和数据库中的数据不同步。

二级缓存测试代码

    // 测试二级缓存
    @Test
    public void testCache2() {
        SqlSession session1 = factory.openSession();
        SqlSession session2 = factory.openSession();
        SqlSession session3 = factory.openSession();

        OrdersMapper mapper1 = session1.getMapper(OrdersMapper.class);
        OrdersMapper mapper2 = session2.getMapper(OrdersMapper.class);
        OrdersMapper mapper3 = session3.getMapper(OrdersMapper.class);

        User user1 = mapper1.findUserById(28);
        System.out.println(user1);
        session1.close();

        // 修改用户信息, 会清除掉二级缓存, 第二次查询的时候, 会再次从数据库中查询
        user1.setUsername("learn");
        mapper3.updateUser(user1);
        session3.commit();
        session3.close();

        User user2 = mapper2.findUserById(28);
        System.out.println(user2);
        session2.close();
    }

运行成功:
image.png

补充: Spring整合mybatis

准备环境:
spring的jar包
Mybatis的jar包
Mybatis和spring的整合jar包
spring的配置文件
Mybatis的全局配置文件
Mybatis的映射文件

这里先不用Maven, 到后面SSM整合的时候, 我会使用Maven的!
image.png

传统方式DAO开发的整合(需要编写接口, 也需要编写实现类)
1.spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <!--加载属性文件, 从类路径下直接加载 -->
    <context:property-placeholder location="classpath:dbConfig.properties"/>

    <!-- 注册c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${url}"/>
        <property name="user" value="${user}"/>
        <property name="password" value="${pass}"/>
    </bean>

    <!-- 注册SqlSessionFactory对象, 由于SqlSessionFactory是个接口, 所以
        配置它的实现类
        然后mybatis和spring整合包正好提供了一个实现类 SqlSessionFactoryBean -->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis/sqlMapConfig.xml"/>
    </bean>

    <!-- 注册UserDaoImpl对象 -->
    <bean id="userDaoImpl" class="com.wtu.ssm.dao.UserDaoImpl">
        <property name="sqlSessionFactory" ref="factory"/>
    </bean>

    <!-- mapper代理开发时, 由于没有实现类
        MapperFactoryBean:
        我们通过该类来产生Mapper接口的代理对象 -->
    <!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
        <!--&lt;!&ndash; 我们自己写的Mapper接口的路径 &ndash;&gt;-->
        <!--<property name="mapperInterface" value="com.wtu.ssm.mapper.UserMapper"/>-->
        <!--<property name="sqlSessionFactory" ref="factory"/>-->
    <!--</bean>-->

    <!-- 通过MapperScannerConfigurer来批量扫描某一个包
        生成的每一个代理对象的id为接口名, 并且首字母小写 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wtu.ssm.mapper" />
        <!-- 这个方法过时了, 而是需要sqlSessionFactoryBeanName
            通过工厂的名字来加载 -->
        <!--<property name="sqlSessionFactory" ref="factory"/>-->

        <property name="sqlSessionFactoryBeanName" value="factory"/>
    </bean>
</beans>

Mybatis的全局配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> 
    <!-- 在控制台输出日志信息 -->
    <!--<settings>-->
        <!--<setting name="logImpl" value="LOG4J"/>-->
    <!--</settings>-->

    <typeAliases>
        <package name="com.wtu.ssm.entity"/>
    </typeAliases>

    <!-- 加载映射文件  -->
  <!--<mappers>-->
      <!--&lt;!&ndash; 原始dao开发的加载 &ndash;&gt;-->
      <!--<mapper resource="mybatis/user.xml"/>-->
  <!--</mappers>-->
</configuration>

Dao层的实现类:
image.png

在测试类中 需要启动springIOC容器:
image.png

Mapper代理模式的整合(推荐)

1.通过MapperFactoryBean配置单个Mapper接口的代理对象
由于mapper代理开发方式没有实现类 只有接口,然而在spring中注册对象是需要一个类,所以提供了MapperFactoryBean 这么一个类,可以通过这个类来注册代理对象
image.png
该类中必须配置两个属性:
a. mapperInterface:配置自己编写的mapper接口的路径。
b. sqlSessionFactory: 需要配置一个session工厂 ,引入一个工厂对象
然后再springIOC容器中就可以通过 id 来获取mapper接口的代理对象
(推荐)
2.通过MapperScannerConfigurer来批量扫描某一个包,生成该包下所有的mapper接口的代理对象
image.png
代码:

    <!-- 通过MapperScannerConfigurer来批量扫描某一个包
        生成的每一个代理对象的id为接口名, 并且首字母小写 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wtu.ssm.mapper" />
        <!-- 这个方法过时了, 而是需要sqlSessionFactoryBeanName
            通过工厂的名字来加载 -->
        <!--<property name="sqlSessionFactory" ref="factory"/>-->

        <property name="sqlSessionFactoryBeanName" value="factory"/>
    </bean>

注意: 如果采用的是Mapper代理开发方式进行整合的话,就不需要在mybatis的全局配置文件中再去加载映射文件

完整代码地址

https://github.com/menglanyingfei/SSMLearning/tree/master/mybatis_day04
https://github.com/menglanyingfei/SSMLearning/tree/master/spring-mybatis

阅读更多
版权声明:本文为 梦蓝樱飞 原创文章,可以随意转载,但真诚希望在明确位置注明原文超链接的出处!!! 非常感谢! https://blog.csdn.net/menglanyingfei/article/details/79271407
个人分类: SSM
所属专栏: SSM框架系列学习总结
上一篇SSM框架系列学习总结6之Mybatis中的输入输出映射
下一篇SSM框架系列学习总结8之SpringMVC核心组件介绍
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭