欢迎光临
我们一直在努力

MyBatis详解

一、从JDBC的痛点说起

回顾我们之前学的JDBC,写一个简单的查询需要多少行代码?

public User getUserById(Long id) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root");
String sql = "SELECT * FROM user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
}
return null;
} finally {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
}
}

看着这一大段代码,你会发现一个问题:真正和业务相关的只有三行——SQL语句、设置参数、处理结果。其他全是模板代码:获取连接、关闭连接、处理异常、释放资源。

这只是一个最简单的查询,如果项目里有100张表,每张表都要写这样的代码,那得重复多少次?而且每次都要小心翼翼地记得在finally里关闭资源,一旦忘记,数据库连接就被占着不释放,系统迟早崩溃。

于是,MyBatis诞生了。它的核心理念很简单:把那些重复的、容易出错的模板代码都封装起来,让开发者只关心SQL和结果映射。

二、JDBC vs MyBatis:一张表看懂差距

维度JDBCMyBatis
连接管理 手动获取、手动关闭 SqlSession自动管理
参数设置 手动setXXX() #{}占位符自动绑定
结果映射 手动遍历ResultSet赋值 自动映射到对象
资源释放 finally里手动close try-with-resources自动关闭
SQL位置 混在Java代码里 分离到XML或注解
动态SQL 手动拼接字符串 标签支持(if/where/foreach)

三、MyBatis的核心组件

在深入执行流程之前,先认识几个关键组件:

  • SqlSessionFactory:整个应用的“心脏”,负责创建SqlSession。一个应用只需要一个实例(单例)。

  • SqlSession:相当于JDBC的Connection,代表一次数据库会话。它不是线程安全的,用完必须关闭。

  • Executor:执行器,真正干活的。负责SQL执行、缓存管理、事务控制。

  • MappedStatement:封装了XML里配置的SQL语句、参数类型、返回类型等信息。

四、MyBatis执行流程

我们用一个最简单的查询来跟踪MyBatis的执行流程:

4.1 第一步:读取配置文件

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

mybatis-config.xml里配置了数据库连接信息、事务管理器、以及Mapper映射文件的位置。 如果对于MyBatis配置有疑问可以参考我之前的文章(https://mp.csdn.net/mp_blog/creation/editor/157480246)

4.2 第二步:创建SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一步内部做了很多事情:

  • 解析mybatis-config.xml里的所有配置

  • 解析environments(数据库环境)、settings(全局设置)、mappers(映射器)等标签

  • 把解析结果封装成一个Configuration对象(全局配置)

  • 用这个Configuration对象创建DefaultSqlSessionFactory

 相当于JDBC里加载驱动、准备连接信息的那一步,但MyBatis把它做成了“一次解析,永久使用”。

4.3 第三步:打开SqlSession(获取连接)

SqlSession sqlSession = sqlSessionFactory.openSession();

这一步是关键。看一下openSession()内部做了什么:

  • 从Configuration里获取配置好的Executor类型(默认是SimpleExecutor)

  • 从Configuration里获取Environment,再从Environment里获取DataSource

  • 从DataSource获取数据库连接(如果开启了事务,还会设置autoCommit=false)

  • 创建一个Executor(执行器),把这个连接包装进去

  • 用这个Executor创建DefaultSqlSession

 对应DriverManager.getConnection()——获取数据库连接。

4.4 第四步:获取Mapper接口(动态代理)

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

这一步是MyBatis的精髓。UserMapper是一个接口,我们没有写实现类,那这个对象是怎么来的?答案是动态代理。

getMapper()的调用链:

  • DefaultSqlSession → Configuration → MapperRegistry

  • MapperRegistry里有个knownMappers的Map,存着所有Mapper接口的代理工厂

  • 根据接口类型取出对应的MapperProxyFactory

  • 代理工厂创建MapperProxy(实现了InvocationHandler)

  • 返回代理对象

当我们调用userMapper.getById(1L)时,实际调用的是MapperProxy的invoke方法。

 这一步在JDBC里没有直接对应,它是MyBatis为了让我们不用写实现类而做的封装。

4.5 第五步:执行SQL

User user = userMapper.getById(1L);

看看这一行背后发生了什么:

MapperProxy.invoke():

  • 根据调用的方法,创建MapperMethod对象(如果缓存里没有的话)

  • MapperMethod里有两个内部类:SqlCommand(记录SQL的id和类型)和MethodSignature(记录方法信息)

  • 调用MapperMethod.execute()

MapperMethod.execute():

  • 根据SQL类型决定调用SqlSession的哪个方法

  • 这里调用的是sqlSession.selectOne()

DefaultSqlSession.selectOne():

  • 根据statementId(比如"UserMapper.getById")从Configuration里取出MappedStatement

  • 把查询交给Executor去执行

Executor.query()(以SimpleExecutor为例):

  • 检查一级缓存(Local Cache)里有没有

  • 如果没有,调用doQuery()去数据库查

  • 创建StatementHandler

  • StatementHandler里包含ParameterHandler(处理参数)和ResultSetHandler(处理结果)

  • 通过ParameterHandler设置参数(对应JDBC的pstmt.setXXX())

  • 执行JDBC的PreparedStatement.executeQuery()

  • 通过ResultSetHandler处理ResultSet,把结果映射成对象(对应JDBC的rs.getXXX()和user.setXXX())

 对应pstmt.executeQuery() + rs.next() + 手动赋值。

4.6 第六步:提交事务(如果需要)

如果执行的是修改操作(insert/update/delete),或者你想手动控制事务:

sqlSession.commit(); // 提交事务

MyBatis的事务默认是手动提交模式(autoCommit=false)。也就是说,执行完增删改后,必须显式调用commit(),否则数据不会真正写入数据库。

commit()内部做了什么:

  • 调用Executor的commit()方法

  • Executor拿到它内部包装的Transaction对象

  • Transaction对象拿到真正的JDBC Connection

  • 调用connection.commit()

4.7 第七步:回滚事务

try {
// 执行操作
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback(); // 回滚事务
throw e;
}

rollback()的内部流程:

  • 调用Executor的rollback()方法

  • Executor拿到Transaction对象

  • Transaction拿到JDBC Connection

  • 调用connection.rollback()

4.8 第八步:关闭SqlSession(释放资源)

sqlSession.close();

 对应finally块里那一堆rs.close()、pstmt.close()、conn.close()。

五、总结

回过头看,MyBatis并没有发明新的东西,它只是把JDBC里那些重复的、繁琐的、容易出错的操作都封装起来了:

  • 连接管理 → SqlSessionFactory和SqlSession

  • 参数设置 → #{}占位符

  • 结果映射 → 反射自动赋值

  • 资源释放 → try-with-resources自动close

  • 事务控制 → commit()和rollback()方法

可能会有疑问:用MyBatis是不是比直接写JDBC慢?确实,多了一层封装,理论上会有一点点性能损耗。但这个损耗跟开发效率、代码可维护性比起来,完全可以接受。再说了,MyBatis内部最终还是调用JDBC,那点损耗微乎其微。

最重要的是,用MyBatis之后,我们可以把精力集中在业务逻辑和SQL优化上,而不是跟那些模板代码死磕。


以上是我对MyBatis执行流程的学习理解,如果有不准确的地方,欢迎指正。

赞(0)
未经允许不得转载:171主机测评 » MyBatis详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址