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

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

作者: 梦蓝樱飞2020 | 来源:发表于2018-02-06 15:49 被阅读12次

    Mybatis的延迟加载

    比如在查询订单信息,并且关联查询该订单对应的用户信息。
    注意两个问题:
    1.要是用延迟加载必须使用ResultMap作为输出映射方式
    2.Mybatis默认没有延迟加载, 所以需要在全局配置文件中加入<settings>配置
    在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()操作 那么也会清除二级缓存中的数据
    注意: 二级缓存默认是没有开启的,所以必须配置<settings>中的参数,然后在映射文件中添加<cache/>表示开启二级缓存。因为二级缓存的存储介质不一定是内存可能是是存储在硬盘上面,那么需要我们的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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/vaegzxtx.html