MyBatis高频面试题

234 阅读7分钟

1、mybatis的特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的ORM字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

2、MyBatis 的 #{} 和 ${} 的区别?

  1. #{} 是预编译处理,${}是字符串替换。
  2. Mybatis处理#{}时,会将sql中的#{}转换为?号,然后使用PreparedStatement的set方法来赋值;MyBatis在处理${}时,就是把{}替换成变量的值。
  3. 使用#{}能有效的预防SQL注入,提高系统的安全性。
  • ${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc. Driver。
  • #{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。

在XML文件中,#{}和${}是两种不同的参数占位符,常见于MyBatis等框架中的SQL语句的参数绑定。

#{}和${}的区别

  • #{}:参数占位符,表示使用预编译语句,不仅可以防止SQL注入问题,还可以将传入参数自动转义,保证SQL语句的安全性。当使用#{}对参数进行占位时,MyBatis会使用预编译语句执行SQL语句。
  • ${}:字符串替换,表示直接将参数的值转换成字符串拼接到SQL语句中,不能防止SQL注入问题,存在SQL注入风险。当使用${}对参数进行占位时,MyBatis将直接将参数的值替换占位符,形成的SQL语句是由参数值拼接的,存在SQL注入风险。

MyBatis中#{}和${}的区别

  • #{}:MyBatis中的参数占位符,使用预编译语句执行SQL语句。如果是字符串类型或字符数组类型的参数,默认用#{value}来引用其中的值,值传入后会自动进行转义,可以在防止SQL注入攻击方面起到一定的作用。
  • ${}:MyBatis中字符串替换的方式,将传入的参数值直接拼接到SQL语句中。如果使用${}在SQL语句中引用参数时,引用的是参数名,而非参数类型等。因此,一般在Map或POJO对象中配置SQL参数,可能会使用${}方式引用参数

总的来说,#{}方式能够防止SQL注入等问题,更安全可靠,而${}方式不会起到防止SQL注入攻击的作用,更易受到攻击。根据具体业务场景及安全要求,使用不同的参数占位符方式。

3、Mybatis中的Mapper接口和XML文件里的SQL是如何建立关系的?

一、解析XML

首先,Mybatis在初始化SqlSessionFactoryBean的时候,找到mapperLocations路径去解析里面所有的XML文件,这里我们重点关注两部分。

1、创建SqlSource Mybatis会把每个SQL标签封装成SqlSource对象。然后根据SQL语句的不同,又分为动态SQL和静态SQL。其中,静态SQL包含一段String类型的sql语句;而动态SQL则是由一个个SqlNode组成。

2、创建MappedStatement XML文件中的每一个SQL标签就对应一个MappedStatement对象,这里面有两个属性很重要。

  • id 全限定类名+方法名组成的ID。
  • sqlSource 当前SQL标签对应的SqlSource对象。

创建完MappedStatement对象,将它缓存到Configuration#mappedStatements中。 Configuration对象,我们知道它就是Mybatis中的大管家,基本所有的配置信息都维护在这里。把所有的XML都解析完成之后,Configuration就包含了所有的SQL信息。

到目前为止,XML就解析完成了。看到上面的图示,聪明如你,也许就大概知道了。当我们执行Mybatis方法的时候,就通过全限定类名+方法名找到MappedStatement对象,然后解析里面的SQL内容,执行即可。

二、Dao接口代理

我们的Dao接口并没有实现类,那么,我们在调用它的时候,它是怎样最终执行到我们的SQL语句的呢?

首先,我们在Spring配置文件中,一般会这样配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
    <property name="basePackage" value="com.viewscenes.netsupervisor.dao" /> 
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> 
</bean>

或者你的项目是基于SpringBoot的,那么肯定也见过这种:MapperScan("com.xxx.dao")

它们的作用是一样的。将包路径下的所有类注册到Spring Bean中,并且将它们的beanClass设置为MapperFactoryBean

有意思的是,MapperFactoryBean实现了FactoryBean接口,俗称工厂Bean。那么,当我们通过@Autowired注入这个Dao接口的时候,返回的对象就是MapperFactoryBean这个工厂Bean中的getObject()方法对象。

那么,这个方法干了些什么呢?

简单来说,它就是通过JDK动态代理,返回了一个Dao接口的代理对象,这个代理对象的处理器是MapperProxy对象。所以,我们通过@Autowired注入Dao接口的时候,注入的就是这个代理对象,我们调用到Dao接口的方法时,则会调用到MapperProxy对象的invoke方法。

对这块内容不太能理解的朋友,可以先看看Spring中的FactoryBean 和 JDK动态代理相关知识。

曾经有个朋友问过这样一个问题:对于有实现的dao接口,mapper还会用代理么?

答案是肯定的,只要你配置了MapperScan,它就会去扫描,然后生成代理。但是,如果你的dao接口有实现类,并且这个实现类也是一个Spring Bean,那就要看你在Autowired的时候,去注入哪一个了。

具体什么意思呢?我们来到一个例子。如果我们给userDao搞一个实现类,并且把它注册到Spring。

@Component
public class UserDaoImpl implements UserDao{
    public List<User> getUserList(Map\<String,Object> map){
        return new ArrayList<User>();
    }
}

然后我们在Service方法中,注入这个userDao。猜猜会发生什么?

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userDao1;

    public List<User> getUserList(Map<String,Object> map) {
        return userDao1.getUserList(map);
    }
}

也许你已经猜到了,是的,它会启动报错。因为在注入的时候,找到了两个UserMapper的实例对象。日志是这样的:

No qualifying bean of type \[com.viewscenes.netsupervisor.dao.UserDao] is defined: expected single matching bean but found 2: userDaoImpl,userDao

当然了,也许我们的命名不太规范。其实我们通过名字注入就可以了,像这样:@Autowired UserMapper userDao; 或者 @Autowired UserMapper userDaoImpl;再或者给你其中一个Bean加上@Primary注解。

三、执行

如上所述,当我们调用Dao接口方法的时候,实际调用到代理对象的invoke方法。 在这里,实际上调用的就是SqlSession里面的东西了。

public class DefaultSqlSession implements SqlSession {

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms,
                wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        }
    }
}

看到以上代码,说明我们想的不错。它就是通过statement全限定类名+方法名拿到MappedStatement 对象,然后通过执行器Executor去执行具体SQL并返回。

四、总结

到这里,再回到开头我们提到的问题,也许你能更好的回答。同时笔者觉得,这道题目,如果你覆盖到以下几个关键词,面试官可能会觉得很满意。

  • SqlSource以及动态标签SqlNode
  • MappedStatement对象
  • Spring 工厂Bean 以及动态代理
  • SqlSession以及执行器

那么,针对第二个问题:如果有两个XML文件和这个Dao建立关系,岂不是冲突了? 答案也是显而易见,不管有几个XML和Dao建立关系,只要保证namespace+id唯一即可。

转载自:Mybatis中的Mapper接口和XML文件里的SQL是如何建立关系的?

4、mybatis用过哪些标签

5、MyBatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?

MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。MyBatis 提供了 9 种动态 sql 标签:

  • <if></if>
  • <where></where>(trim,set)
  • <choose></choose>(when, otherwise)
  • <foreach></foreach>
  • <bind/>

6、mybatis实现分页的几种方式

关于 MyBatis 动态 SQL 的详细介绍,请看这篇文章:java - Mybatis系列全解(八):Mybatis的9大动态SQL标签你知道几个?

关于这些动态 SQL 的具体使用方法,请看这篇文章:Mybatis【13】-- Mybatis动态sql标签怎么使用?

7、Mybatis执行流程?

考察点:理解了各个组件的关系、Sql的执行过程(参数映射、sql解析、执行和结果处理)

image.png

  1. 读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
  2. 构造会话工厂SqlSessionFactory
  3. 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
  4. 操作数据库的接口,Executor执行器,同时负责查询缓存的维护
  5. Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
  6. 输入参数映射
  7. 输出结果映射