目前的工作是 Java 服务端开发,用到的技术有 SpringSpring BootMyBatisRedisMySQL 等;近期通过两个项目的实践,对 MyBatis 进行了系统性的学习和踩坑,在此对其做一个简单的总结和回顾。

学习 MyBatis

MyBatis 官方文档 比较清晰明了,而且有英文、中文等多个语言版本,是非常好的学习资料,建议先通读一遍官方文档;

总的来说,通过以下几个方面就可以熟悉 MyBatis:

  • 通读官方文档,理解 MyBatis 的概念及其用法;
  • 项目实践,遇到问题 Google;
  • 对于遇到的问题,搜索相关博客,进行系统性的学习,争取理解其来龙去脉;
  • 代码 debug,逐步跟踪源码,熟悉 MyBatis 的处理流程,从代码层面分析问题;

后文将通过几个主题谈谈我对 MyBatis 的理解。

注:还有一个项目 Mybatis-Plus,是对 MyBatis 的增强,可以参考一下。

什么是 MyBatis ?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

终结篇:MyBatis原理深入解析(一) 详细讲解了 JDBC 到 Mybaits 的演变,非常清晰的展示了 MyBatis 的功能及用法。

此外,个人觉得 深入分析 Java Web 技术内幕 一书中 【第 15 章 深入分析 iBatis 框架之系统架构与映射原理】讲的也比较明白,将 MyBatis 的功能总结为如下两点:

  • 根据 JDBC 规范建立与数据库的连接;
  • 通过反射打通 Java 对象与数据库参数交互之间相互转换的关系;

书中下面两幅图中的内容清晰直观的描述了 MyBatis 功能、配置文件和代码的关系:

作用域(Scope)和生命周期

作用域和生命周期在程序中非常重要,好在 MyBatis 已经帮我们处理好了这些问题,具体可查看 作用域(Scope)和生命周期

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器(mapper)并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

MyBatis

其中涉及到下面几个类,对 MyBatis 的环境配置,主要是对这几个类的配置,比如配置 DataSource、TransactionManager、SqlSessionFactoryBean 等。

  • SqlSessionFactoryBuilder 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • SqlSessionFactory SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。SqlSessionFactory 的最佳作用域是应用作用域。
  • SqlSession 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。

Mapper 配置

MyBatis 目前支持两种方式的配置,分别是:

  • Java 注解:只有 Java 文件,简单清晰,但是不够灵活,可用于处理一些简单的 SQL;
  • XML 配置:需要增加 XML 文件,但是功能强大,借助于 MyBatis 的 动态 SQL 可以完成各种复杂的 SQL 语句;

注: 在 XML 配置中,MyBatis 可通过配置 databaseIdProvider 支持不同的数据库,针对它们各自的语法,同一条语句可以有不同的版本,MyBatis 通过 SQL 映射语句中 databaseId 字段选择合适的语句,从而对于逻辑层屏蔽数据库差异。

目前常见的做法是采用 XML 方式配置,特殊情况下借助于 Java 注解完成;

对于 XML 配置的方式,可以采用 MyBatis Generator 工具一键生成对应的配置文件,比如对于数据库 demo,通过配置可生成下面的四个文件:

  • DemoMapper.xml 包含 <mapper><sql><select><update> 等数据库语句的具体配置;
  • DemoMapper.java 是一个接口,定义了一些数据库操作,和 DemoMapper.xml 中的 sql 语句对应;
  • Demo.Java 是 PO(Persistent Object)持久层对象,和数据库表中的字段一一对应;
  • DemoExample.java 拥有构造操作数据库的条件,在 Java 代码中组合出任何查询条件,本质是借助 MyBatis 的 动态 SQL,它可以根据参数值生成不同的 sql;

注:修改 generatorConfig.xml 后重新生成上述四个文件时,需要先删除对应的 *Mapper.xml 文件,否则新生成的 XML 会追加到旧文件后面,出现错误。

在我们的项目中,由于用到了 shardbatis 分表,所以需要对 DemoExample.javaDemoMapper.java 做一些修改,采用 XML 配置、Java 注解配置共存的方式,也踩了一些坑,总结如下:

  • 注解和 XML 配置不要混用;
  • 推荐使用 XML 配置,灵活强大;
  • 使用 XML 配置的情况下,部分注解会失效;如 SELECT 语句配置在 XML 中时,@Options() 注解该 SELECT 对应的接口函数会失效,MyBatis 按照 XML 中的语句配置工作;
  • 部分情况下可搭配注解和 XML 一起使用;如 @Select() 情况下,可用 @ResultMap() 和 Mapper.xml 搭配处理返回结果,进行列名转换等;
  • 在被 @Transactional 注解的函数中,所有数据库操作会共用一个 sqlSession;
  • SELECT 语句默认开启查询缓存,所以在相同的 sqlSession 中(比如在 @Transactional 注解的函数中),多次用相同的条件查询数据库时,后面的查询会直接读取缓存返回;

插件(plugins)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。

其实就是说可以为 MyBatis 增加各种插件,扩展 MyBatis 的功能,比如在我们的项目中就用到了下面两个插件:

  • shardbatis 分表插件;
  • PageInterceptor 这是一个自定义的分页插件,通过拦截 StatementHandlerprepare 方法,重写sql语句实现物理分页。
    • 增加这个插件是因为 MyBatis 默认的分页是逻辑分页,非常低效,是将所有的结果查询出来之后,再来截取指定页面的结果。
    • Mybatis-PageHelper 这是在网上看到的一个分页插件,我没实践过。

注:各个插件在一起可能会出现冲突,需要根据具体问题查看代码来解决。

缓存

MyBatis 支持两级缓存,默认情况只开启一级缓存(sqlSession 缓存,不同 sqlSession 互不干扰),二级缓存需要配置才能启用,具体可看文章 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理

在实际的项目中一般不会启用 MyBatis 的二级缓存,往往在业务逻辑层使用 Redis、Qihoo360/pika 等内存数据库做缓存,这样会更加灵活和可控,程序相应的可靠性、扩展性也会提高。

在项目过程中,由于不熟悉一级缓存,踩了一个坑,具体见另一篇博客 MyBatis 事务操作中缓存带来的坑

References