精华内容
下载资源
问答
  • MyBatis-延迟加载与MyBatis缓存(面试题)

    千次阅读 2020-04-05 19:55:00
    MyBatis-延迟加载与MyBatis缓存MyBatis-延迟加载与MyBatis缓存-概念性延迟加载(面试题)1、什么是延迟加载(按需加载)2、延迟加载MyBatis缓存(面试题)1、Cache缓存2、MyBatis缓存分析3、一级缓存4、二级缓存原理开启...

    MyBatis-延迟加载与MyBatis缓存-概念性

    延迟加载(面试题)

    1、什么是延迟加载(按需加载

     resultMap中的association(has a)和collection(has some)标签具有延迟加载的功能。
     延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

    • 设置延迟加载(配置问题使用代码演示)
      Mybatis默认是没开启延迟加载功能的,我们需要手动开启。
    • 需要在mybatis-config.xml文件中,在标签中开启延迟加载功能。
      lazyLoadingEnabled、aggressiveLazyLoading
      在这里插入图片描述
       在最新官方MyBatis文档里,有上面这2个属性,一个是延迟加载,一个是分层加载。
      lazyLoadingEnabled 默认值为false,那么在有级联关系的resultMap里,查询后会加载出所有的级联关系,当然有时候我们并不需要这些所有的时候,我们就可以应用到延迟加载给我们带来的好处了。
      aggressiveLazyLoading默认值是true,这里我称之为分层加载,大概意思是如果它为true,那么当我使用了延迟加载,要么所有级联都不加载,要么如果我加载一个,其他都得加载.
      aggressiveLazyLoading值是false 那么就是按需加载,如果是true表示只要使用一个级联对象,就全部加载!
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 全部配置
      在这里插入图片描述
    • 局部配置
       fetchType是可以注明在association 和 collection里的,选值为eager和lazy,就是为了方便我们结合aggressiveLazyLoading(false)来配合使用的,让延迟加载发挥到极致,即只加载我需要的!
      在这里插入图片描述
      如果全局和局部同时生效,那么就近原则,局部生效!

    2、延迟加载

    在这里插入图片描述
    以下是resultMap对应的POJO

    • Student:
    public class Student {
    
    	private Integer id;
    	private String studentName;
    	private String studentAge;
    
    	private List studentHealthCards;
    	private ParentOfStudent parentOfStudent;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getStudentName() {
    		return studentName;
    	}
    
    	public void setStudentName(String studentName) {
    		this.studentName = studentName;
    	}
    
    	public String getStudentAge() {
    		return studentAge;
    	}
    
    	public void setStudentAge(String studentAge) {
    		this.studentAge = studentAge;
    	}
    
    	public List getStudentHealthCards() {
    		return studentHealthCards;
    	}
    
    	public void setStudentHealthCards(List studentHealthCards) {
    		this.studentHealthCards = studentHealthCards;
    	}
    
    	public ParentOfStudent getParentOfStudent() {
    		return parentOfStudent;
    	}
    
    	public void setParentOfStudent(ParentOfStudent parentOfStudent) {
    		this.parentOfStudent = parentOfStudent;
    	}
    
    	@Override
    	public String toString() {
    		return "Student [id=" + id + ", studentName=" + studentName + ", studentAge=" + studentAge + "]";
    	}
    
    }
    
    • StudentHealthCard:
    public class StudentHealthCard {
    
    	private Integer id;
    	private Integer stu_id;
    	private String name;
    	private String message;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public Integer getStu_id() {
    		return stu_id;
    	}
    
    	public void setStu_id(Integer stu_id) {
    		this.stu_id = stu_id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getMessage() {
    		return message;
    	}
    
    	public void setMessage(String message) {
    		this.message = message;
    	}
    
    	@Override
    	public String toString() {
    		return "StudentHealthCard [id=" + id + ", stu_id=" + stu_id + ", name=" + name + ", message=" + message + "]";
    	}
    }
    
    • ParentOfStudent:
    public class ParentOfStudent {
    
    	private Integer id;
    	private Integer stu_id;
    	private String name;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public Integer getStu_id() {
    		return stu_id;
    	}
    
    	public void setStu_id(Integer stu_id) {
    		this.stu_id = stu_id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		return "ParentOfStudent [id=" + id + ", stu_id=" + stu_id + ", name=" + name + "]";
    	}
    }
    
    • Mapper文件
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.mybatis.mapper.StudentMapper">
    
    	<resultMap type="Student" id="stu">
    		<id property="id" column="id" />
    		<result property="studentName" column="studentName" />
    		<result property="studentAge" column="studentAge" />
    		<association property="parentOfStudent" column="id"
    			select="selectOneParentOfStudent" fetchType="eager">
    		</association>
    		<collection property="studentHealthCards" column="id"
    			ofType="Model.StudentHealthCard" 
    			select="selectOneStudentHealthCard" fetchType="eager">
    		</collection>
    	</resultMap>
    	
    	<select id="selectOneStudent" resultMap="stu"> 
    	    select * from student
    		where id = #{id}		  
    	</select>
    	
    	<select id="selectOneParentOfStudent" resultType="ParentOfStudent"> 
    	    select * from
    		parentofstudent where stu_id = #{id}		  
    	</select>
    	
    	<select id="selectOneStudentHealthCard" resultType="StudentHealthCard"> 
    	    select *
    		from studenthealthcard where stu_id = #{id}		  
    	</select>
    	
    </mapper>
    
    • 测试

    • 情况1:开启延迟加载,默认分层加载,不开启局部加载
      执行语句 Student student = sm.selectOneStudent(1);
      以下是运行结果:
      在这里插入图片描述
      执行语句:Student student = sm.selectOneStudent(1);
      student.getParentOfStudent();
      在这里插入图片描述
      这就是默认分层加载的后果,好的那么现在我把分层加载设置为false

    • 即情况2:开启延迟加载,分层加载false,不适用局部加载
      执行语句 Student student = sm.selectOneStudent(1);
      以下是运行结果:
      在这里插入图片描述
      执行语句:Student student = sm.selectOneStudent(1);
      student.getParentOfStudent();
      在这里插入图片描述
      好了 3条sql变成了2条

    • 情况3:就是使用fetchType的情况下,可以指明即使在延迟加载情况下也可以立即加载某个级联关系!

    MyBatis缓存(面试题)

    1、Cache缓存

    在这里插入图片描述
    缓存中有,先查询缓存。缓存中没有,那么查询数据库。这样的话不用每次都查询数据库。减轻数据库的压力。提高查询率!!!

    第一次查询的时候,由于缓存中没有,那么去查询数据库返回给客户端。同时还会把这个次查询的数据放入缓存。
    第二次查询同样的数据时候,发现缓存中曾经有查询过的数据,那么直接从缓存中读取。不必再次查询数据库,减轻数据库压力!

    2、MyBatis缓存分析

     mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
    在这里插入图片描述
     一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
     二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

    &emsp;Mybatis的缓存,包括一级缓存和二级缓存
     一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
    一级缓存是session级别的,同一个session! 1级缓存是系统自带,不需要手动开启!

     二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是二级缓存区域。二级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。二级缓存中的value,就是查询出的结果对象。
    二级缓存,可以跨session!二级缓存是要配置,然后手动开启!

    一级缓存是默认使用的。
     二级缓存需要手动开启。

    Map<String,Object> key 缓存标志 Value 缓存的数据

    3、一级缓存

    在这里插入图片描述
     第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
     如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
     第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
    Mybatis默认支持一级缓存。

    • 测试1
    @Test
    	public void test1(){
    		Student s1 = mapper.selectOneStudent(1);
    		Student s2 = mapper.selectOneStudent(1);
    		System.out.println(s1==s2);
    	}
    
    • 测试2
    @Test
    	public void test1(){
    		Student s1 = mapper.selectOneStudent(1);
    		
    		//session.commit();
    		//session.clearCache();
    		
    		Student s2 = mapper.selectOneStudent(1);
    		System.out.println(s1==s2);
    	}
    
    • 应用
      正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
      一个service方法中包括很多mapper方法调用。
    service{
            //开始执行时,开启事务,创建SqlSession对象
    
    	    //第一次调用mapper的方法findUserById(1)
            //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    
           
            //方法结束,sqlSession关闭
    }
    

    第一次调用mapper的方法findUserById(1)
    第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。

    4、二级缓存

    原理

    下图是多个sqlSession请求UserMapper的二级缓存图解。
    在这里插入图片描述
    二级缓存是mapper级别的。
    第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
    第二次调用相同namespace下的mapper映射文件(xml)中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
    如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存

    开启二级缓存

    Mybatis默认是没有开启二级缓存
     1.在核心配置文件myBatis-config.xml中加入以下内容(开启二级缓存总开关):
     在settings标签中添加以下内容:
    在这里插入图片描述
    &emsp2.在StudentMapper映射文件中,加入以下内容,开启二级缓存:
    在这里插入图片描述
     3.实现序列化(持久化)
    在这里插入图片描述
    由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
    缓存默认是存入内存中,但是如果需要把缓存对象存入硬盘那么久需要序列化(实体类要实现)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    如果该类存在父类,那么父类也要实现序列化。
    在这里插入图片描述测试1:

    @Test
    	public void test2(){
    		SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
    		SqlSession session1 = factory.openSession();
    		StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
    		Student s1 = mapper1.selectOneStudent(1);
    		System.out.println(s1);
    		session1.close();
    		
    		SqlSession session2 = factory.openSession();
    		StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
    		Student s2 = mapper2.selectOneStudent(1);
    		System.out.println(s2);
    	}
    

    测试2:
    @Test

    public void test2(){
    		SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
    		
    		SqlSession session1 = factory.openSession();
    		StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
    		Student s1 = mapper1.selectOneStudent(1);
    		System.out.println(s1);
    		session1.close();
    		SqlSession session2 = factory.openSession();
    		StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
    		s1.setStudentName("王二小");
    		mapper2.updateStudent(s1);
    		session2.commit();
    		session2.close();
    		
    		SqlSession session3= factory.openSession();
    		StudentMapper mapper3 = session3.getMapper(StudentMapper.class);
    		Student s2 = mapper3.selectOneStudent(1);
    		System.out.println(s2);
    	}
    

    根据SQL分析,确实是清空了二级缓存了

    5、禁用二级缓存

    该statement中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
    在这里插入图片描述

    6、刷新二级缓存

     该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
     如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
     如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 参考资料:Mybatis 官网 本文内容如下: 一、简答 一级缓存 二级缓存 spring 整合 Mybatis 总结 二、代码验证缓存 ...1、验证一级缓存的存在 ...Mybatis缓存有 一级缓存 和 二级缓存。 一级缓存 一...

    参考资料:Mybatis 官网

    本文内容如下:

    一、简答

    • 一级缓存
    • 二级缓存
    • spring 整合 Mybatis 后的缓存
    • 总结

    二、代码验证缓存

    • 1、验证一级缓存的存在
      • 验证作用范围 STATEMENT
      • 验证作用范围 SESSION
    • 2、验证二级缓存的存在
    • 3、Spring整合Mybatis 之后,一级缓存的验证

    一、简答

    Mybatis 的缓存有 一级缓存二级缓存

    一级缓存

    一级缓存在 Mybatis 中默认是开启并生效的。

    一级缓存存在两种作用范围:

    • SESSION(默认)
      在同一个 SqlSession 中多次执行同一个查询,除第一次走数据库,剩下的都走缓存
    • STATEMENT
      每执行完一个 Mapper 中的语句后都会将一级缓存清除。

    二级缓存

    二级缓存在 Mybatis 中默认是不开启。准确的来讲应该是二级缓存的全局配置开关是默认开启的但是想要二级缓存生效,还需要进行配置。

    二级缓存的作用范围是同一个 namespace 下的mapper 映射文件内容。多个 SqlSession 之间可以共享缓存内容。

    spring 整合 Mybatis 后

    Spring 整合 Mybatis 之后,二级缓存照常生效,但是一级缓存有了改变。如果不是在同一个事务中每一次 Mapper 方法的调用,都会生成一个新的 Sqlsession,这时候一级缓存就不会命中。

    总结

    默认情况下,相关信息如下:

    作用域默认是否开启
    一级缓存SESSION(默认) 和 STATEMENT开启
    二级缓存同一个namespace下的mapper映射文件内容关闭

    二、代码实践

    验证一级缓存的存在

    验证作用范围 STATEMENT

    当 一级缓存 的作用域改为 STATEMENT 时,每执行一次 Mapper 中的语句后会将一级缓存清除。

    此时一级缓存相当于摆设。

    step1、设置一级缓存作用范围为 STATEMENT(mybatis-config.xml中)

    <settings>
    	<!-- 调整一级缓存作用域为 STATEMENT -->
    	<setting name="localCacheScope" value="STATEMENT"/>
    </settings>
    

    step2、相关代码

    /**
     * <p> Mybatis 缓存验证测试 </p>
     *
     * @Author WTF名字好难取
     */
    public class DemoTestFromXML {
        public static SqlSession getSqlSession() throws FileNotFoundException {
            //配置文件
            InputStream configFile = new FileInputStream("xxx.mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
            //加载配置文件得到SqlSessionFactory
            return sqlSessionFactory.openSession();
        }
    
        public static void main(String[] args) throws Exception {
            // 一级缓存:STATMENT 验证
            oneLeveCacheVerifySTATEMENT();
        }
    
        /**
         * <p> 一级缓存:STATMENT验证 </p>
         *
         * @throws Exception
         */
        public static void oneLeveCacheVerifySTATEMENT() throws Exception{
            UserInfoMapper mapper = getSqlSession().getMapper(UserInfoMapper.class);
            // 第一次查询
            List<UserInfo> userInfos1 = mapper.selectList();
            // 第二次查询
            List<UserInfo> userInfos2 = mapper.selectList();
        }
    }
    

    step3、控制台打印信息

    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1188e820]
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - ==>  Preparing: SELECT * FROM user_info 
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - ==> Parameters: 
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - <==      Total: 1
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - ==>  Preparing: SELECT * FROM user_info 
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - ==> Parameters: 
    [main] DEBUG c.q.m.m.UserInfoMapper.selectList - <==      Total: 1
    

    从控制台打印的 SQL 能够知道,查询语句执行了两次。

    一级缓存作用范围为STATEMENT时,每一次mapper 接口的调用,缓存都会被刷新,相当于缓存没什么卵用

    验证作用范围 SESSION

    当 一级缓存 的作用范围是 SESSION 时,再同一个 SESSION 中,多次进行同一个查询,只有第一次查询会执行SQL语句,其他相同的SQL查询,走的是一级缓存。

    step1、设置一级缓存作用范围为 SESSION (mybatis-config.xml中)

    一级缓存 默认作用范围为 SESSION ,可以不设置

    <settings>
    	<!-- 调整一级缓存作用域为 SESSION -->
    	<setting name="localCacheScope" value="SESSION"/>
    </settings>
    

    step2、相关代码

    /**
     * <p> Mybatis 缓存验证测试 </p>
     *
     * @Author WTF名字好难取
     */
    public class DemoTestFromXML {
        public static SqlSession getSqlSession() throws FileNotFoundException {
            //配置文件
            InputStream configFile = new FileInputStream("xxx.mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
            //加载配置文件得到SqlSessionFactory
            return sqlSessionFactory.openSession();
        }
    
        public static void main(String[] args) throws Exception {
            // 一级缓存:SESSION 验证
            oneLeveCacheVerifySESSION();
        }
    
        /**
         * <p> 一级缓存:SESSION验证 </p>
         *
         * @throws Exception
         */
        private static void oneLeveCacheVerifySESSION() throws Exception{
            /** 方式一:同一个 SqlSession **/
            UserInfoMapper mapper = getSqlSession().getMapper(UserInfoMapper.class);
            // 第一次查询
            List<UserInfo> way1_info1 = mapper.selectList();
            // 第二次查询
            List<UserInfo> way1_info2 = mapper.selectList();
    
            System.out.println("=====================");
    
            /** 方式二:不同的 SqlSession **/
            UserInfoMapper mapper1 = getSqlSession().getMapper(UserInfoMapper.class);
            UserInfoMapper mapper2 = getSqlSession().getMapper(UserInfoMapper.class);
            List<UserInfo> way2_info1 = mapper1.selectList();
            List<UserInfo> way2_info2 = mapper2.selectList();
        }
    }
    

    step3、控制台打印信息

    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection
    [main] DEBUG o.a.i.d.pooled.PooledDataSource - Created connection 789219251.
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f0a87b3]
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==>  Preparing: select id, name from user_info where id = ? 
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==> Parameters: 1(Integer)
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - <==      Total: 1
    =====================
    [main] DEBUG o.a.i.d.pooled.PooledDataSource - Created connection 1686369710.
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6483f5ae]
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==>  Preparing: select id, name from user_info where id = ? 
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==> Parameters: 1(Integer)
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - <==      Total: 1
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection
    [main] DEBUG o.a.i.d.pooled.PooledDataSource - Created connection 134310351.
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@80169cf]
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==>  Preparing: select id, name from user_info where id = ? 
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==> Parameters: 1(Integer)
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - <==      Total: 1
    

    从控制台打印能够得知:
    在 SESSION 范围,同一 SqlSession 执行多次相同sql语句,除第一次外,其余都会直接命中一级缓存。
    不同 SqlSession 中执行的相同 sql 不会命中缓存。

    验证二级缓存的存在

    小贴士:
    1、缓存只作用于 cache 标签所在的映射文件中的语句。
    2、如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
    话句话说:注解和XML不能同时存在,只能选一种,不然二级缓存不生效。
    3、二级缓存要命中,需要先开启一个 sql 语句,然后关闭该 session ,在建立一个新的 session 还行相同的 sql 此时二级缓存才会命中。

    step1、首先需要确认全局开关是否开启

    默认是开启的。显示的配置如下:

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

    step2、配置开启二级缓存

    在需要开启的 Mapper.xml 文件中追加

    <mapper namespace="xxx.UserInfoMapper">
    	......
    	<!-- 二级缓存开启 -->
      	<cache/>
    	......
    </mapper>
    

    step3、相关代码

    /**
     * <p> Mybatis 二级缓存验证测试 </p>
     *
     * @Author WTF名字好难取
     */
    public class TwoLeveCacheDemoTestFromXML {
        public static SqlSessionFactory getSqlSessionFactory() throws FileNotFoundException {
            //配置文件
            InputStream configFile = new FileInputStream("xxx.mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
            //加载配置文件得到SqlSessionFactory
            return sqlSessionFactory;
        }
    
        public static void main(String[] args) throws Exception {
            // 二级缓存 验证
            twoLeveCacheVerify();
    
        }
    
        /**
         * <p> 二级缓存 验证 </p>
         *
         * @throws Exception
         */
        private static void twoLeveCacheVerify() throws Exception{
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            /** 不同的 SqlSession **/
            SqlSession sqlSession1 = sqlSessionFactory.openSession();
            UserInfoMapper mapper1 = sqlSession1.getMapper(UserInfoMapper.class);
            UserInfo info1 = mapper1.selectByPrimaryKey(1);
            sqlSession1.close();
    
            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            UserInfoMapper mapper2 = sqlSession2.getMapper(UserInfoMapper.class);
            UserInfo info2 = mapper2.selectByPrimaryKey(1);
            sqlSession2.close();
        }
    }
    

    step4、控制台打印信息

    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection
    [main] DEBUG o.a.i.d.pooled.PooledDataSource - Created connection 1375995437.
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5204062d]
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==>  Preparing: select id, name from user_info where id = ? 
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - ==> Parameters: 1(Integer)
    [main] DEBUG c.q.m.m.U.selectByPrimaryKey - <==      Total: 1
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5204062d]
    [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5204062d]
    [main] DEBUG o.a.i.d.pooled.PooledDataSource - Returned connection 1375995437 to pool.
    [main] DEBUG c.q.mybatis.mapper.UserInfoMapper - Cache Hit Ratio [com.qguofeng.mybatis.mapper.UserInfoMapper]: 0.5
    

    从控制台打印内容,验证了二级缓存的作用范围是同一个 namespace 下的mapper 映射文件内容,多个 SqlSession 之间可以共享缓存内容。

    Spring整合Mybatis 之后,一级缓存的验证

    step1、进行 spring 和 Mybatis 的整合(省略)
    step2、相关测试代码

    /**
     * <p> 缓存单元测试 </p>
     *
     * @Author WTF名字好难取
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Application.class)
    public class CacheTest {
        @Autowired
        private UserInfoMapper userInfoMapper;
    
        /**
         * <p> 缓存测试 </p>
         * 二级缓存照常生效
         */
        @Test
        public void cacheTest() throws Exception{
            UserInfo userInfo1 = userInfoMapper.selectByPrimaryKey(1);
            UserInfo userInfo2 = userInfoMapper.selectByPrimaryKey(1);
    
            System.in.read();
        }
    
    
        /**
         * <p> 缓存测试 </p>
         * spring 整合 mybatis 之后 : 每一次 sql 查询都会生成一个 Sqlsession 实例 。
         * 只有在同一个事务中,才会是同一个 SqlSession ,一级缓存才会生效。
         */
        @Test
        @Transactional
        public void cacheTestTransaction() throws Exception{
            UserInfo userInfo1 = userInfoMapper.selectByPrimaryKey(1);
            UserInfo userInfo2 = userInfoMapper.selectByPrimaryKey(1);
    
            System.in.read();
        }
    }
    

    step3、控制台打印
    在这里插入图片描述

    从控制台信息得到,非同一事务每一次 mapper 调用都是新的SqlSession 实例。在同一事务中,一级缓存才会命中

    精彩内容推送,请关注公众号!
    展开全文
  • 一、问题 1.1、环境 电脑环境:Windows 10; 开发工具:IntelliJ IDEA;...如何使用Mybatis框架实现二级缓存? 二、解答 2.1、基本知识; 1.一级缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该S...

    一、问题

    1.1、环境
    电脑环境:Windows 10;
    开发工具:IntelliJ IDEA;
    数据库环境:MySQL 11.0.10;
    JDK环境: Jdk1.8;
    Maven环境:Maven3.5.0;
    1.2、问题
    如何使用Mybatis框架实现二级缓存?

    二、解答

    2.1、基本知识;
    1.一级缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
    2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper ,并且可自定义存储源,如 Ehcache

    2.2、实战
    首先我们搭建一个简单的项目:
    ①、实体类:
    如果要实现二级缓存,必须要实现序列化,即实现实现Serializable接口,为什么要实现序列化接口?我的理解是,Mybatis如何能判断你这个User实体类的查询和另一个User实体类查询时不一样的?序列化就像身份证一样,是标记一个对象是否相同的一个唯一验证码;并且,我们再调用外部接口或者传输过程中,都需要序列化,也是为了将一个立体的实体类,在保证对象结构和对象数据的基础上,做一个线性,二进制化的一个处理;就像一个方阵要快速通过一个窄桥,宽度只能满足一个人通过,大家按照一定的规则(从前到后,从左到右)依次快速通过,通过窄桥后,再恢复为原来的方阵(反序列化);总而言之就是:唯一标记和快速传输;

    package com.example.student.domain;
    
    
    import java.io.Serializable;
    
    public class User  implements Serializable{
        private int id;
        private int age;
        private String userName;
        private String userAddress;
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getUserAddress() {
            return userAddress;
        }
        public void setUserAddress(String userAddress) {
            this.userAddress = userAddress;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString()
        {
            final StringBuilder builder = new StringBuilder();
            builder.append("id=").append(id).append(",");
            builder.append("age=").append(age).append(",");
            builder.append("userName=").append(userName).append(",");
            builder.append("userAddress=").append(userAddress).append(".");
    
            return builder.toString();
    
        }
    }
    
    
    

    ②、mapper.xml文件
    mapper.xml文件很简单,这里我们再select标签上新加了一个cache标签,用来配置缓存配置;eviction表示缓存回收的策略;flushInterval表示清理间隔,满了多少条之后清空缓存;这里如果设置1,则第2次查询都会新开一个sqlSession;你也可以设置大一些;例如60000等;

    映射语句文件中的所有select 语句将会被缓存。
    映射语句文件中的所有insert,update 和delete 语句会刷新缓存。
    缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
    根据时间表(比如no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
    缓存会存储列表集合或对象(无论查询方法返回什么)的1024 个引用。
    缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 这里namespace必须是PostsMapper接口的路径,不然要运行的时候要报错 “is not known to the MapperRegistry”-->
    <mapper namespace="com.example.student.mapper.UserMapper">
        <!-- 这儿的resultType是配置在mybatis-config.xml中得别名 -->
    
        <!--<cache/>-->
    
    <!--  回收策略:
        LRU – 最近最少使用的:移除最长时间不被使用的对象。
        FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。-->
        <!--<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />-->
        <select id="getUserById" parameterType="int" resultType="com.example.student.domain.User">
            select * from user where id=#{id}
        </select>
    </mapper>
    

    ③、mapper.java类

    package com.example.student.mapper;
    
    import com.example.student.domain.User;
    
    //通过面向接口的mybatis编程方式,需要保证方法名和配置文件中的id名称一致
    public interface UserMapper {
        public User getUserById(int id);
    }
    

    ④、测试类
    注意导入的包的版本和路径;

    import com.alibaba.fastjson.JSON;
    import com.example.student.domain.User;
    import com.example.student.mapper.UserMapper;
    import junit.framework.TestCase;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class TestClass extends TestCase {
    
            Logger logger = LoggerFactory.getLogger(TestClass.class);
            private static SqlSessionFactory sqlSessionFactoy;
    
            static{
    //            获取mybatis配置文件的方式:
    //            String resource = "org/mybatis/builder/mybatis-config.xml";
    //            InputStream inputStream = Resources.getResourceAsStream(resource);
    //            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //            SqlSessionFactory factory = builder.build(inputStream);
                String resource = "mybatis.config.xml";
                try {
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    sqlSessionFactoy = new SqlSessionFactoryBuilder().build(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Test
            public void testgetUserById() {
    
    //            默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
    //            1.会开启一个事务(并且不自动提交)。
    //            2.将从由当前环境配置的 DataSource 实例中获取 Connection 对象。
    //            3.事务隔离级别将会使用驱动或数据源的默认设置。
    //            4.预处理语句不会被复用,也不会批量处理更新。
    
                //这里开启两个SqlSession来模拟两次请求;
                SqlSession sqlSession = sqlSessionFactoy.openSession();
                SqlSession sqlSession2 = sqlSessionFactoy.openSession();
                try{
                    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    
                    User u1=mapper.getUserById(1);
    //                事务控制方法;
    //                void commit()
    //                void commit(boolean force)
    //                void rollback()
    //                void rollback(boolean force)
                    sqlSession.commit();
                    logger.debug(JSON.toJSONString(u1));
    
                    User u2=mapper2.getUserById(1);
                    sqlSession2.commit();
                    logger.debug(JSON.toJSONString(u2));
    
                } finally {
                    sqlSession.close();
                }
            }
        }
    

    ⑤、建表语句(MySQL):

    drop table if exists user;
    
    
    create table user(
        id int primary key auto_increment,
        username varchar(50) unique,
        password varchar(100),
        useraddress varchar(50)
    );
    
    select * from user ;
    insert into user values(1,"123","123","beijing");
    insert into user values(2,"46","456","shanghai");
    

    ⑥配置文件
    配置mybatis.config.xml文件;这里要放开settings标签,让缓存生效;你可以对比放开前后日志打印的情况,是否开启了两个sqlSession;还是调用2次查询,但是只开启依次sqlSession;下面还要注意一点,现在mysql的Driver废弃了com.mysql.jdbc.Driver,最好改用com.mysql.cj.jdbc.Driver;并且url后面设置服务器的时区serverTimezone;而且url里【&】符号要用转移字符代替,即【&amp;】;别忘了修改本地MySQL数据库的连接信息;关联的实体类的路径,关联的mapper.xml的路径;一般mapper.xml文件都放在resources下面;我这里放在resources下面的mapper文件夹里的;

    <?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="cacheEnabled" value="true"/>
        </settings>-->
        <typeAliases>
            <typeAlias alias="User" type="com.example.student.domain.User" />
        </typeAliases>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC" />
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/sys?characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8" />
                    <property name="username" value="root" />
                    <property name="password" value="123456" />
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="mapper/UserMapper.xml" />
        </mappers>
    
    </configuration>
    
    

    测试:

    11:45:00.844 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
    11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
    11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
    11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
    11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.11:45:01.099 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
    11:45:01.105 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
    11:45:02.716 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1496949625.
    11:45:02.716 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
    11:45:02.718 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
    11:45:02.746 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
    11:45:02.769 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
    11:45:02.831 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
    11:45:02.831 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
    11:45:02.831 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
    11:45:02.841 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1665197552.
    11:45:02.841 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6340e5f0]
    11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
    11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
    11:45:02.843 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
    11:45:02.843 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
    11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
    11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
    11:45:02.843 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1496949625 to pool.
    
    Process finished with exit code 0
    

    截图:
    在这里插入图片描述

    如上图所示的报文里,就开启了请求查询的两个事务;

    本篇说明内容完毕;

    三、总结

    1、实体类序列化;
    2、mybatis.config.xml(或者你自己起的别的名称)配置文件里导入settings标签,配置setting标签,属性name为cacheEnabled,value值为true;

        <!--必须要配置可缓存-->
        <settings>
            <setting name="cacheEnabled" value="true"/>
        </settings>
    

    3、具体的mapper.xml文件标签上配置cache标签;配置属性eviction为FIFO(先入先出),就是优先移除最早的缓存;flushInterval间隔可以自定义设置,设置为1则每次查询过后都会清空缓存;readOnly看你业务是否需要只读与否;

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

    参考博客:
    1、JAVABEAN为什么需要序列化
    2、mybatis 介绍cache和使用cache
    3、mybatis 缓存(cache)的使用
    4、mybatis入门教程(九)------mybatis缓存
    5、mybatis中文文档

    欢迎关注我的
    CSDN博客: https://blog.csdn.net/River_Continent
    微信公众号:幕桥社区
    在这里插入图片描述
    知乎:张牧野, https://www.zhihu.com/people/zhang-mu-ye-37-76/activities
    简书: https://www.jianshu.com/u/02c0096cbfd3

    展开全文
  • Mybatis常见面试题总结

    万次阅读 多人点赞 2018-07-09 21:29:01
    1、什么是Mybatis? (1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态...

    1、什么是Mybatis?

    (1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。

    (2)作为一个半ORM框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

    Mybatis是半自动ORM映射工具,是因为在查询关联对象或关联集合对象时,需要手动编写sql来完成。不像Hibernate这种全自动ORM映射工具,Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取。

    (3)通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

    (4)由于MyBatis专注于SQL本身,灵活度高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目。

    2、Mybaits的优缺点:

    (1)优点:

    ① 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。

    ② 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;

    ③ 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

    ④ 能够与Spring很好的集成;

    ⑤ 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

    (2)缺点:

    ① SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

    ② SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

    3、#{}和${}的区别是什么?

    ${}是字符串替换,#{}是预处理;

    Mybatis在处理${}时,就是把${}直接替换成变量的值。而Mybatis在处理#{}时,会对sql语句进行预处理,将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

    使用#{}可以有效的防止SQL注入,提高系统安全性。

     

    4、通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

    (详细的工作原理请参考这篇文章:Mybatis中 Dao接口和XML文件的SQL如何建立关联_张维鹏的博客-CSDN博客_dao层接口与xml文件怎么关联

    Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。

    Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。

    Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。

    当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个SQL标签,比如、、、标签,都会被解析为一个MapperStatement对象。

    举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。

    5、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

    不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;

    原因就是namespace+id是作为Map的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

    备注:在旧版本的Mybatis中,namespace是可选的,不过新版本的namespace已经是必须的了。

     

    6、Mybatis是如何进行分页的?分页插件的原理是什么?

            Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

           分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

    7、简述Mybatis的插件运行原理,以及如何编写一个插件。

    答:Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

    编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件。

    8、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

    Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

    延迟加载的基本原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。

    当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

     

     9、Mybatis的一级、二级缓存:

    (1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

    (2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

    (3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新

    10、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

    第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。

    第二种是使用sql列的别名功能,将列的别名书写为对象属性名。

    有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

     

    11、Mybatis动态sql有什么用?执行原理?有哪些动态sql?

    Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断 并动态拼接sql的功能。

    Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。

    12、Xml映射文件中,除了常见的select|insert|updae|delete标签外,还有哪些标签?

    <resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态sql的9个标签 trim | where | set | foreach | if | choose | when | otherwise | bind ,其中 <sql> 为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

    13、使用MyBatis的mapper接口调用时有哪些要求?

    •  Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
    •  Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
    •  Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
    •  Mapper.xml文件中的namespace即是mapper接口的类路径。

    14、 模糊查询like语句该怎么写?

    第1种:在Java代码中添加sql通配符。

        string wildcardname = “%smi%”;
        list<name> names = mapper.selectlike(wildcardname);
     
        <select id=”selectlike”>
         select * from foo where bar like #{value}
        </select>

    第2种:在sql语句中拼接通配符,会引起sql注入

        string wildcardname = “smi”;
        list<name> names = mapper.selectlike(wildcardname);
    
        <select id=”selectlike”>
             select * from foo where bar like "%"${value}"%"
        </select>

    15、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

    第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

        <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
           select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
        </select>

    第2种: 通过来映射字段名和实体类属性名的一一对应的关系。

     <select id="getOrder" parameterType="int" resultMap="orderresultmap">
            select * from orders where order_id=#{id}
        </select>
    
       <resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
            <!–用id属性来映射主键字段–>
            <id property=”id” column=”order_id”>
    
            <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
            <result property = “orderno” column =”order_no”/>
            <result property=”price” column=”order_price” />
        </reslutMap>

    16、如何获取自动生成的(主)键值?

    insert 方法总是返回一个int值 ,这个值代表的是插入的行数。

    如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。

    <insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
         insert into names (name) values (#{name})
    </insert>
        name name = new name();
        name.setname(“fred”);
    
        int rows = mapper.insertname(name);
        // 完成后,id已经被设置到对象中
        system.out.println(“rows inserted = ” + rows);
        system.out.println(“generated key value = ” + name.getid());

    17、在mapper中如何传递多个参数?

    (1)第一种:
    //DAO层的函数
    Public UserselectUser(String name,String area);  
    //对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
    <select id="selectUser"resultMap="BaseResultMap">  
        select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
    </select>  
    
    (2)第二种: 使用 @param 注解:
    public interface usermapper {
       user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
    }
    然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
    <select id=”selectuser” resulttype=”user”>
             select id, username, hashedpassword
             from some_table
             where username = #{username}
             and hashedpassword = #{hashedpassword}
    </select>
    
    (3)第三种:多个参数封装成map
    try{
    //映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
    //由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数
    Map<String, Object> map = new HashMap();
         map.put("start", start);
         map.put("end", end);
         return sqlSession.selectList("StudentID.pagination", map);
     }catch(Exception e){
         e.printStackTrace();
         sqlSession.rollback();
        throw e; }
    finally{
     MybatisUtil.closeSqlSession();
     }

    18、 一对一、一对多的关联查询 ? 

    <mapper namespace="com.lcb.mapping.userMapper">  
        <!--association  一对一关联查询 -->  
        <select id="getClass" parameterType="int" resultMap="ClassesResultMap">  
            select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}  
        </select>  
    
        <resultMap type="com.lcb.user.Classes" id="ClassesResultMap">  
            <!-- 实体类的字段名和数据表的字段名映射 -->  
            <id property="id" column="c_id"/>  
            <result property="name" column="c_name"/>  
            <association property="teacher" javaType="com.lcb.user.Teacher">  
                <id property="id" column="t_id"/>  
                <result property="name" column="t_name"/>  
            </association>  
        </resultMap>  
    
     
        <!--collection  一对多关联查询 -->  
        <select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">  
            select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}  
        </select>  
    
        <resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">  
            <id property="id" column="c_id"/>  
            <result property="name" column="c_name"/>  
            <association property="teacher" javaType="com.lcb.user.Teacher">  
                <id property="id" column="t_id"/>  
                <result property="name" column="t_name"/>  
            </association>  
    
            <collection property="student" ofType="com.lcb.user.Student">  
                <id property="id" column="s_id"/>  
                <result property="name" column="s_name"/>  
            </collection>  
        </resultMap>  
    </mapper> 

    19、MyBatis实现一对一有几种方式?具体怎么操作的?

    有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;

    嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。

    20、MyBatis实现一对多有几种方式,怎么操作的?

            有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。

    21、Mapper编写有哪几种方式?

    第一种:接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。

    (1)在sqlMapConfig.xml中配置mapper.xml的位置:

    <mappers>
            <mapper resource="mapper.xml 文件的地址" />
            <mapper resource="mapper.xml 文件的地址" />
    </mappers>

    (2)定义mapper接口:

    (3)实现类集成SqlSessionDaoSupport:mapper方法中可以this.getSqlSession()进行数据增删改查。

    (4)spring 配置:

    <bean id="对象ID" class="mapper 接口的实现">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

     第二种:使用org.mybatis.spring.mapper.MapperFactoryBean:

    (1)在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置

    <mappers>
            <mapper resource="mapper.xml 文件的地址" />
            <mapper resource="mapper.xml 文件的地址" />
    </mappers>

    (2)定义mapper接口:

    ① mapper.xml中的namespace为mapper接口的地址

    ② mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致

    ③ Spring中定义:

    <bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="mapper 接口地址" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

    第三种:使用mapper扫描器:

    (1)mapper.xml文件编写:

    mapper.xml中的namespace为mapper接口的地址;

    mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;

    如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。 

    (2)定义mapper接口:

    注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录

    (3)配置mapper扫描器:

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="mapper接口包地址" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    (4)使用扫描器后从spring容器中获取mapper的实现对象。

    22、什么是MyBatis的接口绑定?有哪些实现方式?

    接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。

    接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。

    23、MyBatis与Hibernate有哪些不同?

    (1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。

    (2)Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 

    (3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。


    相关阅读:

    Spring常见面试题总结

    SpringMVC常见面试题总结

    Mybatis常见面试题总结

    MySQL常见面试题总结

    Redis常见面试题总结

    RabbitMQ消息队列常见面试题总结

    ElasticSearch搜索引擎常见面试题总结

    计算机网络常见面试题总结

    操作系统常见面试题总结

    Java基础、集合、多线程常见面试题总结

    Java虚拟机常见面试题总结

     Java常见设计模式总结

    海量数据处理的方法总结

    展开全文
  • 客观面试题--15.Mybatis缓存机制?

    千次阅读 2018-06-01 09:00:09
    mybatis的查询缓存分为一级缓存和二级缓存,一级缓存是SqlSession级别的缓存,二级缓存时mapper级别的缓存,二级缓存是多个SqlSession共享的。mybatis通过缓存机制减轻数据压力,提高数据库性能。一级缓存mybatis...
  • MyBatis面试题(2020最新版)

    万次阅读 多人点赞 2019-09-24 16:40:33
    整理好的MyBatis面试题库,史上最全的MyBatis面试题MyBatis面试宝典,特此分享给大家 MyBatis 介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC ...
  • MyBatis 缓存机制 前言 缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。 缓存体系结构: ...
  • 23、Mybatis的一级、二级缓存: 24、什么是MyBatis的接口绑定?有哪些实现方式? 25、使用MyBatis的mapper接口调用时有哪些要求? 26、Mapper编写有哪几种方式? 27、简述Mybatis的插件运行原理,以及如何编写一个...
  • 下面分享24道MyBatis高频面试题解析 1、什么是Mybatis? 1.Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等...
  • 7. 一级、二级缓存 8. Mybatis 是否支持延迟加载?如果支持,它的实现原理是什 么? 9. Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 标 签的内容,请问,B 标签能否定义在 A 标签的后面,还是说 必须...
  • 2021年MyBatis面试题30道

    千次阅读 多人点赞 2021-03-25 18:25:25
    文章目录前言面试题系列文章传送门MyBatis面试题内容1. 模糊查询like语句该怎么写?2. MyBatis 框架适用场合?3. MyBatis是如何进行分页的?分页插件的原理是什么?4. Dao 接口里的方法,参数不同时,方法能重载吗?5...
  • 什么是数据持久化? 数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。例如,文件的存储、数据的读取等都...MyBatis框架是一个开源的数据持久层框架。 它的内部封装了...
  • 答:MyBatis缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有,二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射...
  • MyBatis常见面试题

    千次阅读 多人点赞 2021-04-26 20:37:46
    2、MyBatis缓存是什么,它的原理呢 3、#{ } 和 ${ } 的区别是什么 4、什么是MyBatis的接口绑定?有哪些实现方式? 5、MyBatis是如何将SQL的执行结果封装为目标对象并返回的?都有哪些映射形式? 6、MyBatis中...
  • mybatismybatis面试题

    万次阅读 2018-09-06 20:41:06
    mybatis的基本工作流程 1.读取配置文件,配置文件包含数据库连接信息和Mapper映射文件或者Mapper包路径。 2.有了这些信息就能创建SqlSessionFactory,SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来...
  • Mybatis常见面试题总结及答案

    千次阅读 2020-04-18 23:57:18
    Java面试笔试面经、Java技术每天学习一点Java面试关注不迷路作者:a745233700来源:https://blog.csdn.net/a745233700/article/de...
  • 就开发速度而言,Hibernate的真正掌握要比Mybatis来得难些。Mybatis框架相对简单很容易上手,但也相对简陋些。个人觉得要用好Mybatis还是首先要先理解好Hibernate。 比起两者的开发速度,不仅仅要考虑到两者的特性...
  • 面试官都会问的Mybatis面试题,你会这样回答吗? 一、概述 面试,难还是不难?取决于面试者的底蕴(气场+技能)、心态和认知及沟通技巧。面试其实可以理解为一场聊天和谈判,在这过程中有心理、思想上的碰撞和...
  • 2021Mybatis面试题(持续更新中)

    千次阅读 2021-07-15 09:25:42
    1.什么是mybatis mybatis是一个半orm的框架,他底层封装了jdbc.在使用的过程中,我们只需要关注sql本身,我们不需要关系数据库的加载驱动,创建连接和创建statement,极大的缩减了我们的代码量. mybats可以使用注解或...
  • Mybatis面试题整理

    2020-04-25 15:12:38
    1、什么是Mybatis? (1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态...
  • 今天,写下mybatis常见面试题总结。 1:mybatis中#和$的区别? #可以有效的防止sql注入,$不能防止sql注入;#预编译是采用占位符的方式?,$则是字符串值替换。 2:mybatis中resultType和resultMap的区别? ...
  • Mybatis基础面试题

    2020-04-07 19:54:08
    自用复习的Mybatis面试题 1/34 填空题 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复? 不同的Xml映射文件,如果配置了namespace,那么id____;如果没有配置 namespace,那么id____; 正确答案: 可以...
  • 关于mybatis面试题总结

    千次阅读 2020-04-06 09:23:15
    --延时加载:按需加载 resultMap中的association(has a)和collection(has some)标签具有延时加载的功能 在关联查询时利用它 aggressiveLoading:分层加载 lazyLoadingEnabled(==true)、...二级缓存多个sqlSessin
  • 文章目录1、什么是Mybatis?2、Mybaits的优点?3、MyBatis框架的缺点?4、MyBatis框架适用场合5、MyBatis与Hibernate有哪些不同?6、#{} 和${}的区别是什么?7、当实体类中的属性名和表中的字段名不一样,怎么办?8...
  • mybatis 面试题

    千次阅读 多人点赞 2017-08-08 16:08:19
    mybatis 面试题
  • MyBatis面试题总结

    万次阅读 多人点赞 2020-09-21 21:46:52
    啃下MyBatis源码 - MyBatis面试题总结 1.概念/使用方法向的问题 1.1 什么是Mybatis? 1.2为什么说Mybatis是半ORM框架?/与Hibernate有哪些不同? 1.3Mybaits的优点? 1.4MyBatis框架的缺点? 1.5#{}和${}的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,647
精华内容 7,458
关键字:

mybatis缓存面试题