1 从JDBC开始
Mybatis本质上就是一个用来执行数据库访问的Java框架,而JDBC是Java提供的标准数据库访问接口。所有的Java数据库访问框架最终都逃不开JDBC,所以在进一步了解Mybatis之前,我们有必要先把JDBC访问数据库的方式给搞清楚。
1.1 JDBC访问数据库
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCPreparedStatementExample {
public static void main(String[] args) {
// 数据库连接信息
String url = "jdbc:mysql://localhost:3306/yourDatabaseName"; // 数据库URL
String user = "yourUsername"; // 数据库用户名
String password = "yourPassword"; // 数据库密码
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载JDBC驱动程序,
// mysql的Driver类里面有个static代码块
// 调用了DriverManager.registerDriver(new Driver)
// 在类首次加载的时候,就会将驱动对象注册到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
// DriverManager使用注册的mysql Driver对象获取数据库连接
connection = DriverManager.getConnection(url, user, password);
// 定义SQL查询模板,其中的问号(?)为占位符
String sql = "SELECT id, name, email FROM users WHERE status = ?";
// 创建PreparedStatement对象
preparedStatement = connection.prepareStatement(sql);
// 设置参数值,第一个参数1表示第一个问号占位符的索引
preparedStatement.setString(1, "active");
// 执行查询并获取结果集
resultSet = preparedStatement.executeQuery();
// 遍历结果集
while (resultSet.next()) {
// 通过列名获取数据
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
// 输出结果到控制台
System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
}
} catch (ClassNotFoundException e) {
// 捕获加载JDBC驱动程序时的异常
e.printStackTrace();
} catch (SQLException e) {
// 捕获SQL异常
e.printStackTrace();
} finally {
// 确保资源被释放
try {
if (resultSet != null) resultSet.close(); // 关闭ResultSet
if (preparedStatement != null) preparedStatement.close(); // 关闭PreparedStatement
if (connection != null) connection.close(); // 关闭Connection
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.2 MyBatis到底能干什么呢?
从上节的示例代码中,可以看出JDBC访问数据的大致操作流程。那么 MyBatis到底能干什么呢?
- 样板代码太多
JDBC问题:在JDBC中,开发者需要编写大量重复性的代码来管理连接、创建语句、执行查询以及处理异常和关闭资源。这些样板代码容易导致代码冗余和出错。
MyBatis解决:MyBatis使用SQL映射文件(XML)和注解来定义SQL语句,使得代码更加简洁。它还提供了自动管理资源的功能,使得开发者可以专注于业务逻辑。 - SQL语句硬编码
JDBC问题:在JDBC中,SQL语句通常是硬编码在Java代码中的,这不仅降低了代码的清晰性和可维护性,还导致SQL语句无法复用。
MyBatis解决:MyBatis将SQL语句分离到映射文件中,增强了SQL的可读性和可维护性。SQL配置在XML文件中可以进行版本控制,并且不同的SQL语句可以复用。 - 难以映射结果集
JDBC问题:开发者需要自己解析ResultSet并将数据映射到Java对象上,这一操作繁琐且容易出错。
MyBatis解决:MyBatis提供了自动映射功能,可以根据预定义的映射规则将数据库结果自动转为Java对象。通过映射文件或注解,很容易完成对象与结果的映射。 - 异常处理复杂
JDBC问题:JDBC需要手动处理SQLException和其他可能出现的运行时异常,以及一些必要的转换。
MyBatis解决:MyBatis封装了异常处理,将JDBC的检查型异常转换为RuntimeException,简化了异常管理。 - 需要手动管理事务
JDBC问题:在JDBC中,事务管理通常要求开发者手动控制,这对于复杂的事务操作来说非常繁琐。
MyBatis解决:MyBatis可以与Spring这样的框架集成使用Spring的事务管理,从而自动管理事务的开始、提交和回滚,简化了开发。 - 难以动态SQL处理
JDBC问题:在JDBC中实现动态SQL需要在代码中进行复杂的字符串拼接和条件判断。
MyBatis解决:MyBatis提供了功能强大的动态SQL支持,使用XML标签(如if、choose、foreach等)来实现动态SQL,使SQL的构建更加灵活和直观。
2 Mybatis架构介绍
2.1 整体架构
2.2 基础支持层
- 数据源模块
- 负责管理数据库连接,通常包括连接池的配置和使用。
- 提供对多种数据源类型的支持,比如 DBCP、C3P0、JNDI 等,以提高数据库连接的性能和资源利用率。
- 事务管理
- 实现事务的提交和回滚功能。
- 支持不同的事务管理方式,包括 JDBC 事务和通过框架(如 Spring)托管的事务管理。
- 缓存模块
- 提供缓存策略以优化查询性能,通过减少数据库查询次数来提升效率。
- 包括一级缓存(会话级别)和二级缓存(全局/映射器级别),其中一级缓存默认开启,而二级缓存需要配置启用。
- Binding 模块
- 负责将 Mapper 接口与 SQL 语句动态绑定。
- 使用 Java 动态代理机制,为 Mapper 接口生成实现类,以便实现方法调用和 SQL 执行之间的映射。
- 反射模块
- 封装了Java原生的反射API并且进行了一系列优化,提供易用性更好,更简洁的API。
- 通过反射模块,MyBatis 实现了灵活、动态和高性能的对象属性访问及映射操作,这使得数据从数据库到 Java 对象的转换过程既健壮又高效。。
- 类型转换模块:
- 在 SQL 数据类型和 Java 数据类型之间进行转换。
- 提供默认的类型处理器(TypeHandler),并支持用户自定义类型处理器以处理特殊的转换需求。
- 日志模块
- 提供日志记录功能,帮助开发者调试和监控 SQL 执行。
- MyBatis 支持多种日志框架的集成,如 Log4j、SLF4J、Commons Logging,并可通过配置文件进行选择。
- 资源加载
- 负责加载 MyBatis 的核心配置文件和 Mapper 映射文件。
- 支持从多种来源(类路径、文件系统、网络 URL)查找和加载资源文件。
- 解析器模块
- 解析 MyBatis 的 XML 配置文件和注解,将其转换为框架内部使用的配置信息。
- 通过 XML 解析器和注解处理器,生成与 Java 代码相对应的 SQL 映射。
2.3 核心处理层
- 配置解析
- 主要指 MyBatis 对配置文件(如 mybatis-config.xml 和 Mapper XML 文件)的解析过程。
在 mybatis-config.xml 中,MyBatis 读取全局设置信息,比如环境配置、数据库连接池、事务管理器和全局别名等。 - 在 Mapper XML 文件中,MyBatis 解析每个 SQL 语句的配置信息,包括动态 SQL、结果集映射(ResultMap)及其他特定配置。
- MyBatis 使用这些信息来生成执行 SQL 所需的上下文和环境。
- 参数映射:
- 指的是将 Java 方法中的输入参数映射到 SQL 语句中对应的占位符。
- 使用 #{} 或 ${} 占位符在 SQL 模板中插入参数。#{} 会进行预编译处理,防止 SQL 注入,而 ${} 则是直接文本替换,可能存在安全隐患。
- MyBatis 提供丰富的特性来处理复杂的参数类型,例如,通过 Param 注释来显式命名参数,或使用 Map 和对象封装参数。
- SQL 解析
- MyBatis 在执行 SQL 之前需要对 SQL 进行解析,在这个过程中,动态 SQL 会被解析为可执行的 SQL 语句。
- 解析过程可能涉及到条件判断、循环和其他动态 SQL 功能的处理。
- 解析之后,得到一条完整的 SQL 语句,这条语句可以直接传递给数据库进行执行。
- SQL 执行
- 解析完成的 SQL 语句通过 MyBatis 的 Executor 执行器进行具体的执行。
- 执行过程涉及与 JDBC 的交互,准备语句对象、设置参数,并执行更新或查询操作。
- 结果集映射
- SQL执行结果返回之后,MyBatis 将结果集通过 ResultSetHandler 映射成预期的 Java 对象或者集合。
- 支持复杂数据结构的映射,如嵌套对象和集合,通过详细的结果配置实现。
- 插件
- MyBatis 提供了一套拦截机制,允许用户通过插件来增强 MyBatis 的功能。常用的拦截器有分页、日志记录等。
2.4 接口层
SqlSession:核心接口,表示和数据库的会话。用于执行 SQL 命令,获取映射器,管理事务等。通常,一次数据库交互操作对应一个 SqlSession 实例。
2.5 从JDBC到MyBatis
看完了整体架构,再通过一个Mybatis访问数据的流图来初步感受一下,它的各个模块是如何配合工作的。