栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java面试题之-Mybatis篇(持续更新)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java面试题之-Mybatis篇(持续更新)

文章目录
    • 1、MyBatis是什么?
    • 2、JDBC编程有哪些缺陷?MyBatis又是如何改进的?
    • 3、MyBatis与Hibernate的区别在哪?
    • 4、MyBatis的优缺点
    • 5、MyBatis的执行流程?
    • 6、#{}和${}的区别
    • 7、如何获取生成的主键?
    • 8、当实体类中的属性名和表中的字段名不一样怎么办?
    • 9、什么是MyBatis的接口绑定?有哪些实现方式?
    • 10、Mybatis接口方法参数绑定有哪几种方式?
    • 11、使用MyBatis的mapper接口调用时有哪些要求?
    • 12、Mybatis中的mapper接口都没有实现类,是如何执行里面的方法的?
    • 13、MyBatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?
    • 14、MyBatis的一级、二级缓存是什么?

1、MyBatis是什么?

MyBatis是一款优秀的持久层框架、一个半ORM(对象关系映射)框架。它支持定制化SQL、存储过程、高级映射、缓存机制,同时MyBatis几乎避免了JDBC代码中手动设置参数以及获取结果集等繁杂操作的过程。

2、JDBC编程有哪些缺陷?MyBatis又是如何改进的?
  • JDBC,Dbutils,JdbcTemplate(spring提供),执行SQL语句的步骤:

  • JDBC编程中频繁创建、释放数据库连接对象,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。

    Mybatis:在mybatis-config.xml中配置数据库连接池,使用连接池管理数据库连接。

  • JDBC编程中 sql 语句写在代码中造成代码不易维护,实际应用场景中sql的变化可能较大,sql变动需要改变java代码。

    Mybatis:将SQL语句配置在XXXXmapper.xml映射文件中,与java代码分离。

  • JDBC编程中向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

    Mybatis:Mybatis中自动将java对象映射至sql语句。

  • JDBC编程中对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

    Mybatis:Mybatis中自动将sql执行结果映射至java对象。

3、MyBatis与Hibernate的区别在哪?

Hibernate:是一个全自动全映射的ORM(Object Relation Mapping)框架,旨在消除SQL,执行如下图:

  • 不方便定制化SQL:如果我们想定制SQL,还需要学习Hibernate提供的HQL。

  • 全映射不太好:大量字段的POJO进行部分映射比较困难,导致数据库性能下降。

    举个例子,假如一张员工表有15个字段,我们只需要查其中的两个字段,如果使用Hibernate将会把所有字段都查询并返回(全映射),如果想定制化,也需要学习HQL。

  • 内部自动生成的SQL:不易做特殊优化。

Mybatis:是一个半自动的轻量级ORM(Object Relation Mapping)框架,执行如下图:

  • 手动编写SQL:方便做SQL语句定制化。
4、MyBatis的优缺点

优点

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

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

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

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

  • 能够与Spring很好的集成。

缺点

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

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

5、MyBatis的执行流程?
  • 1、读取主配置文件:mybatis-config.xml

     InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    
  • 2、通过主配置文件读取到映射配置文件:xxxmapper.xml

  • 3、使用会话工厂的创建器创建会话工厂:SqlSessionFactoryBuilder—>SqlSessionFactory

     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
  • 4、通过会话工厂创建会话对象:SqlSessionFactory—> SqlSession

     SqlSession session = sqlSessionFactory.openSession();
    
  • 5、通过会话对象的getMapper()方法获取mapper接口的代理对象:

     UserMapper userMapper = session.getMapper(UserMapper.class);
    
  • 6、通过代理对象执行mapper中的方法:

     User user=userMapper.selectById(1);
    
  • 7、对返回结果进行封装

6、#{}和${}的区别

先看一下Mybatis的执行流程:

  • #{}是占位符,预编译处理,可以防止SQL注入;

  • ${}是拼接符,字符串替换,没有预编译处理,不能防止SQL注入。

  • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

  • Mybatis在处理${}时,是原值传入,就是把${}替换成变量的值,相当于JDBC中的Statement编译。

  • #{} 的变量替换是在DBMS 中,变量替换后,#{} 对应的变量自动加上单引号;

  • ${} 的变量替换是在 DBMS 外,变量替换后,${} 对应的变量不会加上单引号;

注意:在Mybatis框架下,不能使用预编译的地方是不可以使用#{}传递参数的,只能使用${}传递参数,举例如下:

//查询指定年份的工资表
select * from ${year}_salary where id=#{}
  • 在获取year参数的时候,是不可以使用#{}的,因为不支持预编译。
  • 在获取id参数的时候,是可以使用#{}的,因为支持预编译。
7、如何获取生成的主键?

这个分两种情况:

  • 第一种:支持自增主键的数据库,例如:MySQL
  • 第二种:不支持自增主键的数据库,例如:Oracle

第一种:MySQL

  • useGeneratedKeys:使用自增主键获取主键值策略(默认为false)

  • keyProperty:指定主键对应的属性

    
    	insert into tb_user(name,age,sex) values(#{name},#{age},#{sex})
    
    

第二种:Oracle

  • Oracle不支持自增,Oracle使用序列来模拟自增,每次插入的数据的主键是从序列中拿到的值

  • 使用select * from tb_user_sequences;来查询对应表中的所有序列。

    
    	
    	
    		
    		select USER_SEQ.nextval from dual
    	
    	insert into tb_user(id,name,age,sex) values(#{id},#{name},#{age},#{sex})
    
    
8、当实体类中的属性名和表中的字段名不一样怎么办?

方式一:通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。


        select * from tb_user where id = #{id}
    

10、Mybatis接口方法参数绑定有哪几种方式?

接口方法有单一参数:

  • 如果接口方法是单一参数,xml文件中直接使用#{}获取即可,如下:

    public interface UserMapper{
    	public User selectById(Integer id);
    }
    
    
        select * from tb_user where id = #{param1} and name=#{param2}
    
    
  • 方式二:使用@Param注解来指定封装时使用的key,然后使用#{key}就可以取出map中的值,如下:

    public interface UserMapper{
    	public User selectByIdAndName(@Param("id")Integer id,@Param("name")String name);
    }
    
    
        select * from tb_user where id = #{id} and name=#{name}
    
    
  • 方式四:可以传递一个map,使用这个map来封装要传递的属性。

    public interface UserMapper{
    	public User selectByIdAndName(Map map);
    }
    
    //测试类
    map=new HashMap<>();
    map.put("id",1);
    map.put("name","aismall")
    
    
       ......
    
    

    3、Mapper接口中方法的输入参数类型和映射配置文件中定义的每个sql语句的parameterType的类型相同。

    
    

    4、Mapper接口方法的输出参数类型和映射配置文件中定义的每个sql语句的resultType的类型相同。

    
    
    • 注意下面三点:

    • 1、如果返回的类型是一个集合的话,resultType的类型应该是集合中包含类的类型,例如下面的情形,resultType="com.aismall.pojo.User"

      public List selectByName(String name);
      
    • 2、 如果返回值的类型是一个map的话(返回一行数据),resultType的类型就直接指定为map,例如下面的情形,resultType="map"

      public Map selectById(int id);
      
    • 3、 如果返回值的类型是一个map的话(返回多行数据),resultType的类型应该map中用来封装数据类的类型,但是,还应该在mapper中的方法上指定,谁做为key存在,例如下面的情形,resultType="com.aismall.pojo.User"

      @MapKey("id")// 使用id作为key,也可以使用别的属性最为key
      public Map selectByName(String name);
      
    12、Mybatis中的mapper接口都没有实现类,是如何执行里面的方法的?

    Mybatis中是接口+xml的方式,这个接口我们并没有实现它,可是我们却可以直接调接口的方法操作数据库,其中的原理到底是为什么呢?

    • 先说答案:Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象MapperProxy,代理对象MapperProxy会拦截接口方法调用,转而执行方法对应的sql语句,然后将sql执行结果返回。

    具体分析:
    先看一段代码:

    public void test() throws IOException{
        InputStream inputStream = Resources.getResourceAsSteream("mybatis-config.xml");
        //读取mybatis配置文件创建SqlssionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取对应的mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //执行查询语句并返回结果
        User user = mapper.selectById(1);
    }
    

    我们重点关注:build()方法,openSession()方法,getMapper方法

    • SqlSessionFactory类中的 build()方法:
    //debug进入这个方法,根据传入的参数为inputStream会选择这个重载的build方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    	//声明一个SqlSessionFactory对象
        SqlSessionFactory var5;
        try {
        	//创建一个XMLConfigBuilder对象  创建一个解析XML文件的对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 注意:XMLConfigBuilder类中的parse()方法会返回一个Configuration对象
            // 根据传入的值为一个Configuration对象,执行下面的的build重载方法
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
    
            try {
                inputStream.close();
            } catch (IOException var13) {
            }
    
        }
        //由于DefaultSqlSessionFactory继承了SqlSessionFactory,所以用父类来接收子类的对象
        return var5;
    }
    // 可以接收Configuration对象的重载方法
    public SqlSessionFactory build(Configuration config) {
    	//根据配置返回一个持有Configuration对象的DefaultSqlSessionFactory对象
        return new DefaultSqlSessionFactory(config);
    }
    
    • DefaultSqlSessionFactory类中的openSession()方法
    // 根据传入的参数为空,会选择这个重载的openSession方法
    public SqlSession openSession() {
    	 // 调用这个openSessionFromDataSource方法
         return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
     }
    
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
          Transaction tx = null;
          // 注意最后会返回一个DefaultSqlSession对象
          DefaultSqlSession var8;
          try {
          	  // 获取环境配置
              Environment environment = this.configuration.getEnvironment();
              // 根据环境获取一个创建事务的工厂类
              TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
              // 获取事务:里面设置了事务的隔离级别,是否自动提交
              tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
              // 获得一个执行器
              Executor executor = this.configuration.newExecutor(tx, execType);
              // 将前面设置好的各种配置和事务一起封装到一个DefaultSqlSession对象里面并返回 
              var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
          } catch (Exception var12) {
              this.closeTransaction(tx);
              throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
          } finally {
              ErrorContext.instance().reset();
          }
    
          return var8;
      }
    
    • DefaultSqlSession类中的getMapper方法
    // 返回值类型是形参类型:Mapper类的代理类类型
    public  T getMapper(Class type) {
    	   // 调用Configuration类中的getMapper方法
          return this.configuration.getMapper(type, this);
      }
    
    • Configuration类中的getMapper方法
    // 返回值类型是形参类型:Mapper类的代理类类型
    public  T getMapper(Class type, SqlSession sqlSession) {
         //调用MapperRegistry类中的getMapper方法
         return this.mapperRegistry.getMapper(type, sqlSession);
     }
    
    • MapperRegistry类中的getMapper方法
    // 返回值类型是形参类型:Mapper类的代理类类型
    public  T getMapper(Class type, SqlSession sqlSession) {
    	// 获取一个代理工厂类,根据传入类的类型(Mapper接口类型的)
        MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        //为空则进入
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
            	//调用mapperProxyFactory.newInstance方法,返回一个MapperProxy代理类对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
    

    执行流程图如下:

    13、MyBatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?
    • Mybatis动态sql可以让我们在xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。

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

    • 其执行原理为,使用OGNL(对象导航图语言Object Graph Navigation Language)从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

    14、MyBatis的一级、二级缓存是什么?
    • 一级缓存:会话级别的缓存,同一个SqlSession会话共享。采用 PerpetualCache,HashMap 存储,MyBatis默认打开一级缓存,其存储作用域为当前sqlSession会话对象,当 sqlSession flush 或 close 之后,该 sqlSession 中的所有 Cache 就将清空。可以调用 clearCache();,手动清理缓存。

    • 二级缓存:namespace界别的缓存,同一个namespace中的所有方法共享。默认也是采用 PerpetualCache,HashMap 存储,二级缓存的存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache,默认不打开二级缓存,开启二级缓存之后(在mybatis主配置文件中添加;其次在对应的mapper映射文件中添加 ),对应的实体类需要实现Serializable序列化接口(可用来保存对象的状态)。

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

    • 缓存的查询顺序为:二级——>一级——>数据库

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/299035.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号