一文搞定 MyBatis,经典问题集锦

1、啥是 MyBatis?

MyBatis 是一个半自动 ORM 持久层框架,她内部对 JDBC 做了封装,并使用 XML 或注解来配置和映射数据库信息,开发人员只需要关注 SQL 语句本身,避免了处理繁琐的数据库映射过程,使 Java 代码和 SQL 完全分离,有利于分层开发,提高开发效率和灵活性。

2、MyBatis 的优缺点和适用场景?

1)优点

  • 基于SQL语句编程,非常灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除SQL与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
  • 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
  • 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
  • 能够与Spring很好的集成;
  • 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

2)缺点

  • SQL语句的编写工作量较大,尤其当字段多、关联表多时。
  • 不支持方言,数据库移植性差。
  • SQL存在XML文件中,调试不方便。
  • 对动态SQL的支持不是很好,提供的标签过于简单。
  • 由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。

3)适用场景

MyBatis 解耦了 SQL 和 程序代码,适合业务逻辑相对复杂或者变动频繁的场景,可以使开发人员专注于 SQL 逻辑的修改。目前,在互联网、金融等项目中,MyBatis 是不错的选择。

3、MyBatis 与 Hibernate 的区别?

  • Hibernate 是全自动 ORM框架,而 MyBatis是一个 半自动 ORM 框架,MyBatis 需要程序员自己编写 SQL 语句。
  • MyBatis 直接编写原生态 SQL,可以严格控制 SQL执行性能,灵活度高,适合需求变化频繁的项目,但是对数据库的依赖较大,移植性差。Hibernate 的灵活度相对较低,但是做到了数据库的无关性,移植性较好。

总结:MyBatis 半自动,SQL分离,灵活度高,移植性差。Hibernate 全自动,SQL不分离,灵活度差,移植性好。

4、#{} 和 ${} 的区别,防注入?(重点)

#{} 是预编译处理,${}是字符串替换。

  • MyBatis 在处理#{}时,会将 sql 中的#{}替换为?号,sql语句解析会加上"",调用 PreparedStatement 的 set 方法来赋值,使用#{}可以有效的防止 SQL 注入,提高系统安全性。
  • MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。${} 方式一般用于传入数据库对象,例如传入表名或配置信息。但是一般能用#的就别用$。

5、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>为不支持自增的主键生成策略标签。

6、MyBatis 的核心组件?

  • SqlSessionFactoryBuilder(构造器),根据代码或配置生成SqlSessionFactory,采用分布构建的 Builder 模式。
  • SqlSessionFactory(工厂接口),生成SqlSession,采用的是工厂模式。
  • SqlSession(会话),既可以发送SQL去执行并返回结果,也可以获取Mapper的接口。
  • SQL Mapper(映射器),由一个Java接口和一个XML文件(或注解)构成,需要给出对应的SQL和映射规则,负责发送SQL并返回结果。

7、MyBatis 如何分页?

MyBatis 本身提供了 RowBounds 对象进行分页,她是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

  • RowBounds 对象进行分页,针对ResultSet结果集执行的内存分页,非物理分页。
  • 插件分页,实现Mybatis提供的插件接口,如 PageHelper,物理分页,在插件的拦截方法内拦截待执行的sql,然后重写sql,添加对应的物理分页语句和物理分页参数。

8、MyBatis 都有哪些 Executor 执行器?

MyBatis有四种常见的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor  CachingExecutor

  • SimpleExecutor:单次执行。每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  • ReuseExecutor:重复使用。执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
  • BatchExecutor:批量操作。执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
  • CachingExecutor:启用二级缓存。启用于二级缓存时的执行器,采用静态代理,代理一个 Executor 对象。执行 update 方法前判断是否清空二级缓存,执行 query 方法前先在二级缓存中查询,命中失败再通过被代理类查询。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

9、MyBatis 的一级缓存和二级缓存?

  • 一级缓存:SqlSession 级别的缓存。基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
  • 二级缓存:Mapper 级别的缓存。与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> 。

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

10、MyBatis 的插件运行原理,如何编写一个插件?

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

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

11、最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。

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

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

12、MyBatis 执行批量插入,能返回数据库主键列表吗?

能,JDBC都能,Mybatis当然也能。

  • 对于支持生成自增主键的数据库:增加 useGenerateKeys和keyProperty ,<insert>标签属性。
  • 不支持生成自增主键的数据库:使用<selectKey>。
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

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

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

14、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,支持延迟加载的原理都是一样的。

15、MyBatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

16、 ResultMap 和 ResultType 的区别?

resultType和resultMap功能类似 ,都是返回对象信息,区别在于resultMap要手动配置一下,表和类的对应关系当实体类和数据库表字段一一对应的时候,resultType会自动建立对应关系;当实体类和数据库表字段不一致的时候,使用resultMap手动建立对应关系。

在MyBatis进行查询映射时,查询出来的每一个属性都是放在一个对应的Map里面的,其中键是属性名,值则是其对应的值。
1.resultType,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis会自动的给把对应的值赋给resultType所指定对象的属性
2.当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的手动把它转化为对应的对象,这常常在复杂查询中很有作用。

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

第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用 sql 列的别名功能,将列的别名书写为对象属性名。有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

发布了133 篇原创文章 · 获赞 131 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/shipfei_csdn/article/details/104059091
今日推荐