剖析doocs/source-code-hunter中的MyBatis基础支持层
本文深入解析MyBatis基础支持层的核心组件,包括反射工具箱、TypeHandler实现、数据源与事务模块设计、缓存模块原理以及binding模块的作用与实现。反射工具箱通过Reflector和ObjectFactory提供动态操作Java对象的能力;TypeHandler实现Java与JDBC类型转换;数据源模块支持池化/非池化管理;缓存模块采用装饰器模式提升性能;binding模块通过动态代理实现Mapper接口与SQL的自动绑定。
反射工具箱与TypeHandler的实现
MyBatis 的基础支持层是其核心功能的重要组成部分,其中反射工具箱和 TypeHandler 的实现尤为关键。反射工具箱为 MyBatis 提供了动态操作 Java 对象的能力,而 TypeHandler 则负责处理 Java 类型与 JDBC 类型之间的转换。本文将深入剖析这两部分的实现细节。
反射工具箱的实现
Reflector 类
Reflector 类是 MyBatis 反射工具箱的核心,它封装了 JavaBean 的元数据信息,包括可读/可写属性、对应的 getter/setter 方法以及构造函数等。其核心功能如下:
-
元数据封装:
readablePropertyNames和writablePropertyNames分别存储可读和可写的属性名。getMethods和setMethods存储属性名对应的 getter 和 setter 方法调用器。getTypes和setTypes存储属性名对应的返回值类型。
-
构造函数处理:
- 通过
addDefaultConstructor方法获取类的默认构造函数,若无空参构造则抛出异常。
- 通过
public class Reflector {
private final Class<?> type;
private final String[] readablePropertyNames;
private final String[] writablePropertyNames;
private final Map<String, Invoker> setMethods = new HashMap<>();
private final Map<String, Invoker> getMethods = new HashMap<>();
private final Map<String, Class<?>> setTypes = new HashMap<>();
private final Map<String, Class<?>> getTypes = new HashMap<>();
private Constructor<?> defaultConstructor;
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
}
}
ReflectorFactory
ReflectorFactory 是 Reflector 的工厂类,支持缓存和自定义扩展。其默认实现 DefaultReflectorFactory 使用 ConcurrentMap 缓存 Reflector 实例,提升性能。
public interface ReflectorFactory {
Reflector findForClass(Class<?> type);
}
public class DefaultReflectorFactory implements ReflectorFactory {
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
@Override
public Reflector findForClass(Class<?> type) {
return reflectorMap.computeIfAbsent(type, Reflector::new);
}
}
ObjectFactory
ObjectFactory 负责通过反射创建对象实例,其默认实现 DefaultObjectFactory 支持无参和有参构造方法的调用。
public interface ObjectFactory {
<T> T create(Class<T> type);
}
public class DefaultObjectFactory implements ObjectFactory {
@Override
public <T> T create(Class<T> type) {
try {
return type.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new ReflectionException("Error instantiating " + type, e);
}
}
}
TypeHandler 的实现
TypeHandler 是 MyBatis 类型转换的核心组件,负责处理 Java 类型与 JDBC 类型之间的双向转换。
JdbcType 枚举
JdbcType 枚举定义了 JDBC 支持的所有数据类型,例如 VARCHAR、INTEGER 等。
public enum JdbcType {
VARCHAR(Types.VARCHAR),
INTEGER(Types.INTEGER),
// 其他类型
private final int TYPE_CODE;
JdbcType(int code) {
this.TYPE_CODE = code;
}
}
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;
}
内置 TypeHandler
MyBatis 提供了多种内置的 TypeHandler,例如 StringTypeHandler、IntegerTypeHandler 等,用于处理常见的数据类型转换。
public class StringTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
总结
反射工具箱和 TypeHandler 是 MyBatis 基础支持层的两大核心组件。反射工具箱通过动态操作 Java 对象,为 ORM 提供了灵活性和扩展性;TypeHandler 则通过类型转换机制,实现了 Java 与数据库之间的无缝对接。理解这两部分的实现原理,有助于更好地掌握 MyBatis 的核心工作机制。
数据源与事务模块的设计
在数据持久层中,数据源和事务是两个至关重要的组件,它们直接影响系统的性能和稳定性。MyBatis 通过其基础支持层提供了对数据源和事务的灵活管理,本文将深入剖析 MyBatis 中数据源与事务模块的设计与实现。
1. DataSource 模块
1.1 DataSourceFactory
MyBatis 通过 DataSourceFactory 接口及其实现类来管理数据源的创建与配置。DataSourceFactory 提供了两个核心方法:
setProperties(Properties props):用于配置数据源的属性。getDataSource():用于获取数据源实例。
MyBatis 提供了两种数据源工厂实现:
UnpooledDataSourceFactory:用于创建非池化的数据源。PooledDataSourceFactory:继承自UnpooledDataSourceFactory,用于创建池化的数据源。
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public void setProperties(Properties properties) {
// 处理属性配置
}
@Override
public DataSource getDataSource() {
return dataSource;
}
}
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
1.2 UnpooledDataSource
UnpooledDataSource 是 MyBatis 提供的非池化数据源实现,直接通过 JDBC 的 DriverManager 获取数据库连接。其主要功能包括:
- 加载并注册数据库驱动。
- 配置连接属性(如超时时间、事务隔离级别等)。
public class UnpooledDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(new Properties());
}
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) {
// 加载并注册驱动
}
}
}
1.3 PooledDataSource
PooledDataSource 是 MyBatis 提供的池化数据源实现,通过复用数据库连接提升性能。其核心设计包括:
- PooledConnection:封装真实的数据库连接,并通过动态代理拦截
close()方法,实现连接的归还而非关闭。 - PoolState:管理空闲连接和活跃连接的状态。
class PooledConnection implements InvocationHandler {
private final Connection realConnection;
private final Connection proxyConnection;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("close")) {
dataSource.pushConnection(this);
return null;
}
return method.invoke(realConnection, args);
}
}
public class PooledDataSource implements DataSource {
private final PoolState state = new PoolState(this);
@Override
public Connection getConnection() throws SQLException {
return popConnection().getProxyConnection();
}
private PooledConnection popConnection() throws SQLException {
// 从连接池中获取连接
}
}
2. 事务模块
MyBatis 的事务管理依赖于 Transaction 接口及其实现类。常见的事务实现包括:
- JdbcTransaction:基于 JDBC 的事务管理。
- ManagedTransaction:由容器(如 Spring)管理事务。
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
public class JdbcTransaction implements Transaction {
private final Connection connection;
private final boolean autoCommit;
@Override
public void commit() throws SQLException {
if (!autoCommit) {
connection.commit();
}
}
}
3. 总结
MyBatis 的数据源与事务模块通过灵活的接口设计和实现,支持多种数据源和事务管理方式。其核心特点包括:
- 支持池化和非池化数据源。
- 通过动态代理实现连接池的高效管理。
- 提供简单的事务管理接口,便于扩展。
java public interface Cache { String getId(); // 获取缓存 ID void putObject(Object key, Object value); // 存储键值对 Object getObject(Object key); // 获取缓存值 Object removeObject(Object key); // 删除指定缓存项 void clear(); // 清空缓存 int getSize(); // 获取缓存大小 }
`PerpetualCache` 是 `Cache` 接口的基础实现,其内部使用 `HashMap` 存储缓存项。这种实现简单直接,但缺乏高级功能,如缓存清理策略或线程安全控制。
```java
public class PerpetualCache implements Cache {
private final Map<Object, Object> cache = new HashMap<>();
// 实现 Cache 接口的方法
}
装饰器模式的应用
MyBatis 通过装饰器模式为缓存添加了丰富的功能,每种装饰器专注于解决特定问题。以下是常见的缓存装饰器及其功能:
| 装饰器类 | 功能描述 |
|---|---|
BlockingCache | 提供线程安全支持,确保同一时刻只有一个线程访问数据库。 |
FifoCache | 实现先进先出策略,当缓存满时删除最早进入的缓存项。 |
LruCache | 基于最近最少使用算法清理缓存,优先移除最久未使用的缓存项。 |
SoftCache | 使用软引用存储缓存项,当 JVM 内存不足时自动清理缓存。 |
ScheduledCache | 周期性清理缓存,默认间隔为 1 小时。 |
SerializedCache | 对缓存值进行序列化和反序列化,支持跨进程或网络传输。 |
缓存装饰器实现细节
BlockingCache
BlockingCache 通过 ReentrantLock 实现线程安全。当线程尝试获取某个键的缓存时,如果缓存未命中,该线程会获取锁并查询数据库,其他线程则被阻塞。
FifoCache
FifoCache 维护了一个键的队列,当缓存达到容量上限时,删除队列头部的键及其对应的缓存项。
public class FifoCache implements Cache {
private final Cache delegate;
private final Deque<Object> keyList;
private int size;
@Override
public void putObject(Object key, Object value) {
keyList.addLast(key); // 记录键的顺序
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey); // 清理最早进入的缓存项
}
delegate.putObject(key, value);
}
}
LruCache
LruCache 通过 LinkedHashMap 实现最近最少使用算法。每次访问缓存项时,将其移动到队列尾部,清理时优先移除队列头部的缓存项。
public class LruCache implements Cache {
private final Cache delegate;
private final Map<Object, Object> keyMap;
private Object eldestKey;
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key); // 更新键的顺序
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
delegate.removeObject(eldestKey); // 清理最久未使用的缓存项
}
}
}
缓存的键设计
MyBatis 使用 CacheKey 类作为缓存的键,该类封装了多个影响缓存项的因素(如 SQL 语句、参数值等),确保键的唯一性。
public class CacheKey {
private final List<Object> updateList;
public void update(Object object) {
updateList.add(object); // 添加影响因素
}
}
通过上述分析,可以看出 MyBatis 缓存模块的设计既灵活又高效,能够满足不同场景下的需求。
binding模块的作用与实现
MyBatis 的 binding 模块是基础支持层中的一个重要组件,它的核心作用是将 Mapper 接口与映射配置文件中的 SQL 语句动态绑定,从而避免了手动调用 SqlSession 方法时可能出现的错误。通过 binding 模块,开发者可以更加安全、高效地使用 Mapper 接口,而无需担心 SQL 语句的拼写错误或参数传递问题。
1. binding模块的核心组件
binding 模块主要由以下几个核心组件构成:
- MapperRegistry:负责注册和管理 Mapper 接口的代理工厂。
- MapperProxyFactory:为 Mapper 接口创建代理对象的工厂。
- MapperProxy:实现了
InvocationHandler接口,负责拦截 Mapper 接口的方法调用。 - MapperMethod:封装了 Mapper 接口方法与 SQL 语句的映射关系,是连接接口与 SQL 的桥梁。
1.1 MapperRegistry 和 MapperProxyFactory
MapperRegistry 是 binding 模块的入口,它维护了一个 knownMappers 集合,用于存储 Mapper 接口与其对应的 MapperProxyFactory 工厂对象。以下是一个简化的代码示例:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
MapperProxyFactory 的主要职责是为 Mapper 接口创建代理对象。它内部维护了一个 methodCache,用于缓存 Mapper 接口方法与 MapperMethod 对象的映射关系:
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
1.2 MapperProxy
MapperProxy 是动态代理的核心实现类,它拦截 Mapper 接口的方法调用,并通过 MapperMethod 执行对应的 SQL 语句。以下是 MapperProxy 的关键代码片段:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final Map<Method, MapperMethod> methodCache;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
1.3 MapperMethod
MapperMethod 是 binding 模块中最复杂的部分,它封装了 Mapper 接口方法与 SQL 语句的映射逻辑。MapperMethod 的核心方法是 execute(),它根据方法的签名和 SQL 命令类型(如 SELECT、INSERT 等)执行相应的操作:
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case INSERT:
return sqlSession.insert(command.getName(), param);
case SELECT:
return sqlSession.selectOne(command.getName(), param);
// 其他操作类型
}
}
2. binding模块的工作流程
binding 模块的工作流程可以概括为以下几个步骤:
- 注册 Mapper 接口:在 MyBatis 初始化阶段,
MapperRegistry会将所有 Mapper 接口注册到knownMappers集合中。 - 创建代理对象:当调用
getMapper方法时,MapperProxyFactory会为 Mapper 接口创建一个动态代理对象。 - 拦截方法调用:代理对象的方法调用会被
MapperProxy拦截,并通过MapperMethod执行对应的 SQL 语句。 - 执行 SQL:
MapperMethod根据方法的签名和 SQL 命令类型,调用SqlSession的相应方法完成数据库操作。
以下是一个流程图,展示了 binding 模块的工作流程:
3. binding模块的优势
通过 binding 模块,MyBatis 实现了以下优势:
- 类型安全:开发者无需手动编写 SQL 语句的字符串标识符,避免了拼写错误。
- 动态代理:通过动态代理技术,实现了 Mapper 接口与 SQL 语句的自动绑定。
- 性能优化:
MapperMethod的缓存机制避免了重复解析方法签名和 SQL 语句的开销。
4. 示例代码
以下是一个简单的 Mapper 接口定义及其对应的映射配置文件:
public interface UserMapper {
User selectUserById(Integer id);
}
对应的映射配置文件:
<mapper namespace="com.example.UserMapper">
<select id="selectUserById" resultType="com.example.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
通过 binding 模块,UserMapper 接口的方法会自动与映射配置文件中的 SQL 语句绑定,开发者可以直接调用 selectUserById 方法,而无需关心底层的 SQL 执行细节。
总结
MyBatis基础支持层通过精巧的模块化设计,为ORM功能提供了坚实的技术底座。反射工具箱和TypeHandler解决了对象操作与类型转换的核心问题;数据源与事务模块通过灵活的接口设计支持多样化配置;缓存模块的装饰器模式实现了高效缓存策略;binding模块的动态代理机制简化了开发流程。这些组件共同构成了MyBatis高效、灵活的基础架构,值得开发者深入学习和借鉴。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



