如何学习 MyBatis 框架才算到位?
你是否只会用 MyBatis/MyBatis-Plus写 CURD?
你对 MyBatis 的核心源码了解吗?
面试问你 MyBatis 源码你能回答上来吗?
别说啥啥太卷?卷的永远是技术不到位的人
这篇笔记学会的人,只会学选offer,而不是找不到 offer。
MyBatis 是一个优秀的持久层框架,它内部封装了 JDBC,使得开发者只需要关注 SQL 本身,而不需要花费过多精力去处理如注册驱动、创建连接、创建 statement、手动设置参数、结果集检索等 JDBC 繁杂的过程代码。MyBatis 的核心源码涉及多个关键组件,其中最关键的有8大组件,搞明白,对 MyBatis 的实现原理绝对是通透,以下是 V 哥在上课的时候整理的笔记,分享给大家。注意笔记篇幅比较长,建议收藏慢慢消化。
1、SqlSessionFactoryBuilder:
- SqlSessionFactoryBuilder 负责解析配置文件并构建 SqlSessionFactory。
- 它通常使用 XML 配置文件(mybatis-config.xml)作为输入。
- 在解析过程中,它会创建 Configuration 对象,该对象包含 MyBatis 的所有配置信息。
- 解析完成后,它会调用 SqlSessionFactoryBuilder 的 build 方法来创建 SqlSessionFactory 实例。
SqlSessionFactoryBuilder
是 MyBatis 中用于构建 SqlSessionFactory
的类。它主要负责解析 MyBatis 的配置文件,并基于配置信息构建 SqlSessionFactory
。由于 MyBatis 的源代码文件通常较长,V哥尽量简化并只列出与 SqlSessionFactoryBuilder
相关的关键代码段,并加上注释。
以下是 SqlSessionFactoryBuilder
的代码简化版本:
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.io.Reader;
public class SqlSessionFactoryBuilder {
// 使用XML配置文件构建SqlSessionFactory
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
// 使用XML配置文件构建SqlSessionFactory,并允许传入Environment和Properties
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 使用XML配置构建器创建Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件,返回Configuration对象
Configuration configuration = parser.parse();
// 基于Configuration对象创建SqlSessionFactory
return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
// 关闭读取器
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// 忽略关闭读取器时可能抛出的异常
}
}
}
// 使用InputStream构建SqlSessionFactory
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 使用InputStream构建SqlSessionFactory,并允许传入Environment和Properties
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 使用XML配置构建器创建Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析配置文件,返回Configuration对象
Configuration configuration = parser.parse();
// 基于Configuration对象创建SqlSessionFactory
return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
// 关闭输入流
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// 忽略关闭输入流时可能抛出的异常
}
}
}
// SqlSessionFactoryImpl是SqlSessionFactory的默认实现
private static class SqlSessionFactoryImpl implements SqlSessionFactory {
private final Configuration configuration;
private SqlSessionFactoryImpl(Configuration configuration) {
this.configuration = configuration;
}
// ... 其他方法实现,例如openSession等
}
}
解释:
1、SqlSessionFactoryBuilder
类提供了几个重载的 build
方法,这些方法接收不同的参数(如 Reader
或 InputStream
),用于读取 MyBatis
的配置文件。
2、在每个 build
方法中,首先创建了一个 XMLConfigBuilder
对象,这个对象负责解析 MyBatis 的 XML 配置文件。
3、XMLConfigBuilder
的 parse
方法被调用,它会读取配置文件并构建 Configuration
对象,该对象包含了 MyBatis 的所有配置信息。
4、构建完 Configuration
对象后,使用它创建 SqlSessionFactory
的默认实现 SqlSessionFactoryImpl
的实例。
5、如果在解析配置文件或创建 SqlSessionFactory
的过程中发生异常,会捕获异常并包装为 MyBatis 自定义的异常类型。
6、在方法执行完毕后,无论是否发生异常,都会尝试关闭 Reader
或 InputStream
以释放资源。
7SqlSessionFactoryImpl
是 SqlSessionFactory
接口的一个默认实现,它内部持有 Configuration
对象,并提供了如 openSession
等方法用于创建 SqlSession
。
2、SqlSessionFactory:
SqlSessionFactory
是创建SqlSession
的工厂类。- 它内部持有一个
Configuration
对象,该对象包含了 MyBatis 的所有配置信息。 - 当调用
openSession
方法时,它会根据配置信息创建一个新的SqlSession
实例。
SqlSessionFactory
在 MyBatis 中是一个核心接口,用于生产 SqlSession
对象。通常情况下,我们不会直接实现这个接口,而是使用 SqlSessionFactoryBuilder
来构建它的一个实现类实例。但是,为了解释 SqlSessionFactory
的作用,V哥先展示一个简化的 SqlSessionFactory
接口和其可能的一个实现类的代码。
首先是 SqlSessionFactory
接口的简化版本:
import org.apache.ibatis.session.SqlSession;
public interface SqlSessionFactory {
/**
* 打开一个新的SqlSession。
*
* @return 新的SqlSession实例
* @throws Exception 如果打开SqlSession时出错
*/
SqlSession openSession();
/**
* 打开一个新的SqlSession,并允许传入执行器类型。
*
* @param executorType 执行器类型
* @return 新的SqlSession实例
* @throws Exception 如果打开SqlSession时出错
*/
SqlSession openSession(ExecutorType executorType);
/**
* 打开一个新的SqlSession,并允许传入执行器类型和自动提交参数。
*
* @param executorType 执行器类型
* @param autoCommit 是否自动提交
* @return 新的SqlSession实例
* @throws Exception 如果打开SqlSession时出错
*/
SqlSession openSession(ExecutorType executorType, boolean autoCommit);
/**
* 打开一个新的SqlSession,并允许传入配置属性。
*
* @param properties 配置属性
* @return 新的SqlSession实例
* @throws Exception 如果打开SqlSession时出错
*/
SqlSession openSession(Properties properties);
/**
* 打开一个新的SqlSession,并允许传入执行器类型、自动提交参数和配置属性。
*
* @param executorType 执行器类型
* @param autoCommit 是否自动提交
* @param properties 配置属性
* @return 新的SqlSession实例
* @throws Exception 如果打开SqlSession时出错
*/
SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties);
// ... 可能还有其他方法,如关闭SqlSessionFactory等
}
接下来是一个可能的 SqlSessionFactory
实现类的简化版本(注意:MyBatis 并没有直接提供一个名为 SqlSessionFactoryImpl
的类,V哥这里只是为了演示):
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.Properties;
public class SqlSessionFactoryImpl implements SqlSessionFactory {
private final Configuration configuration;
public SqlSessionFactoryImpl(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSession(ExecutorType.SIMPLE);
}
@Override
public SqlSession openSession(ExecutorType executorType) {
return openSession(executorType, false);
}
@Override
public SqlSession openSession(ExecutorType executorType, boolean autoCommit) {
return openSession(executorType, autoCommit, null);
}
@Override
public SqlSession openSession(Properties properties) {
return openSession(ExecutorType.SIMPLE, properties);
}
@Override
public SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties) {
// 创建Executor实例
Executor executor = configuration.newExecutor(executorType, autoCommit);
// 使用Configuration和Executor创建SqlSession
return new DefaultSqlSession(configuration, executor);
}
// ... 其他方法实现,如关闭SqlSessionFactory等
}
解释:
1、SqlSessionFactory
接口定义了如何打开一个或多个 SqlSession
。SqlSession
是 MyBatis 的核心接口,它提供了执行 SQL 语句和获取映射结果的方法。
2、SqlSessionFactoryImpl
类是 SqlSessionFactory
接口的一个可能实现。在实际应用中,MyBatis 使用了不同的实现类,但原理类似。
3、SqlSessionFactoryImpl
的构造函数接收一个 Configuration
对象,该对象包含了 MyBatis 的所有配置信息,如环境设置、类型别名、映射文件等。
4、openSession
方法有多个重载版本,允许用户指定执行器类型、是否自动提交事务以及配置属性来打开 SqlSession
。这些重载方法最终都会调用一个或多个带有所有参数的 openSession
方法,以便在打开 SqlSession
时应用所有必要的配置。
5、在 openSession
方法中,根据传入的执行器类型 (ExecutorType
) 和是否自动提交 (autoCommit
) 的参数,调用 Configuration
对象的 newExecutor
方法来创建一个新的执行器 (Executor
) 实例。执行器负责管理和执行 SQL
语句。
6、使用 Configuration
和 Executor
实例来创建一个新的 SqlSession
实例。这个 SqlSession
实例会用于执行 SQL
语句、获取映射结果以及管理数据库事务。
7、在实际应用中,SqlSessionFactory
通常通过 SqlSessionFactoryBuilder
构建。SqlSessionFactoryBuilder
会读取 MyBatis 的配置文件(通常是 XML
格式),解析配置信息,并创建一个 Configuration
对象。然后,使用这个 Configuration
对象来创建一个 SqlSessionFactory
实例。
8、SqlSessionFactory
是线程安全的,一旦创建,就可以在整个应用程序中重用。通常,每个应用程序只需要一个 SqlSessionFactory
实例。
9、SqlSession
则是非线程安全的,因此不应该在多个线程之间共享。每个线程应该有自己的 SqlSession
实例。使用完 SqlSession
后,应该调用其 close 方法来释放资源。
10、SqlSessionFactory
和 SqlSession
的设计符合了工厂模式和单例模式的思想。SqlSessionFactory
负责生产 SqlSession
,而 SqlSession
则负责执行具体的数据库操作。
上面的代码示例是一个简化的版本,用于解释 SqlSessionFactory 和其实现类的基本概念和工作原理。
3、SqlSession:
SqlSession
是执行SQL
的核心接口。- 它通过
Executor
来执行SQL
语句。 - 当调用
selectOne
、selectList
、insert
、update
、delete
等方法时,实际上会调用Executor
的相应方法。 SqlSession
也负责事务的管理,例如提交或回滚事务。
当涉及到 SqlSession
的源代码时,实际上 MyBatis 框架的源代码包含了多个与 SqlSession
相关的类,例如 DefaultSqlSession
,这是 SqlSession
接口的一个常见实现。以下是一个简化的 DefaultSqlSession
类的示例,V 哥会在代码中加入注释来解释它的作用和功能:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
import java.util.Map;
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// 根据statement和parameter获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 创建StatementHandler
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 使用Executor执行查询,并返回结果
return executor.query(ms, statementHandler);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 类似selectOne,但返回结果是List
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
return executor.query(ms, statementHandler, RowBounds.DEFAULT, ResultHandler.DEFAULT_RESULT_HANDLER);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// 与上一个selectList方法类似,但允许传入RowBounds以进行分页查询
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, rowBounds, null, null);
return executor.query(ms, statementHandler, rowBounds, ResultHandler.DEFAULT_RESULT_HANDLER);
}
@Override
public void select(String statement, Object parameter, ResultHandler resultHandler) {
// 执行查询,并将结果传递给ResultHandler进行处理
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
executor.query(ms, statementHandler, RowBounds.DEFAULT, resultHandler);
}
@Override
public int insert(String statement, Object parameter) {
// 执行插入操作,并返回影响的记录数
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
return executor.update(ms, statementHandler);
}
@Override
public int update(String statement, Object parameter) {
// 执行更新操作,并返回影响的记录数
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
return executor.update(ms, statementHandler);
}
@Override
public int delete(String statement, Object parameter) {
// 执行删除操作,并返回影响的记录数
MappedStatement ms = configuration.getMappedStatement(statement);
StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
return executor.update(ms, statementHandler);
}
@Override
public <T> T getMapper(Class<T> type) {
// 获取Mapper接口的代理实现
return configuration.getMapper(type, this);
}
// ... 可能还有其他方法,如提交事务、回滚事务、关闭SqlSession等
@Override
public void close() {
// 清理资源,
// 关闭SqlSession
}
// ... 省略其他可能的方法和细节
上面的代码片段是 DefaultSqlSession
的简化版本,用于解释 SqlSession
的一些基本操作。下面V 哥将对关键部分进行解释:
构造器:
DefaultSqlSession
的构造器接受一个Configuration
对象和一个Executor
对象。Configuration
对象包含了 MyBatis 的所有配置信息,而Executor
对象则负责执行 SQL 语句。
查询方法:
selectOne
,selectList
,select
等方法用于执行查询操作。它们首先从Configuration
中获取与提供的 SQL 语句标识符对应的MappedStatement
,然后创建一个StatementHandler
来处理 SQL 语句的生成和参数绑定。最后,它们使用Executor
来执行查询并返回结果。
增删改方法:
insert
,update
,delete
等方法用于执行插入、更新和删除操作。它们与查询方法类似,但返回的是受影响的记录数。
获取Mapper:
getMapper
方法用于获取一个 Mapper 接口的代理实现。这允许你直接使用接口调用方法而无需手动创建和配置代理。
关闭SqlSession:
close
方法用于关闭SqlSession
,释放相关资源。
需要注意的是,SqlSession
是线程不安全的,因此通常每个线程都应该有自己的 SqlSession
实例。同时,SqlSession
的使用通常遵循“打开-执行-关闭”的模式,以确保资源的正确释放。
在实际应用中,你通常不会直接创建 DefaultSqlSession
的实例,而是使用 SqlSessionFactory
来创建 SqlSession
。SqlSessionFactory
负责根据配置创建 SqlSession
实例,并管理相关的资源。
希望这些注释和解释能够帮助你理解 SqlSession
的作用和工作原理。如果需要更深入的理解,建议阅读 MyBatis 的官方文档和源代码。
4、Mapper 接口及其实现:
Mapper
接口是开发者定义的,用于描述数据库操作。MyBatis
使用JDK
动态代理为Mapper
接口创建代理对象。- 当调用
Mapper
接口的方法时,代理对象会拦截调用,并转换为SQL
语句的执行。 - 这个转换过程涉及
MapperStatement
的查找和解析,以及参数和结果的映射。
在 MyBatis 中,Mapper
接口通常没有直接的实现类,而是通过 MyBatis 的动态代理机制自动生成代理对象。Mapper
接口定义了与数据库操作相关的方法,而 MyBatis 会根据这些方法自动生成相应的 SQL 语句并执行。
下面是一个简单的 Mapper
接口示例及其注释:
// 定义一个 Mapper 接口,用于映射数据库操作
public interface UserMapper {
// 根据 ID 查询用户信息
// @Select 注解用于指定查询的 SQL 语句
// #{id} 是参数占位符,表示方法参数
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(int id);
// 插入用户信息
// @Insert 注解用于指定插入的 SQL 语句
// 使用 @Options 注解可以配置插入操作的一些选项,比如是否使用生成的键等
@Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
// 更新用户信息
// @Update 注解用于指定更新的 SQL 语句
@Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}")
int updateUser(User user);
// 删除用户信息
// @Delete 注解用于指定删除的 SQL 语句
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(int id);
// 查询所有用户信息
// @Select 注解指定查询所有用户的 SQL 语句
@Select("SELECT * FROM user")
List<User> selectAllUsers();
// 其他的数据库操作方法...
}
解释:
接口定义: UserMapper
是一个接口,它定义了与 user
表相关的数据库操作。
注解: MyBatis 提供了注解(如 @Select
、@Insert
、@Update
、@Delete
)来简化 SQL
语句的编写。这些注解允许你在接口方法上直接指定 SQL 语句。
参数占位符: 在 SQL
语句中,#{id}、#{name}、#{age}
等是参数占位符,它们会在运行时被方法参数的实际值替换。
自动映射: MyBatis 会自动将查询结果映射到 User
类型的对象上,前提是你的 User
类的属性名称和数据库表的列名能够对应上。
动态代理: 当你在 MyBatis 的 SqlSession
中调用 getMapper(UserMapper.class)
时,MyBatis 会根据 UserMapper
接口动态生成一个代理对象。这个代理对象会在运行时拦截方法调用,并自动执行相应的 SQL
语句。
选项配置: @Options
注解用于配置 SQL
语句执行的一些选项。例如,在插入操作中,useGeneratedKeys = true
表示使用数据库自动生成的主键,keyProperty = "id"
指定将生成的主键设置到 User
对象的 id
属性上。
返回类型: 方法的返回类型通常与 SQL
语句的执行结果相对应。例如,查询单个用户返回 User
对象,查询多个用户返回 List<User>
。
在实际应用中,你通常不需要手动编写 Mapper
接口的实现类。你只需要定义接口,并在 XML
映射文件(如果不使用注解)或注解中编写 SQL
语句。MyBatis 会负责接口的动态代理实现和 SQL
语句的执行。这大大简化了数据库操作的开发过程。
5、MappedStatement:
- MappedStatement 是 MyBatis 内部表示一个 SQL 映射语句的对象。
- 它包含 SQL 语句、参数类型、结果映射等信息。
- 当 MyBatis 解析 Mapper XML 文件时,会为每个 SQL 语句创建一个 MappedStatement 对象,并存储在 Configuration 对象中。
- 执行 SQL 时,MyBatis 会根据方法签名或 ID 查找对应的 MappedStatement。
MappedStatement
是 MyBatis 中的一个核心类,它代表了一个映射语句,即一个 SQL
语句及其相关的配置信息。在 MyBatis 中,MappedStatement
对象是由 MyBatis 在解析 XML
映射文件或注解时创建的,并存储在 Configuration
对象中。
由于 MappedStatement
是 MyBatis 内部使用的核心类,其实现细节和源代码通常较为复杂,不适合在这里完整地列出。不过,我可以为你提供一个简化版的 MappedStatement
类结构,并添加必要的注释来解释其主要组成部分。
请注意,以下代码仅用于解释目的,帮助你更好的理解:
// MappedStatement 类简化版,用于解释其主要组成部分
public class MappedStatement {
// 映射语句的唯一标识符
private String id;
// 映射语句对应的 SQL 语句
private String sql;
// 映射语句的类型(SELECT, INSERT, UPDATE, DELETE)
private SqlCommandType sqlCommandType;
// 参数类型,即传递给 SQL 语句的参数的类型
private Class<?> parameterType;
// 结果类型,即 SQL 语句执行后返回的结果的类型
private Class<?> resultType;
// 语句的结果映射配置
private ResultMap resultMap;
// 语句使用的数据库 ID(用于分库分表等情况)
private String databaseId;
// 语句使用的参数处理器类型
private Class<? extends ParameterHandler> parameterHandlerType;
// 语句使用的结果处理器类型
private Class<? extends ResultHandler> resultHandlerType;
// 语句使用的 SQL 语句解析器类型
private Class<? extends StatementHandler> statementHandlerType;
// 语句使用的绑定器类型
private Class<? extends TypeHandler> boundSqlTypeHandler;
// 语句的插件列表
private List<Interceptor> interceptors;
// ... 可能还有其他字段和方法
// 构造函数(通常不是直接创建的,而是通过 MyBatis 的内部机制)
public MappedStatement(String id, String sql, SqlCommandType sqlCommandType, Class<?> parameterType,
Class<?> resultType, ResultMap resultMap, String databaseId,
Class<? extends ParameterHandler> parameterHandlerType,
Class<? extends ResultHandler> resultHandlerType,
Class<? extends StatementHandler> statementHandlerType,
Class<? extends TypeHandler> boundSqlTypeHandler,
List<Interceptor> interceptors) {
this.id = id;
this.sql = sql;
this.sqlCommandType = sqlCommandType;
this.parameterType = parameterType;
this.resultType = resultType;
this.resultMap = resultMap;
this.databaseId = databaseId;
this.parameterHandlerType = parameterHandlerType;
this.resultHandlerType = resultHandlerType;
this.statementHandlerType = statementHandlerType;
this.boundSqlTypeHandler = boundSqlTypeHandler;
this.interceptors = interceptors;
}
// Getter 和 Setter 方法省略...
// ... 可能还有其他方法,如执行 SQL 语句、获取绑定参数等
}
解释:
1、标识符 id
: 每个 MappedStatement
对象都有一个唯一的标识符,它通常对应于 Mapper
接口中的一个方法名。
2、SQL
语句 sql
: 存储了具体的 SQL
语句字符串。
3、语句类型 sqlCommandType
: 表示这个映射语句是查询、插入、更新还是删除操作。
4、参数类型 parameterType
和结果类型 resultType
: 分别表示传递给 SQL
语句的参数类型和 SQL
语句执行后返回的结果类型。
5、结果映射 resultMap
: 用于复杂结果集的映射配置。
6、数据库 ID
databaseId
: 用于分库分表等高级功能。
7、处理器类型: 包括参数处理器 parameterHandlerType
、结果处理器 resultHandlerType
、语句处理器 statementHandlerType
和绑定器 boundSqlTypeHandler
,它们都是用于处理 SQL
语句执行过程中不同阶段的任务的类型。
8、插件列表 interceptors
: 存储了应用于这个映射语句的插件列表,插件可以用于拦截和修改 SQL
语句的执行过程。
在实际应用中,MappedStatement
对象是由 MyBatis 在启动时解析 XML
映射文件或注解时创建的,并存储在 Configuration
对象中。当执行数据库操作时,MyBatis 会根据 Mapper
接口方法的名称查找对应的 MappedStatement
对象,并使用其中的信息来构建和执行 SQL
语句。
由于 MappedStatement
是 MyBatis内部实现的一部分,它的具体细节可能会随着 MyBatis
的版本更新而有所变化。然而,其核心功能和设计原则通常保持一致:为 SQL
映射语句提供元数据信息和运行时环境。
在实际的 MyBatis 实现中,MappedStatement
类通常包含更多的字段和方法,用于处理更复杂的场景,比如动态 SQL
、缓存配置、结果集映射、条件分支等等。它通常还与 MyBatis 的其他关键组件如 SqlSession
、Executor
、StatementHandler
等紧密协作,以完成 SQL
语句的执行和结果处理。
当你使用 MyBatis 时,你通常不需要直接创建或操作 MappedStatement
对象。相反,你会通过定义 Mapper
接口和 XML
映射文件来声明你的 SQL
映射语句,然后 MyBatis 会自动为你处理 MappedStatement
的创建和管理。
6、Executor:
- Executor 是 SQL 语句执行的核心。
- 它有三个实现类:SimpleExecutor、ReuseExecutor 和 BatchExecutor,分别对应不同的执行策略。
- Executor 负责与 JDBC 交互,包括创建 PreparedStatement、设置参数、执行 SQL、处理结果等。
- 它会使用 TypeHandler 来处理参数和结果集的转换。
由于 Executor
类是 MyBatis 框架中的核心组件,其源代码相对较长且涉及多个内部类和复杂逻辑。在这里,我将为你提供一个简化版的 Executor
类及其部分实现,并添加必要的注释来解释其主要功能。
// Executor接口,定义了执行SQL语句的方法
public interface Executor {
// 执行更新操作(插入、更新、删除)
int update(MappedStatement ms, Object parameter);
// 执行查询操作,返回结果列表
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);
// 执行查询操作,返回单个结果
<E> E query(MappedStatement ms, Object parameter, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);
// 执行查询操作,返回结果集游标
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);
// 刷新缓存
void flushStatements();
// 关闭Executor,释放资源
void close(boolean forceClose);
// 是否已关闭
boolean isClosed();
// 获取事务对象
Transaction getTransaction();
// 延迟加载是否开启
boolean isLazyLoadEnabled();
// 设置延迟加载是否开启
void setLazyLoadEnabled(boolean lazyLoadEnabled);
}
// BaseExecutor类,Executor接口的一个基础实现类
public abstract class BaseExecutor implements Executor {
protected final Configuration configuration;
protected final Transaction transaction;
protected ErrorContext errorContext;
public BaseExecutor(Configuration configuration, Transaction transaction) {
this.configuration = configuration;
this.transaction = transaction;
this.errorContext = new ErrorContext();
}
// 省略其他方法...
// 更新操作实现
@Override
public int update(MappedStatement ms, Object parameter) {
// ... 更新操作的实现逻辑,包括预处理语句、设置参数、执行更新等
}
// 查询操作实现(返回结果列表)
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) {
// ... 查询操作的实现逻辑,包括预处理语句、设置参数、执行查询、处理结果集等
}
// ... 其他方法的实现...
}
// SimpleExecutor类,BaseExecutor的一个简单实现,用于执行SQL语句
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 更新操作实现(继承自BaseExecutor)
@Override
public int update(MappedStatement ms, Object parameter) {
// 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现
return super.update(ms, parameter);
}
// 查询操作实现(继承自BaseExecutor)
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) {
// 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现
return super.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
// ... 其他方法的实现...
}
解释:
1、Executor
接口: 定义了执行 SQL
语句所需的方法,包括更新、查询等。它是 MyBatis 中执行器模式的核心部分,允许不同的执行策略(如批处理、重用预处理语句等)通过不同的实现类来实现。
2、BaseExecutor
类: 是 Executor
接口的一个基础实现类,提供了执行器的一些通用逻辑。它通常包含配置信息、事务对象和错误上下文等成员变量。BaseExecutor
提供了对 SQL
语句执行的基础支持,但具体的执行逻辑可能由其子类实现。
3、SimpleExecutor
类: 是 BaseExecutor
的一个具体实现,它可能不包含复杂的逻辑或优化,但提供了基本的 SQL
执行功能。在实际应用中,MyBatis 可能提供了更多的执行器实现类,比如 ReuseExecutor
用于重用预处理语句,BatchExecutor
用于批量执行等。
在 MyBatis 的实际实现中,Executor
类及其实现通常包含更多的成员变量、方法和复杂的逻辑,以处理SQL
语句的解析、参数绑定、结果映射以及缓存等高级功能。此外,Executor
类通常还会与其他组件如 StatementHandler
、ParameterHandler
、ResultSetHandler
和 TypeHandler
等紧密合作,以构建和执行完整的SQL
执行流程。
下面,我将进一步解释 Executor
及其实现类在 MyBatis 中的一些核心功能:
1、SQL解析与绑定:
Executor
接收MappedStatement
作为输入,该对象包含了SQL
语句的元数据信息。- 使用
ParameterHandler
处理参数绑定,将用户提供的参数转换为JDBC
可以理解的格式,并设置到预处理语句中。
2、执行SQL语句:
- 调用
JDBC
的Statement
或PreparedStatement
执行SQL
语句。 Executor
可能管理自己的预处理语句缓存,以提高性能。
3、结果处理:
- 使用
ResultSetHandler
将JDBC
的ResultSet
转换为Java
对象列表。 - 涉及类型转换和结果映射,使用
TypeHandler
来处理字段类型和Java
类型之间的转换。
4、事务管理:
Executor
通常与事务管理对象(如Transaction
)一起工作,以确保SQL操作在事务的上下文中执行。- 负责提交或回滚事务,以处理成功或失败的
SQL
操作。
5、缓存管理:
- MyBatis 提供了一级缓存和二级缓存机制,
Executor
负责管理这些缓存。 - 在执行查询时,首先检查缓存中是否有结果,如果有则直接返回,避免重复执行
SQL
。
6、延迟加载:
- MyBatis 支持延迟加载,即当需要时才加载关联数据。
Executor
需要在适当的时候触发延迟加载的执行。
MyBatis 提供了多种 Executor
实现类,它们之间的主要差异在于执行策略和资源管理:
SIMPLE
: 最基本的实现,每次执行都创建一个新的预处理语句。
REUSE
: 重用预处理语句,以减少JDBC对象的创建和销毁开销。
BATCH
: 批量执行SQL语句,适用于大量数据的插入、更新或删除操作。
每种实现都有其特定的使用场景和性能特点,用户可以根据应用的需求选择合适的实现。
Executor
是 MyBatis 框架中的核心组件之一,它负责执行SQL
语句并处理结果。通过不同的实现类,MyBatis 提供了灵活的执行策略,以满足不同应用场景的性能需求。在实际应用中,用户通常不需要直接创建或管理 Executor
对象,而是通过配置和使用 MyBatis 的 API
来间接使用它。
7、TypeHandler:
TypeHandler
是 Java 类型和JDBC
类型之间的桥梁。- MyBatis 提供了一系列内置的
TypeHandler
,如StringTypeHandler
、IntegerTypeHandler
等。 - 当需要自定义类型转换时,开发者可以实现自己的
TypeHandler
。 TypeHandler
负责将 Java 对象转换为JDBC
参数,以及将 JDBC 结果集转换为 Java 对象。
TypeHandler
是 MyBatis 中一个非常核心的组件,它负责 Java 类型和 JDBC
类型之间的转换。TypeHandler
定义了类型转换的接口,并提供了一些基础实现。以下是一个简化版的 TypeHandler
接口及其一个实现类的示例,并附带注释来解释其主要功能。
// TypeHandler接口,定义了类型转换的方法
public interface TypeHandler<T> {
// 设置参数值
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 从结果集中获取值
T getResult(ResultSet rs, String columnName) throws SQLException;
// 从结果集中获取值(使用列索引)
T getResult(ResultSet rs, int columnIndex) throws SQLException;
// 从CallableStatement中获取值
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
// BaseTypeHandler类,TypeHandler的一个基础实现类,提供了默认的类型转换逻辑
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
// 设置参数值(默认实现,子类可覆盖)
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
ps.setNull(i, jdbcType.TYPE_CODE);
} else {
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// 设置非空参数值(子类需要实现这个方法)
protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 从结果集中获取值(默认实现,子类可覆盖)
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
return getResult(rs, rs.findColumn(columnName));
}
// 从结果集中获取值(默认实现,子类需要实现这个方法)
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
return getNullableResult(rs, columnIndex);
}
// 从CallableStatement中获取值(默认实现,子类需要实现这个方法)
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
return getNullableResult(cs, columnIndex);
}
// 获取非空结果(子类需要实现这个方法)
protected abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
// 获取非空结果(子类需要实现这个方法)
protected abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
// 获取非空结果(子类需要实现这个方法)
protected abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
// IntegerTypeHandler类,TypeHandler的一个具体实现,用于处理Integer类型的转换
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
// 设置非空参数值
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter);
}
// 从结果集中获取非空Integer值
@Override
protected Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName);
}
// 从结果集中获取非空Integer值(使用列索引)
@Override
protected Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getInt(columnIndex);
}
// 从CallableStatement中获取非空Integer值
@Override
protected Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getInt(columnIndex);
}
}
解释:
1、TypeHandler 接口:
setParameter
方法:将 Java 类型的参数设置到PreparedStatement
对象中,以便执行SQL
语句。getResult
方法:从ResultSet
或CallableStatement
对象中获取指定列的结果,并将其转换为 Java 类型。
2、BaseTypeHandler 抽象类:
setParameter
方法提供了默认实现,用于处理null
值和JDBC
类型的设置。setNonNullParameter
是一个抽象方法,子类需要实现,用于处理非空值的设置。getResult
方法也提供了默认实现,它们通常调用getNullableResult
抽象方法,子类需要实现具体的转换逻辑。
3、IntegerTypeHandler 类:
- 继承自
BaseTypeHandler<Integer>
,专门用于处理Integer
类型的转换。 - 实现了
setNonNullParameter
方法,用于将Integer
类型的参数设置到PreparedStatement
中。 - 实现了
getNullableResult
方法的三个重载版本,用于从ResultSet
或CallableStatement
中获取Integer
类型的结果。
使用场景:
当 MyBatis 执行 SQL
语句时,它需要根据 Java 类型的参数和 SQL
查询的结果来设置参数和获取结果。这时,MyBatis 会查找合适的 TypeHandler
来执行这些类型转换。如果 MyBatis 提供了现成的 TypeHandler
(如 IntegerTypeHandler
),它可以直接使用。如果没有现成的 TypeHandler
,用户也可以自定义 TypeHandler
来处理特殊的类型转换逻辑。
TypeHandler
接口及其实现类在 MyBatis 中扮演了非常重要的角色,它们负责在 Java 类型和 JDBC 类型之间进行转换,使得 MyBatis 能够灵活地处理各种类型的参数和结果集。通过自定义 TypeHandler
,用户可以扩展 MyBatis 的类型转换能力,以满足不同的业务需求。
8、Plugin:
Plugin
是 MyBatis 的插件机制,允许开发者在核心流程中插入自定义逻辑。- 插件通过实现
Interceptor
接口并覆盖intercept
方法来定义自己的拦截逻辑。 - 插件在 MyBatis 初始化时通过
Plugin
类进行包装,并插入到目标对象的代理链中。 - 当目标对象的方法被调用时,插件的拦截逻辑会先被执行。
Plugin
类在 MyBatis 中通常用于拦截和修改 MyBatis 的核心行为。它允许用户在不修改 MyBatis 核心代码的情况下,对 SQL
语句的生成、参数设置、结果集处理等过程进行自定义处理。以下是一个简化版的 Plugin
类及其实现,并附带注释来解释其主要功能。
// Plugin接口,定义插件需要实现的方法
public interface Plugin {
// 包裹目标对象,返回一个被拦截对象
Object wrap(Object target);
// 获取插件的属性
Class<?> getType();
// 获取插件的处理程序
Interceptor getInterceptor();
// 插件是否可以被用于目标对象
boolean isTarget(Object target);
// 静态方法,用于生成插件实例
static Object wrap(Object target, Interceptor interceptor, Class<?> type) {
// 创建Plugin对象
Plugin plugin = new Plugin(target, interceptor, type);
// 返回被拦截的目标对象
return plugin.wrap(target);
}
// Plugin类的私有构造器,防止外部直接实例化
private Plugin(Object target, Interceptor interceptor, Class<?> type) {
// 初始化成员变量
this.target = target;
this.interceptor = interceptor;
this.type = type;
}
// 成员变量
private Object target;
private Interceptor interceptor;
private Class<?> type;
}
// Interceptor接口,定义插件需要实现的拦截方法
public interface Interceptor {
// 插件在MyBatis初始化时调用
void intercept(Invocation invocation) throws Throwable;
// 插件的ID,用于唯一标识插件
Object plugin(Object target);
// 插件的属性集合
void setProperties(Properties properties);
}
// 假设我们有一个实现Interceptor接口的自定义插件
public class MyCustomPlugin implements Interceptor {
// 插件的属性
private String someProperty;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在这里编写拦截逻辑
// 例如,可以修改SQL语句、参数等
System.out.println("Intercepted method: " + invocation.getMethod().getName());
// 继续执行原始逻辑
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 在这里可以对目标对象进行包装或处理
return Plugin.wrap(target, this, MyCustomPlugin.class);
}
@Override
public void setProperties(Properties properties) {
// 设置插件的属性
this.someProperty = properties.getProperty("someProperty");
}
}
解释:
1、Plugin 接口:
wrap(Object target)
: 这是一个用于包装目标对象的方法,通常会在插件初始化时被调用,返回被包装后的对象,这个对象会代理目标对象的行为,并在必要时插入拦截逻辑。getType()
: 返回插件的类类型。getInterceptor()
: 返回插件的拦截器实现。isTarget(Object target)
: 判断插件是否适用于目标对象。wrap(Object target, Interceptor interceptor, Class<?> type)
: 这是一个静态方法,用于创建并返回 Plugin 实例,同时完成目标对象的包装。
2、Interceptor 接口:
intercept(Invocation invocation)
: 这是插件的核心方法,当目标对象的方法被调用时,这个方法会被执行。在这里,你可以编写自定义的拦截逻辑。plugin(Object target)
: 这是一个用于包装目标对象的方法,返回包装后的对象。在 MyBatis 中,这个方法通常与Plugin
接口的wrap
方法结合使用,以创建代理对象。setProperties(Properties properties)
: 这是一个设置插件属性的方法,MyBatis 在配置插件时会调用此方法。
3、MyCustomPlugin 类:
- 这个类实现了
Interceptor
接口,是自定义插件的具体实现。 - 在
intercept
方法中,你可以编写拦截目标对象方法执行的代码,例如修改SQL
语句、修改参数等。 plugin
方法返回包装后的目标对象,通常直接调用Plugin.wrap
方法。setProperties
方法用于设置插件的配置属性。
使用场景:
当你在 MyBatis 中需要修改 SQL 语句、参数设置或结果集处理时,你可以编写一个自定义的 Interceptor
实现,并使用 Plugin
接口来包装目标对象,从而在不修改 MyBatis 核心代码的情况下扩展其功能。在 MyBatis 的配置文件中配置插件后,MyBatis 会在启动时加载插件。
Plugin 类的使用:
在 MyBatis 中,Plugin 类的使用通常涉及到以下步骤:
1、编写自定义插件:
- 创建一个类实现
Interceptor
接口,实现其中的intercept
、plugin
和setProperties
方法。 - 在
intercept
方法中编写拦截逻辑,比如修改SQL
语句、参数或处理结果集。 - 在
plugin
方法中调用Plugin.wrap
方法包装目标对象。 - 在
setProperties
方法中处理插件配置属性。
2、配置插件:
- 在 MyBatis 的配置文件(通常是
mybatis-config.xml
)中,使用<plugins>
元素配置插件。 - 在
<plugin>
子元素中指定插件的interceptor
实现类,以及可能的属性。
3、启动 MyBatis:
- 当 MyBatis 启动时,它会加载并初始化配置的插件。
- 插件的
intercept
方法会在相应的方法调用时被触发。
示例配置:
在 mybatis-config.xml
配置文件中配置自定义插件:
<configuration>
<!-- 其他配置 -->
<plugins>
<plugin interceptor="com.example.MyCustomPlugin">
<property name="someProperty" value="someValue"/>
</plugin>
</plugins>
<!-- 其他配置 -->
</configuration>
Plugin 类的实现细节:
在 Plugin
类的实现中,通常会使用动态代理技术来包装目标对象。当目标对象的方法被调用时,动态代理会拦截调用,并首先执行插件的拦截逻辑,然后再调用原始方法。
Plugin 类中的 wrap
方法通常利用 Java 的反射 API
和动态代理(例如 JDK
动态代理或 CGLIB
)来创建目标对象的代理。代理对象会实现目标对象的接口,并在调用方法时执行拦截逻辑。
注意:
- 插件的
intercept
方法必须谨慎处理,避免引入性能问题或破坏 MyBatis 的行为。 - 插件的
plugin
方法必须正确处理目标对象,确保返回的是正确的代理对象。 - 插件的
setProperties
方法应该能够处理所有必要的配置属性,并在需要时验证它们的值。
Plugin
类在 MyBatis 中是一个非常重要的机制,它允许用户在不修改 MyBatis 核心代码的情况下扩展其功能。通过编写自定义的 Interceptor
实现,并正确配置插件,用户可以拦截和修改 MyBatis 的行为,以满足特定的业务需求。在实际应用中,需要深入理解 MyBatis 的内部机制和动态代理技术,才能有效地使用 Plugin
类来扩展 MyBatis 的功能。
最后的最后
好了以上 是V哥给大家整理的8大核心组件的全部内容,为什么说选择 Java 就是选择未来,真正爱 Java 的人,一定喜欢深入研究,学习源码只是第一步,要有一杆子捅到操作系统才够刺激。
今天的内容就分享到这里,欢迎留言与 V 哥一起讨论交流。