与传统的JDBC相比,MyBatis的优点?
面试官您好,MyBatis相比于传统的JDBC,它并不是要完全颠覆JDBC,而是作为JDBC的一个强大的“增强框架”。它的核心价值在于,在保留了SQL最大灵活性的前提下,极大地简化了数据访问层的开发工作量,并解决了原生JDBC开发的诸多痛点。
要理解MyBatis的优点,我们可以先回顾一下使用原生JDBC开发的“痛苦”:
- 大量的样板代码:每次数据库操作,我们都需要手动地获取连接、创建
PreparedStatement
、设置参数、执行SQL、处理ResultSet
、然后又要在finally
块中小心翼翼地、按顺序地关闭ResultSet
、Statement
和Connection
。这些代码高度重复且极其繁琐。 - SQL语句硬编码:SQL语句通常硬编码在Java代码中,导致SQL与业务逻辑紧密耦合,难以维护和优化。
- 手动的结果集映射:我们需要手动地从
ResultSet
中逐个字段地取出数据,然后再set
到Java对象中。这个过程既枯燥又容易出错。
MyBatis正是为了解决以上所有痛点而生的。它的优点主要体现在:
1. 大幅简化代码,解放生产力
- 自动化的资源管理:MyBatis完全消除了JDBC大量的冗余代码。它在底层为我们处理好了连接的获取和释放,我们完全不需要再关心手动开关连接的问题。
- 自动化的结果集映射:这是它最便捷的功能之一。我们只需要在XML中配置好一个
ResultMap
,或者遵循简单的命名约定,MyBatis就能自动地将查询结果集映射成我们的Java对象(POJO)或集合,彻底告别了手动封装数据的痛苦。据统计,这至少能减少50%以上的代码量。
2. SQL与代码解耦,提供强大的动态SQL
- SQL集中管理:MyBatis允许我们将所有的SQL语句都写在XML映射文件中,实现了SQL与Java代码的彻底分离。这有几个巨大的好处:
- DBA或SQL优化人员可以不接触Java代码,直接专注于优化XML中的SQL。
- SQL语句的管理和维护变得非常清晰、方便。
- 强大的动态SQL:这是MyBatis的一大杀手锏。它提供了
<if>
,<choose>
,<when>
,<foreach>
等丰富的XML标签,让我们可以在XML中,非常方便地根据不同的条件拼接出不同的SQL语句。这对于处理复杂查询(如多条件动态查询)的场景,比在Java代码里用if-else
去拼字符串要优雅得多、安全得多。
3. 兼顾灵活性与ORM便利性
- SQL的完全控制权:与Hibernate这样的全自动ORM框架不同,MyBatis是一个 “半自动”的ORM框架 。它把SQL的编写权完全交还给了开发者。这意味着,我们可以充分利用特定数据库的方言和特性,编写出最高效、最复杂的SQL语句,这是全自动ORM框架难以企及的。
- 简单的ORM映射:同时,它又通过
<resultMap>
等标签,提供了对象与数据库表之间的字段映射,以及一对一、一对多等关系映射,享受到了ORM带来的便利。
4. 优秀的兼容性与集成性
- 数据库兼容性:因为它底层依然是使用JDBC,所以理论上,任何提供了JDBC驱动的数据库,MyBatis都能支持。
- 与Spring的无缝集成:MyBatis与Spring框架的集成非常成熟(通过
mybatis-spring-boot-starter
)。它可以被Spring的IoC容器管理,并且能够完美地融入到Spring的声明式事务管理体系中,让我们的开发效率如虎添翼。
总结一下,MyBatis通过自动化资源管理和结果集映射,解放了我们的双手;通过SQL与代码解耦和动态SQL,提升了代码的灵活性和可维护性;同时又让我们保留了对SQL的完全控制权。它在 “开发效率” 和 “SQL性能” 之间,找到了一个完美的平衡点,因此成为了国内互联网公司中数据访问层框架的首选。
你觉得MyBatis在哪方面做的比较好?
面试官您好,MyBatis的优点很多,比如它简化了JDBC的样板代码、与Spring的无缝集成等等。但如果说我个人认为它做得最好的、最让我受益匪浅的方面,我认为是以下两点:
第一点,也是最重要的一点:它在“开发效率”与“SQL的灵活性和性能”之间,找到了一个完美的平衡点。
在我看来,MyBatis是一个非常“务实”的框架,它深刻地理解了国内互联网公司复杂的业务需求和对性能的极致追求。
一方面,它像一个“半自动”的ORM框架,极大地提升了开发效率。
- 它为我们自动处理了最繁琐、最重复的JDBC样板代码,比如连接的获取与释放、
PreparedStatement
的创建、参数的设置以及最头疼的ResultSet
到Java对象的手动映射。这些自动化的工作,至少为我们减少了50%的数据访问层代码量,让我们能更专注于业务。
- 它为我们自动处理了最繁琐、最重复的JDBC样板代码,比如连接的获取与释放、
另一方面,它又不像Hibernate那样的全自动ORM,它把最重要的SQL编写权,完全交还给了开发者。
- 这一点至关重要。 在复杂的业务场景下,我们经常需要编写一些非常复杂的、高度优化的SQL,比如多表连接、子查询、使用数据库特有的函数、甚至是
WITH
子句等。 - 对于这些复杂SQL,全自动的ORM框架往往难以生成高效的语句,甚至根本无法表达。而MyBatis允许我们手写最原生、最高效的SQL,让我们能够充分压榨出数据库的性能。
- SQL与代码的解耦:它还将SQL语句从Java代码中抽离到XML文件里,这使得SQL的维护和优化变得非常方便。DBA可以不接触任何Java代码,直接对XML中的SQL进行审核和调优,权责非常清晰。
- 这一点至关重要。 在复杂的业务场景下,我们经常需要编写一些非常复杂的、高度优化的SQL,比如多表连接、子查询、使用数据库特有的函数、甚至是
所以,MyBatis的这种“半自动化”哲学,既让我们享受到了ORM的便利,又保留了对SQL的完全控制,这是我认为它做得最好的地方。
第二点:它的动态SQL功能,设计得非常强大和优雅。
在实际项目中,我们经常会遇到需要根据不同条件动态拼接SQL的场景,比如一个商品的多条件筛选查询。
- 如果没有动态SQL,我们可能需要在Java代码里用大量的
if-else
来拼接字符串,这不仅代码丑陋,而且非常容易因为漏掉空格或引号而产生SQL语法错误,甚至有SQL注入的风险。 - MyBatis的动态SQL,通过提供
<if>
,<choose>
,<when>
,<foreach>
这些直观的XML标签,让我们可以在XML层面,以一种声明式、更安全的方式来构建复杂的动态查询。- 例如,用
<foreach>
标签来处理IN
查询的参数集合,比我们自己在Java里拼(?,?,?)
要简单和安全得多。 - 用
<if test="...">
来判断一个查询条件是否为空,并决定是否要拼接对应的AND
子句。
- 例如,用
这个功能极大地提高了复杂查询语句的可读性和可维护性。
总结一下,我认为MyBatis做得最好的地方,就是它非常 “接地气”。它深刻洞察了Java后端开发的实际痛点,通过保留SQL控制权和提供强大的动态SQL,在 “高性能” 和 “高效率” 这两个看似矛盾的目标之间,找到了一个无与伦比的“甜点区”。
JDBC连接数据库的步骤是什么?
面试官您好,使用JDBC来连接和操作数据库,需要遵循一套非常经典、固定的流程。这个流程可以分为以下几个核心步骤:
第一步:加载数据库驱动 (Registering the Driver)
- 做什么:正如您所说,我们需要告诉JVM我们要使用哪个数据库的驱动。在JDBC 4.0之前,最常见的方式是使用反射来显式加载:
这行代码会执行驱动类中的静态代码块,从而将驱动实例注册到Class.forName("com.mysql.cj.jdbc.Driver"); // 注意:新版MySQL驱动类名是这个
DriverManager
中。 - 现代实践:在JDBC 4.0及以后的版本中,我们通常不再需要手动调用
Class.forName()
。只要我们将数据库驱动的jar包(比如mysql-connector-java.jar
)放在项目的classpath下,驱动就会通过SPI(Service Provider Interface)机制被自动加载和注册。
第二步:建立数据库连接 (Opening a Connection)
- 做什么:通过
DriverManager
这个工厂类,根据数据库的URL、用户名和密码,来获取一个到数据库的物理连接。String url = "jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC"; String user = "root"; String password = "password"; Connection conn = DriverManager.getConnection(url, user, password);
- 这是一个非常昂贵的操作,因为它涉及到网络通信和数据库的认证。这也是为什么我们需要数据库连接池技术来复用连接的原因。
第三步:创建语句对象 (Creating a Statement)
做什么:
Connection
对象本身不能执行SQL,我们需要通过它来创建一个“语句执行器”。主要有两种:Statement
: 用于执行静态的、不带参数的SQL。Statement stmt = conn.createStatement();
PreparedStatement
(更推荐): 用于执行预编译的、带参数的SQL。String sql = "SELECT * FROM users WHERE id = ?"; PreparedStatement pstmt = conn.prepareStatement(sql);
为什么推荐
PreparedStatement
?- 性能更高:SQL语句会被数据库预编译和缓存,当多次执行同一结构的SQL时,效率更高。
- 更安全:它从根本上防止了SQL注入攻击,因为用户传入的参数会被当作纯粹的数据处理,而不是作为SQL指令的一部分。
第四步:执行SQL语句
- 做什么:使用上一步创建的语句对象来执行SQL。
- 对于查询操作(
SELECT
):pstmt.setInt(1, 123); // 设置第1个问号参数 ResultSet rs = pstmt.executeQuery();
- 对于更新操作(
INSERT
,UPDATE
,DELETE
):int rowsAffected = pstmt.executeUpdate();
- 对于查询操作(
第五步:处理结果集 (Processing the ResultSet)
- 做什么:如果执行的是查询,返回的
ResultSet
对象就包含了查询结果。它像一个指向数据行的游标。 - 如何处理:我们通常在一个
while
循环中,调用rs.next()
方法来移动游标到下一行。如果next()
返回true
,说明有数据,我们就可以通过rs.getString("column_name")
或rs.getInt(column_index)
等方法来获取当前行各个字段的值。while (rs.next()) { String name = rs.getString("name"); int age = rs.getInt("age"); // ... 处理数据 ... }
第六步:关闭资源 (Closing Resources) —— 最关键、最易错的一步
- 做什么:所有与数据库交互的资源(
Connection
,Statement
,ResultSet
)都是非常宝贵的、需要手动释放的外部资源。使用完毕后,必须确保它们被关闭,否则会导致严重的资源泄漏。 - 最佳实践:
- 关闭顺序:必须遵循 “后开先关” 的原则,即先关闭
ResultSet
,再关闭Statement
,最后关闭Connection
。 - 必须在
finally
块中关闭:为了保证即使在代码执行过程中发生异常,资源也一定能被关闭,关闭操作必须放在finally
块中。 - 使用
try-with-resources
(JDK 7+,强烈推荐):这是简化资源关闭的最佳方式。所有实现了AutoCloseable
接口的资源,都可以放在try()
的括号里。代码块结束时,JVM会自动地、按正确的顺序为我们关闭这些资源。// 推荐的写法 try (Connection conn = DriverManager.getConnection(url, user, password); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, 123); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { // ... } } } catch (SQLException e) { // ... 异常处理 ... }
- 关闭顺序:必须遵循 “后开先关” 的原则,即先关闭
遵循这套完整的流程,特别是最后的资源关闭最佳实践,我们就能编写出健壮、安全的JDBC代码了。当然,在实际项目中,我们通常会使用MyBatis、JPA等框架,它们在底层为我们封装了这一切。
如果项目中要用到原生的 MyBatis去查询,该怎样写?
面试官您好,如果在项目中需要使用原生的MyBatis(即不依赖mybatis-spring-boot-starter
等集成框架),我会按照一套非常经典、标准的流程来配置和使用它。
这个流程可以概括为:配置 -> 编码 -> 执行。
我以一个“根据ID查询用户信息”的简单需求为例,来展示整个过程:
第一步:配置MyBatis核心文件 (mybatis-config.xml
)
这是MyBatis的“大脑”,它告诉MyBatis如何连接数据库、去哪里找SQL映射文件等。
<!-- mybatis-config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1. 配置数据库连接环境 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<!-- 2. 注册SQL映射文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
第二步:创建数据实体类 (User.java
)
这是一个简单的POJO,用于承载从数据库查询出来的数据。
public class User {
private Long id;
private String username;
private String email;
// ... getters and setters ...
}
第三步:创建DAO接口 (UserMapper.java
)
我们定义一个接口,来声明所有与User
相关的数据库操作方法。
public interface UserMapper {
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户对象
*/
User getUserById(Long id);
}
第四步:编写SQL映射文件 (UserMapper.xml
)
这是MyBatis的精髓所在。我们在这个XML文件中,为DAO接口的每个方法提供具体的SQL实现。
<!-- mappers/UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 命名空间必须与DAO接口的全限定名一致 -->
<mapper namespace="com.example.mapper.UserMapper">
<!--
id: 必须与接口中的方法名一致
parameterType: 传入参数的类型
resultType: 返回结果的类型(MyBatis会自动进行结果集映射)
-->
<select id="getUserById" parameterType="long" resultType="com.example.model.User">
SELECT id, username, email FROM users WHERE id = #{id}
</select>
</mapper>
第五步:编写Java代码来执行查询
这是最后一步,我们将所有东西串联起来。
加载配置,创建
SqlSessionFactory
:SqlSessionFactory
是MyBatis的核心工厂,它负责创建SqlSession
。它是一个重量级对象,通常在应用启动时只创建一次。
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
获取
SqlSession
,执行操作:SqlSession
是与数据库进行一次会话的“通道”,它不是线程安全的,每次操作都应该获取一个新的实例,并且用完后必须关闭。- 最佳实践:使用
try-with-resources
语句来确保SqlSession
被自动关闭。
public User findUser(Long userId) { User user = null; // 使用try-with-resources确保session被关闭 try (SqlSession session = sqlSessionFactory.openSession()) { // 1. 获取Mapper代理对象 UserMapper mapper = session.getMapper(UserMapper.class); // 2. 调用接口方法,执行查询 user = mapper.getUserById(userId); } // session 在这里会自动关闭 return user; }
这个执行过程的核心:
- 我们不直接与XML打交道,而是通过
session.getMapper(UserMapper.class)
,让MyBatis为我们的UserMapper
接口动态地创建一个代理对象。 - 当我们调用代理对象的
getUserById(1L)
方法时,MyBatis会找到UserMapper.xml
中id
为getUserById
的<select>
标签,执行其中的SQL,并将结果自动映射成User
对象返回。
通过这套流程,MyBatis就将我们从繁琐的JDBC样板代码中解放了出来,让我们可以更专注于SQL本身的编写。
MyBatis里的#和$的区别?
面试官您好,#{}
和${}
是MyBatis中两种不同的参数占位符,它们在底层的处理机制和最终生成的SQL语句上,有着本质的区别。这个区别直接决定了它们的安全性、性能和使用场景。
1. #{...}
—— 预编译的参数占位符 (PreparedStatement)
这是我们最常用、也最推荐的使用方式。
- 底层原理:正如您所说,MyBatis在处理
#{}
时,会将其替换为一个?
问号占位符 ,并创建一个预编译的SQL语句(PreparedStatement
)。- SQL示例:
- Mapper XML中:
SELECT * FROM users WHERE username = #{name}
- 发送到数据库的SQL:
SELECT * FROM users WHERE username = ?
- Mapper XML中:
- SQL示例:
- 工作流程:在执行时,MyBatis会调用
PreparedStatement
的set
方法(如setString()
,setInt()
),将传入的参数值安全地设置到对应的?
上。参数值是作为纯粹的数据传递给数据库的,而不是作为SQL指令的一部分。 - 带来的两大优势:
- 安全性高:从根本上防止SQL注入。因为用户的输入(参数值)永远不会被当作SQL代码来解析和执行,所以无论用户输入什么(比如
' or '1'='1'
),它都只会被当作一个普通的字符串值来处理。 - 性能更好:
PreparedStatement
会被数据库预编译和缓存。如果同一个SQL模板被多次调用(只是参数不同),数据库可以直接使用缓存好的执行计划,无需重新编译SQL,从而提高了执行效率。
- 安全性高:从根本上防止SQL注入。因为用户的输入(参数值)永远不会被当作SQL代码来解析和执行,所以无论用户输入什么(比如
2. ${...}
—— 纯粹的字符串拼接 (String Substitution)
这是一种更直接、但也更危险的方式。
- 底层原理:MyBatis在处理
${}
时,会把它看作一个简单的字符串替换。它会直接将传入的参数值,原封不动地拼接到SQL语句中。- SQL示例:
- Mapper XML中:
SELECT * FROM users ORDER BY ${columnName}
- 如果传入的
columnName
是"create_time"
,那么发送到数据库的SQL就是:SELECT * FROM users ORDER BY create_time
- Mapper XML中:
- SQL示例:
- 带来的风险与问题:
- 极易引发SQL注入攻击:这是它最致命的缺点。如果参数来自于用户输入,且未经过严格的校验和过滤,恶意用户就可以构造恶意的SQL片段。
- 例子:如果
columnName
被传入了"id; DROP TABLE users;"
,那么拼接后的SQL就会变成灾难性的多条语句。
- 例子:如果
- 性能较低:因为每次参数变化,都会生成一条全新的SQL字符串,数据库无法利用预编译的缓存,每次都需要重新解析和编译SQL。
- 极易引发SQL注入攻击:这是它最致命的缺点。如果参数来自于用户输入,且未经过严格的校验和过滤,恶意用户就可以构造恶意的SQL片段。
3. 使用场景与最佳实践
基于以上对比,我的使用原则非常明确:
原则一:能用
#{}
的地方,永远不要用${}
。- 所有需要传递参数值的地方,比如
WHERE
条件的值、INSERT
或UPDATE
的字段值,都必须使用#{}
。
- 所有需要传递参数值的地方,比如
原则二:只在特定、必要且可控的场景下,才使用
${}
。${}
的唯一用武之地,是当我们需要动态地替换SQL中的“非参数”部分时,比如:- 动态指定表名或库名:
SELECT * FROM ${tableName} WHERE ...
- 动态指定排序列名:
ORDER BY ${columnName} ${sortDirection}
- 动态指定表名或库名:
- 使用前提:在这种场景下使用
${}
,我们必须在Java代码层面,对传入的参数进行极其严格的白名单校验,确保它只能是我们预期的几个值之一(比如,columnName
只能是"id"
,"name"
,"create_time"
中的一个),绝对不能将未经验证的用户输入直接拼接到SQL中。
总结一下,#{}
是安全的、高效的“参数绑定”,而${}
是危险的、低效的“字符串拼接”。在开发中,我会将使用#{}
作为铁律,只在无法用#{}
替代且能保证输入绝对安全的极少数情况下,才审慎地使用${}
。
MyBatisPlus和MyBatis的区别?
面试官您好,MyBatis-Plus(我们通常简称MP)并不是用来替代MyBatis的,而是MyBatis的一个极其强大的“增强工具包”。
它的定位是 “为简化开发、提高效率而生”。它在完全兼容MyBatis所有功能的基础上,通过提供大量开箱即用的实用功能,解决了我们在使用原生MyBatis时遇到的一些“痛点”,极大地提升了开发效率。
MyBatis的“痛点”在哪里?
虽然MyBatis已经极大地简化了JDBC的开发,但在实际使用中,我们仍然会发现一些不便之处:
- 大量的重复SQL:对于每一个单表的增、删、改、查,我们都需要在XML文件中手动编写几乎一模一样的SQL语句,这非常繁琐。
- 分页处理复杂:原生MyBatis没有内置的分页功能,我们需要自己引入分页插件(如PageHelper),或者在SQL中手动编写
LIMIT
子句,并额外写一条COUNT(*)
的SQL来查询总数。 - 复杂查询的构建不便:对于一些动态的多条件查询,虽然MyBatis的动态SQL很强大,但当条件非常多时,XML的拼接逻辑也会变得很复杂。
MyBatis-Plus是如何解决这些痛点的?
MP的核心优势,就是针对以上痛点,提供了优雅的解决方案:
解放双手:通用的CRUD操作
- 解决方案:正如您所说,MP提供了一个
BaseMapper<T>
接口。我们只需要让自己的Mapper接口继承这个BaseMapper
,就无需编写任何XML,立刻拥有了一整套非常强大的、开箱即用的单表CRUD方法。 - 例如:
insert
,deleteById
,updateById
,selectById
,selectBatchIds
,selectList
等等。对于日常开发中80%的单表操作,我们一行SQL都不用写。
- 解决方案:正如您所说,MP提供了一个
优雅地构建复杂查询:强大的条件构造器 (
Wrapper
)- 解决方案:MP提供了一个
Wrapper
条件构造器(QueryWrapper
和UpdateWrapper
)。它允许我们在Java代码中,以一种面向对象的、链式调用的方式,来安全、动态地构建复杂的查询条件。 - 示例:
// 查询用户名不为空,且年龄大于18,按年龄降序排列的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("username") .gt("age", 18) .orderByDesc("age"); List<User> users = userMapper.selectList(wrapper);
- 这种方式比在XML里用
<if>
等标签拼接SQL要更直观、更灵活,也更容易维护。
- 解决方案:MP提供了一个
无感分页:内置的分页插件
- 解决方案:MP内置了一个非常强大的分页插件。我们只需要通过一个简单的Java配置类来启用它。
- 如何使用:在查询时,我们只需要像平时一样调用
selectPage
方法,并传入一个Page
对象(指定当前页码和每页大小)。MP的分页插件会自动拦截我们的查询SQL,并为其动态地拼接上对应数据库方言的物理分页语句(如MySQL的LIMIT
),同时还会自动地为我们执行一次COUNT
查询,并将分页结果封装好返回。整个过程对开发者完全透明。
其他强大的增强功能:
- 代码生成器:可以根据数据库表结构,一键生成Entity、Mapper、Service、Controller等所有分层的代码,是项目初期的开发利器。
- 更强大的注解支持:提供了
@TableName
,@TableId
,@TableField
等注解,让我们能更方便地处理表名与类名、字段名与属性名不一致的情况。 - 逻辑删除:内置了对“逻辑删除”的支持,我们只需要在字段上加一个
@TableLogic
注解,所有的查询和删除操作都会自动地带上逻辑删除的条件。 - 多租户支持:内置了多租户插件,可以方便地实现SaaS系统的数据隔离。
总结一下,如果说MyBatis让我们从繁琐的JDBC中解放出来,专注于SQL本身;那么MyBatis-Plus则让我们从大量重复的、简单的SQL编写中再次解放出来,让我们只专注于那些真正复杂的、需要手写的业务SQL。它是一个“锦上添花”的工具,极大地提升了我们的开发幸福感和项目交付速度。
MyBatis运用了哪些常见的设计模式?
面试官您好,MyBatis作为一个设计极其优雅的持久层框架,其源码中巧妙地运用了大量经典的设计模式,以实现其灵活性、可扩展性和易用性。
我们可以沿着MyBatis从配置加载到执行SQL的整个核心流程,来看看这些设计模式是如何协同工作的。
第一阶段:配置解析与SqlSessionFactory
的构建
这个阶段主要是将XML配置文件解析成内存中的配置对象。
- 建造者模式 (Builder Pattern) —— 核心
- 体现:这是MyBatis启动流程的核心模式。我们最熟悉的
SqlSessionFactoryBuilder
就是典型的建造者。 - 作用:它负责接收一个配置文件的输入流,然后一步步地调用内部的各种
XMLConfigBuilder
,XMLMapperBuilder
等更细粒度的“建造者”,来解析XML、构建Configuration
这个庞大的配置对象,最终 “建造” 出一个SqlSessionFactory
实例。这个过程非常复杂,建造者模式将这个复杂的构建过程与最终的对象表示分离开来,使得构建流程非常清晰。
- 体现:这是MyBatis启动流程的核心模式。我们最熟悉的
第二阶段:SqlSession
的创建与Mapper的获取
工厂模式 (Factory Pattern) —— 核心
- 体现:
SqlSessionFactory
就是一个典型的工厂。 - 作用:它的唯一职责就是创建
SqlSession
的实例。SqlSession
是与数据库进行一次会话的“通道”,是重量级、线程不安全的,需要即用即创建。工厂模式完美地封装了SqlSession
的创建细节。
- 体现:
代理模式 (Proxy Pattern) —— 核心
- 体现:这是MyBatis最神奇、最核心的机制。当我们调用
sqlSession.getMapper(UserMapper.class)
时,MyBatis并不是返回一个UserMapper
的实现类,而是通过JDK动态代理,为这个接口动态地创建了一个代理对象 (MapperProxy
)。 - 作用:这个代理对象拦截了我们对接口方法的所有调用。当我们调用
userMapper.getUserById(1)
时,代理对象会根据方法名和参数,找到对应的XML中的SQL语句,然后通过SqlSession
去执行它,并返回结果。这使得我们可以像调用一个本地Java方法一样,来执行数据库操作,极大地简化了编程模型。
- 体现:这是MyBatis最神奇、最核心的机制。当我们调用
第三阶段:SQL的执行与参数/结果处理
模板方法模式 (Template Method Pattern)
- 体现:在SQL的执行器
Executor
体系中。BaseExecutor
是一个抽象类,它定义了查询、更新等操作的标准执行流程(模板),比如查询前先从缓存找,找不到再查数据库。而它的子类,如SimpleExecutor
,ReuseExecutor
等,则去实现其中一些具体的步骤。 - 作用:它为SQL的执行提供了一个可扩展的、统一的骨架。
- 体现:在SQL的执行器
适配器模式 (Adapter Pattern)
- 体现:MyBatis的日志模块 (
Log
)。MyBatis自身不实现具体的日志功能,而是定义了一个统一的Log
接口。然后,它为市面上所有主流的日志框架(如SLF4J, Log4j, JDK Logging等)都提供了一个适配器实现。 - 作用:这使得MyBatis可以无缝地集成到我们项目中正在使用的任何一种日志框架中,而无需改变自身的代码。
- 体现:MyBatis的日志模块 (
其他模式的应用
除了以上几个在核心流程中非常重要的模式,MyBatis还用到了:
- 装饰者模式 (Decorator Pattern):在二级缓存模块中,
Cache
接口有大量的装饰者实现,比如LruCache
,FifoCache
,SynchronizedCache
等,它们像“套娃”一样,层层地为原始缓存对象增加额外的功能(如淘汰策略、同步控制)。 - 单例模式 (Singleton Pattern):MyBatis内部的一些工具类和上下文对象,如
ErrorContext
,被设计为线程内的单例,方便在调用链中传递信息。 - 组合模式 (Composite Pattern):在解析动态SQL时,
<if>
,<where>
,<foreach>
等每一个XML标签都会被解析成一个SqlNode
对象。这些SqlNode
可以互相嵌套,形成一个树形结构,完美地体现了组合模式。
总结一下,MyBatis的源码就像一个“设计模式的博物馆”。它通过建造者模式和工厂模式完成了自身的初始化,通过代理模式提供了神奇的Mapper接口调用,并通过模板方法、适配器、装饰者等大量模式,构建了一个功能强大、高度可扩展的持久层框架。
参考小林coding和JavaGuide