MyBatis 是一个容易上手的持久层框架,使用者通过简单的学习即可掌握其常用特性的用法,这也是MyBatis 被广泛使用的原因之一。在深入分析MyBatis 源码前,我想先向大家介绍一下MyBatis 的一些基础知识。本章将从三个角度向大家介绍 MyBatis,分别是 MyBatis 是什么,为什么要使用,以及如何使用。如果大家对MyBatis 比较了解,可以跳过本章。
MyBatis 的前身是 iBatis,iBatis 是 Apache 软件基金会下的一个开源项目。2010 年该项目从Apache 基金会迁出,并改名为MyBatis。同期,iBatis 停止维护。
MyBatis 是一种半自动化的Java 持久层框架(persistenceframework),其通过注解或 XML的方式将对象和 SQL 关联起来。之所以说它是半自动的,是因为和Hibernate 等一些可自动生成 SQL 的ORM(ObjectRelationalMapping)框架相比,使用 MyBatis 需要用户自行维护 SQL。维护 SQL 的工作比较繁琐,但也有好处。比如我们可控制 SQL 逻辑,可对其进行优化,以提高效率。
我们在使用 Java 程序访问数据库时,有多种可选方案。比如我们可通过编写最原始的
JDBC 代码访问数据库,或是通过 Spring 提供的JdbcTemplate 访问数据库。除此之外,我们还可以选择Hibernate,亦或是本书的主角 MyBatis 等。在有多个可选项的情况下,我们为什么选择MyBatis 呢?要回答这个问题,我们需要将 MyBatis 与这几种框架对比一下,高下立判。当然,技术之间一般没有高下之分。从应用场景的角度来说,符合应用场景需求的技术才是合适的选择。接下来我们通过写代码的方式,来对比这几种数据库访问技术的优缺点, 并在最后说明MyBatis 的适用场景。
这里先把本节所用到的一些公共类和配置贴出来,后面但凡用到这些类和配置的地方, 大家可以到这里进行查看。如下:
public class Article {
private Integer id;
private String title;
private String author;
private String content;
private Date createTime;
// 省 略 getter/setter 和 toString
}
数据库相关配置放在了jdbc.properties 文件中,详细内容如下:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/myblog?useUnicode=true
jdbc.username=root
jdbc.password=root
前面说过,MyBatis 是一种半自动化的 Java 持久层框架,需要用户自行维护 SQL。这里,我们把 SQL 放在 XML 中,文件名称为 ArticleMapper.xml。如下:
<mapper namespace="xyz.coolblog.chapter1.dao.ArticleDao">
<select id="findByAuthorAndCreateTime" resultType="Article"> SELECT
`id`, `title`, `author`, `content`, `create_time` FROM
`article` WHERE
`author` = #{
author} AND `create_time` > #{
createTime}
</select>
</mapper>
上面的 SQL 用于从 article 表中查询出某个作者从某个时候到现在所写的文章记录。在
MyBatis 中, SQL 映射文件需要与数据访问接口对应起来,比如上面的配置对应
xyz.coolblog.dao.ArticleDao 接口,这个接口的定义如下:
public interface ArticleDao {
List<Article> findByAuthorAndCreateTime(@Param("author") String author, @Param("createTime") String createTime);
}
要想让 MyBatis 跑起来,还需要进行一些配置。比如配置数据源、配置 SQL 映射文件的位置信息等。本节所使用到的配置如下:
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<typeAlias alias="Article" type="xyz.coolblog.chapter1.model.ArticleDO"/>
<typeAlias alias="Author" type="xyz.coolblog.model.AuthorDO"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="chapter1/mapper/ArticleMapper.xml"/>
</mappers>
</configuration
到此,MyBatis 所需的环境就配置好了,下面写点测试代码把MyBatis 跑起来。
public class MyBatisTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void prepare() throws IOException {
String resource = "chapter1/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream); inputStream.close();
}
@Test
public void testMyBatis() throws IOException {
SqlSession session = sqlSessionFactory.openSession(); try {
ArticleDao articleDao = session.getMapper(ArticleDao.class); List<Article> articles = articleDao.
findByAuthorAndCreateTime("coolblog.xyz", "2018-06-10");
} finally {
session.commit(); session.close();
}
}
}
在上面的测试代码中,prepare 方法用于创建SqlSessionFactory 工厂,该工厂的用途是创建 SqlSession。通过 SqlSession 可为我们的数据库访问接口 ArticleDao 接口生成一个代理对象。MyBatis 会将接口方法findByAuthorAndCreateTime 和 SQL 映射文件中配置的 SQL 关联起来,这样调用该方法等同于执行相关的 SQL。
上面测试代码的运行结果如下:
如上,大家在学习 MyBatis 框架时,可以配置一下 MyBatis 的日志,这样可把 MyBatis 的调试信息打印出来,方便观察 SQL 的执行过程。在上面的结果中,> 符号所在的行表示向数据库中输入的 SQL 及相关参数。< 符号所在的行则是表示 SQL 的执行结果。上面输入输出不难看懂,这里就不多说了。
在初学 Java 编程阶段,多数朋友应该都是直接通过写 JDBC 代码访问数据库。这种方式的代码执行流程一般为加载数据库驱动,创建数据库连接对象、创建 Statement 对象、执行 SQL,以及处理结果集等,过程比较固定。下面我们再手写一遍 JDBC 代码,回忆一下初学Java 的场景。
public class JdbcTest {
@Test
public void testJdbc() {
String url = "jdbc:mysql://localhost:3306/myblog?user=root&……";
Connection conn = null; try {
Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection(url);
String author = "coolblog.xyz"; String date = "2018.06.10";
String sql = "SELECT id, title, author, content, create_time"
+ " FROM article"
+ " WHERE author = '" + author
+ "' AND create_time > '" + date + "'";
Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);
List<Article> articles = new ArrayList<>(rs.getRow());
while (rs.next()) {
Article article = new Article();
article.setId(rs.getInt("id"));
article.setTitle(rs.getString("title"));
article.setAuthor(rs.getString("author"));
article.setContent(rs.getString("content")); article.setCreateTime(rs.getDate("create_time"));
articles.add(article);
}
System.out.println("Query SQL ==> " + sql);
System.out.println("Query Result: ");
articles.forEach(System.out::println);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
上面代码的步骤比较多,但核心步骤只有两部,分别是执行 SQL 和处理查询结果。从开发人员的角度来说,我们也只关心这两个步骤。如果每次为了执行某个 SQL 都要写很多额外的代码。比如打开驱动,创建数据库连接,就显得很繁琐了。当然我们可以将这些额外的步骤封装起来,这样每次调用封装好的方法即可。这样确实可以解决代码繁琐,冗余的问题。不过,使用JDBC 并非仅会导致代码繁琐,冗余的问题。在上面的代码中,我们通过字符串对 SQL 进行拼接。这样做会导致两个问题,第一是拼接 SQL 可能会导致 SQL 出错,比如少了个逗号或者多了个单引号等。第二是将 SQL 写在代码中,如果要改动 SQL,就需要到代码中进行更改。这样做是不合适的,因为改动 Java 代码就需要重新编译 Java 文件,然后再打包发布。同时,将 SQL 和 Java 代码混在一起,会降低代码的可读性,不利于维护。关于拼接 SQL 问题,是有相应的处理方法。比如可以使用 PreparedStatement,在解决拼接SQL 问题的同时,还可解决 SQL 注入的问题。
除了上面所说的一些问题,直接使用 JDBC 访问数据库还会导致什么问题呢?这次我们将目光转移到执行结果的处理逻辑上。从上面的代码中可以看出,我们需要手动从 ResultSet 中取出数据,然后再设置到 Article 对象中。好在我们的 Article 属性不多,所以这样做看起来也没什么。假如 Article 对象有几十个属性,再用上面的方式接收查询结果,会非常的麻烦。而且可能还会因为属性太多,导致忘记设置某些属性。以上的代码还有一个问题,用户需要自行处理受检异常,这也是导致代码繁琐的一个原因。哦,还有一个问题,差点忘了。用户还需要手动管理数据库连接,开始要手动获取数据库连接。使用好后,又要手动关闭数据库连接。不得不说,真麻烦。
没想到直接使用JDBC 访问数据库会有这么多的问题。但这并不意味着完全不可以在项目中直接JDBC,应视情况而定。如果项目非常小,且对数据库依赖比较低。直接使用 JDBC 也很方便,不需要像 MyBatis 那样搞一堆配置了。
与JDBC 相比,MyBatis 缺点比较明显,它的配置比较多,特别是 SQL 映射文件。如果一个大型项目中有几十上百个 Dao 接口,就需要有同等数量的 SQL 映射文件,这些映射文件需要用户自行维护。不过与JDBC 相比,维护映射文件不是什么大问题。不然如果把同等数量的 SQL 像 JDBC 那样写在代码中,那维护的代价才叫大,出错的可能性也会增大。除了配置文件的问题,大家会发现使用 MyBatis 访问数据库好像过程也很繁琐啊。它的步骤大致如下:
1.读取配置文件
2.创建SqlSessionFactoryBuilder 对象
3.通过SqlSessionFactoryBuilder 对象创建SqlSessionFactory
4.通过SqlSessionFactory 创建SqlSession
5.为 Dao 接口生成代理类
6.调用接口方法访问数据库
如果每次执行一个 SQL 要简历如上几步步骤,那和 JDBC 比较起来,也就没什优势了。好在,事实并非如此。SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等对象的作用域和生命周期是不一样的,这一点在 MyBatis 官方文档中有所说明。我这里照搬一下,
SqlSessionFactoryBuilder 对 象 用 于 构 建 SqlSessionFactory , 只 要 构 建 好 ,
SqlSessionFactoryBuilder 对象就可以丢弃了。SqlSessionFactory 是一个工厂类,一旦被创建就应该在应用运行期间一直存在,不应该丢弃或重建。SqlSession 不是线程安全的,所以不应被多线程共享。官方推荐的使用方式是有按需创建,用完即销毁。
因此,以上步骤中,第1、2 和第 3 步只需执行一次。第 4 和第 5 步需要进行多次创建。至于第 6 步,这一步是必须的。相比之下,MyBatis 访问数据库的步骤要比JDBC 简练不少。同时,使用 MyBatis 无需处理受检异常,比如 SQLException。另外,把 SQL 写在配置文件中,进行集中管理,利于维护。同时将 SQL 从代码中剥离,在提高代码的可读性的同时,也避免了拼接 SQL 可能会导致的错误。除了上面所说这些,MyBatis 会将查询结果映射为相应的对象,无需用户自行处理 ResultSet。
总的来说,MyBatis 在易用性上要比JDBC 好太多。不过这里拿 MyBatis 和 JDBC 进行对比并不太合适。JDBC 作为Java 平台的数据库访问规范,它仅提供一种访问数据库的能力。至于使用者觉得 JDBC 流程繁琐,还要自行处理异常等问题,这些还真不怪 JDBC。比如SQLException 这个异常,JDBC 没法处理,也不应该处理,抛给调用者处理也是理所应当。至于繁杂的步骤,这仅是从使用者的角度考虑的,从 JDBC 的角度来说,这里的每个步骤对于完成一个数据访问请求来说都是必须的。至于 MyBatis,它是构建在 JDBC 技术之上的, 对访问数据库的操作进行了简化,方便用户使用。综上所述,JDBC 可看做是一种基础服务,
MyBatis 则是构建在基础服务之上的框架,它们的目标是不同的。