zoukankan      html  css  js  c++  java
  • 使用SMM框架开发企业级应用-----延迟加载及缓存

    延迟加载

      什么是延迟加载:  

        MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。

      加载时机:  

        直接加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。

        侵入式延迟加载:执行对主加载对象的查询时,不会执行对关联对象的查询。但 当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。

        深度加载:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。

    一、延迟加载

    1.主对象的加载:

    根本没有延迟的概念,都是直接加载。

    2.关联对象的加载时机:

    01.直接加载:

    访问主对象,关联对象也要加载

    02.侵入式延迟:

    访问主对象,并不加载关联对象

    访问主对象属性的属性的时候,关联对象会被加载

    03.深度延迟

    访问主对象,并不加载关联对象

    访问主对象的属性的时候,关联对象也不会被加载

    访问关联对象或关联对象的属性的时候,才会加载关联对象。

    3.一对多延迟加载代码:

    01.实体类代码:

    package cn.pb.bean;

    import java.util.Set;

    /**
    * 国家的实体类
    */
    public class Country {
    private Integer cId;//国家的编号
    private String cName;//国家的名称

    //关联省会的属性
    private Set<Provincial> provincials;

    public Integer getcId() {
    return cId;
    }

    public void setcId(Integer cId) {
    this.cId = cId;
    }

    public String getcName() {
    return cName;
    }

    public void setcName(String cName) {
    this.cName = cName;
    }

    public Set<Provincial> getProvincials() {
    return provincials;
    }

    public void setProvincials(Set<Provincial> provincials) {
    this.provincials = provincials;
    }


    }


    package cn.pb.bean;

    /**
    * 省会对应的实体类
    */
    public class Provincial {
    private Integer pId; //省会的编号
    private String pName; //省会名称

    public Integer getpId() {
    return pId;
    }
    public void setpId(Integer pId) {
    this.pId = pId;
    }
    public String getpName() {
    return pName;
    }
    public void setpName(String pName) {
    this.pName = pName;
    }
    }

    02.dao层代码:

    public interface CountryDao {
    /**
    * 根据国家id 查询国家的信息 以及国家下面的省会
    */
    Country selectCountryById(Integer id);
    }

    03.mapper.xml代码:

    <mapper namespace="cn.pb.dao.CountryDao">
      <!--01.先根据id查询出国家信息 多条sql的查询 可以使用延迟加载策略-->
    <select id="selectCountryById" resultMap="countryMap">
    select cid,cname from country where cid=#{xxx}
    </select>
    
    
      <!--03.根据国家id 查询出省份信息 -->
        <select id="selectProvincialByCountryId" resultType="Provincial">
    select pid,pname from provincial
    where countryid=#{xxx}
    <!--#{xxx} 对应的就是resultMap中 collection节点下面的column -->
    </select>

      <!--02.国家的映射信息-->
        <resultMap id="countryMap" type="Country">
    <id property="cId" column="cid"/>
    <result property="cName" column="cname"/>
    <!--设置关联的集合属性
    select:需要关联的查询语句
    column: select关联语句中需要的参数 -->
    <collection property="provincials" ofType="Provincials"
    select="selectProvincialByCountryId"
    column="cid"/>
    </resultMap>


    </mapper>

    04.测试代码:

    public class CountryTest {
    CountryDao dao=null;
    SqlSession session=null;
    Logger log= Logger.getLogger(CountryTest.class);

    @Before
    public void before(){

    //获取session
    session= SessionFactoryUtil.getSession();
    //获取执行的类对象
    dao=session.getMapper(CountryDao.class);
    }

    /**
    * 在所有的test测试方法执行之后 都要执行的操作
    */
    @After
    public void after(){
    if(session!=null){
    session.close();
    }
    }

      <!--只输出主加载对象 只会有一条sql语句-->
    @Test
    public void testSelectCountryById(){
    Country country=dao.selectCountryById(1);
    log.debug("根据id查询国家信息"+country);
    }

      
      <!--输出关联对象的信息 会有两条sql语句-->
    @Test
    public void testSelectCountryById(){
    Country country=dao.selectCountryById(1);
    log.debug("根据id查询国家信息"+country.getProvincials());
        }

    }


    4.配置延迟加载:

    在mybatis.xml文件中配置:

    <settings>
    <!-- 全局性地启用或禁用延迟加载。当禁用时,所有关联的配置都会立即加载。 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--当启用后,一个有延迟加载属性的对象的任何一个延迟属性被加载时,该对象
    的所有的属性都会被加载。否则,所有属性都是按需加载。 侵入式延迟 -->
    <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    二、MyBatis缓存

    1.查询缓存的作用:

    查询缓存的使用,主要是为了提供查询访问速度。将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。

    2.关于缓存的说明:

    01.MyBatis查询缓存机制。根据缓存区的作用域与生命周期,可划分为两种:一级缓存和二级缓存。

    02.MyBatis查询缓存的作用域是根据映射文件的namespace划分的,相同的namespace的mapper查询数据放在同一个缓存区域。不同namespace下的数据互不干扰。无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的。

    03.但是一级、二级缓存的不同之处在于,SqlSession一旦关闭,则SqlSession中的数据将不存在,即一级缓存就不复存在。而二级缓存的生命周期与整个应用同步,与SqlSession是否关闭无关。换句话说,一级缓存是在同一线程(同一SqlSession)间共享数据,而二级缓存是在不同线程(不同的SqlSession)间共享数据。

    04. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其生命周期为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

    05. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。

    06. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

    3.一级缓存:

    01.一级缓存存在的证明代码:

    001.dao层代码:

    /**
    * 根据学生的编号查询对应的信息
    * 验证一级缓存的存在
    */
    Student selectStudentById(Integer sId);

    002.mapper.xml代码:

    <mapper namespace="cn.pb.dao.StudentDao">
    <!-- 查询指定学生的信息 验证一级缓存的存在 -->
    <select id="selectStudentById" resultType="Student">
    select sid,sname from stu where sid=#{xxx}
    </select>
    </mapper>

    003.测试代码:

    package cn.pb;


    import cn.pb.bean.Student;
    import cn.pb.dao.StudentDao;
    import cn.pb.util.SessionFactoryUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.log4j.Logger;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    public class StudentTest {
    StudentDao dao;
    SqlSession session;
    Logger logger=Logger.getLogger(StudentDao.class);
    @Before
    public void before(){
    session= SessionFactoryUtil.getSession();
    dao=session.getMapper(StudentDao.class);
    }

    /**
    * 在所有的test测试方法执行之后 都要执行的操作
    */
    @After
    public void after(){
    if(session!=null){
    session.close();
    }
    }


    /**
    * 验证一级缓存的存在
    * myBatis的一级缓存是一直开启的,并且不能关闭!
    */
    @Test
    public void test1(){
    Student student=dao.selectStudentById(1);
    logger.debug("第一次查到的id为1的学生:"+student);
    //再次查询相同的id对象
    Student student2 = dao.selectStudentById(1);
    logger.debug("第2次查到的id为1的学生:"+student2);
    }
    }


    02.一级缓存--从缓存中查找数据的依据:

    缓存的底层实现是一个Map,Map的value是查询结果

    Map的key,即查询依据,使用的ORM架构不同,查询依据是不同的

    MyBatis的查询依据是:Sql的id+SQL语句

    Hibernate的查询依据是:查询结果对象的id

    验证代码:

    a.dao层代码:

    /**
    * 验证mybatis缓存查询的依据!
    */
    Student selectStudentById2(Integer sId);

    b.mapper.xml代码:

    <mapper namespace="cn.pb.dao.StudentDao">

        <!-- 查询指定学生的信息    验证mybatis缓存查询的依据!
    两个查询语句的id不一致,但是sql语句一样-->
    <select id="selectStudentById2" resultType="Student">
    select sid,sname from stu where sid=#{xxx}
    </select>

    </mapper>

    c.测试代码:

    /**
    * 验证查询的依据
    * 两个查询都是查询id为1的学生对象,但是查询语句的id不一致
    */

    @Test
    public void test2() {
    Student student = dao.selectStudentById(1);
    logger.debug("第1次查到的id为1的学生:"+student);
    //再次查询相同的id对象
    Student student2 = dao.selectStudentById2(1);
    logger.debug("第2次查到的id为1的学生:"+student2);
    }

    得到的结论是:
    mybatis的查询依据是 : mapper文件中sql的id + sql语句!
    hibernate底层查询的依据是: 查询对象的id!

    其实缓存的底层是一个map,
    map的key就是查询依据,value是查询的结果!



    03.增、删、改对一级缓存的影响:

    增删改会清空一级缓存:注意:必须使用insert标签,不能使用select,否则实验做不成功

    001.dao层代码:

    /**
    * 验证增删改查对一级缓存的影响!
    */
    void addStudent(Student student);

    002.mapper.xml代码:

    <mapper namespace="cn.pb.dao.StudentDao">
    <!-- 查询指定学生的信息 验证一级缓存的存在 -->
    <select id="selectStudentById" resultType="Student">
    select sid,sname from stu where sid=#{xxx}
    </select>

    <!--新增一个学生-->
    <insert id="addStudent">
    <!--#{sId},#{sName} 对应的是实体类中的属性名 -->
    insert into stu values(#{sId},#{sName})
    </insert>
    </mapper>

    003.测试代码:

    package cn.pb;


    import cn.pb.bean.Student;
    import cn.pb.dao.StudentDao;
    import cn.pb.util.SessionFactoryUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.log4j.Logger;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    public class StudentTest {
    StudentDao dao;
    SqlSession session;
    Logger logger=Logger.getLogger(StudentDao.class);
    @Before
    public void before(){
    session= SessionFactoryUtil.getSession();
    dao=session.getMapper(StudentDao.class);
    }

    /**
    * 在所有的test测试方法执行之后 都要执行的操作
    */
    @After
    public void after(){
    if(session!=null){
    session.close();
    }
    }






    /**
    * 验证增删改对一级缓存的影响
    * 之前是只有一条查询语句!
    * 但是加上新增语句之后发现出现了再次查询!
    *
    * 因为增删改查操作都要清空缓存,把数据同步到数据库,
    * 保证后续的查询得到正确的结果集!
    */
    @Test
    public void test3() {


    Student student = dao.selectStudentById(1);
    logger.debug("新增之前查询到的stuent:"+student);
    dao.addStudent(new Student(55, "新增学生"));
    session.commit();
    //再次查询相同的id对象
    Student student2 = dao.selectStudentById(1);
    logger.debug("新增之后查询到的student:"+student2);
    }

    }

    4.二级缓存:

    01.内置二级缓存

    001.由于MyBatis从缓存中读取数据的依据与SQL的id相关,而非查询出的对象。所以,使用二级缓存的目的,不是在多个查询间共享查询结果(所有查询中只要查询结果中存在该对象,就直接从缓存中读取,这是对查询结果的共享,Hibernate中的缓存就是为了在多个查询间共享查询结果,但MyBatis不是),而是为了防止同一查询(相同的Sql id,相同的sql语句)的反复执行。

    002.MyBatis内置的二级缓存为

    https://my.oschina.net/KingPan/blog/280167

    http://www.mamicode.com/info-detail-890951.html

    http://blog.csdn.net/isea533/article/details/44566257

    http://blog.csdn.net/u010676959/article/details/43953087

    http://blog.csdn.net/xiadi934/article/details/50786293

    02.如何开启二级缓存---三条件

    你cacheEnabled=true,默认值为true

    你得在Mapper文件中,<cache/>

    Entity Implements Serializable

    03.增删改对二级缓存的影响

    增删改也会清空二级缓存

    对于二级缓存的清空实质上是对value清空为null,key依然存在,并非将Entry<k,v>删除

    从DB中进行select查询的条件是:

      1.缓存中根本不存在这个key

      2.存在key对应的Entry,但是value为null

    04.二级缓存的配置

    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

    这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用, 而且返回的对象被认为是只读的

    05.可用的收回策略

    LRU – 最近最少使用的:移除最长时间不被使用的对象。

    FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    默认的是 LRU。

    flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒

    形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

    size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

    readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回 缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。 可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

     

    06.二级缓存的关闭:

       01.局部关闭

      在mapper文件中修改

        <select id="selectStudentById"  useCache="false" resultType="Student">
           增加了useCache="false"  相当于 局部关闭 2级缓存  useCache默认值为true===》把查询放入2级缓存

       02.全局关闭

      在mybatis.xml文件中增加

          <settings>
            <!--全局关闭2级缓存  -->
            <setting name="cacheEnabled" value="false"/>
          </settings>

     

    07.二级缓存的使用原则:

     很少被修改的数据 
     不是很重要的数据,允许出现偶尔并发的数据 
     不会被并发访问的数据
    多个namespace不能操作同一张表 不能在关联关系表上执行增删改操作

     

    08.代码验证mybatis2缓存

    1. dao层代码:
      
    public interface StudentDao {
    /**
    * 验证mybatis2级缓存!
    */
    Student selectStudentById(Integer sId);
    /**
    * 验证增删改查对2级缓存的影响!
    */
    void addStudent(Student student);
    }

    002. mapper.xml代码:
    <mapper namespace="cn.pb.dao.StudentDao">
    <!--配置二级缓存-->
    <cache/>
    <select id="selectStudentById" resultType="Student">
    select sid,sname from stu where sid=#{xxx}
    </select>
    <!-- 新增一个学生 验证对2级缓存的影响 标签中 增加 flushCache="false" 可以设置在新增数据的时候不刷新2级缓存
    但是一级缓存不能配置 也就是 只要是一级缓存的增删改 都会刷新 -->
    <insert id="addStudent">
    <!--#{sId},#{sName} 对应的是实体类中的属性 -->
    insert into stu values (#{sId},#{sName})
    </insert>


    </mapper>

     

    003. 测试代码:
    package cn.pb;

    import cn.pb.bean.Student;
    import cn.pb.dao.StudentDao;
    import cn.pb.util.SessionFactoryUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.log4j.Logger;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    public class TestStudent {
    StudentDao dao;
    SqlSession session;
    Logger logger=Logger.getLogger(TestStudent.class);

    @Before
    public void before(){
    session= SessionFactoryUtil.getSession();
    dao=session.getMapper(StudentDao.class);
    }

    /**
    * 在所有的test测试方法执行之后 都要执行的操作
    */
    @After
    public void after(){
    if(session!=null){
    session.close();
    }
    }



    /**
    * 验证2级缓存
    *
    * 开启内置2级缓存的步骤
    * 01.实体类对象 要实现serializable 序列化接口
    * 02.在mapper文件中 增加 <cache/>节点
    */
    @Test
    public void test1() {
    Student student = dao.selectStudentById(1);
    System.out.println(student);

    session.close(); //关闭了session 一级缓存中的数据肯定清空了

    session = SessionFactoryUtil.getSession(); //再次获取session 查询数据
    dao = session.getMapper(StudentDao.class);
    //这时候不会再有sql语句了 因为2级缓存中存在相同的查询(mapper文件中sql的id)和相同的sql语句
    Student student2 = dao.selectStudentById(1);
    System.out.println(student2);
    }

    /**
    * 验证增删改对2级缓存的影响
    */
    @Test
    public void test2() {
    Student student = dao.selectStudentById(1);
    System.out.println(student);

    session.close(); //关闭了session 一级缓存中的数据肯定清空了

    session = SessionFactoryUtil.getSession(); //再次获取session 查询数据
    dao = session.getMapper(StudentDao.class);
    //新增学生信息 看看对2级缓存的影响
    dao.addStudent(new Student(22,"测试"));

    Student student2 = dao.selectStudentById(1);
    System.out.println(student2);
    }

    }
  • 相关阅读:
    0129 System类 Math类 Arrays类 大数据运算
    0127 基本类型包装类
    'telnet' 不是内部或外部命令,也不是可运行的程序 解决方案
    删除时报org.springframework.dao.DataIntegrityViolationException
    mapper自动识别驼峰配置 spring MVC
    spring Security如何debug源码
    公司tomcat项目启动
    java.util.ConcurrentModificationException: null 异常解决
    @Transactional 学习
    mangoDB初探
  • 原文地址:https://www.cnblogs.com/haohanwuyin/p/11689802.html
Copyright © 2011-2022 走看看