剖析doocs/source-code-hunter中的MyBatis基础支持层

剖析doocs/source-code-hunter中的MyBatis基础支持层

【免费下载链接】source-code-hunter 😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等 【免费下载链接】source-code-hunter 项目地址: https://gitcode.com/doocs/source-code-hunter

本文深入解析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 方法以及构造函数等。其核心功能如下:

  1. 元数据封装:

    • readablePropertyNames 和 writablePropertyNames 分别存储可读和可写的属性名。
    • getMethods 和 setMethods 存储属性名对应的 getter 和 setter 方法调用器。
    • getTypes 和 setTypes 存储属性名对应的返回值类型。
  2. 构造函数处理:

    • 通过 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 的数据源与事务模块通过灵活的接口设计和实现,支持多种数据源和事务管理方式。其核心特点包括:

  • 支持池化和非池化数据源。
  • 通过动态代理实现连接池的高效管理。
  • 提供简单的事务管理接口,便于扩展。

mermaidjava 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 实现线程安全。当线程尝试获取某个键的缓存时,如果缓存未命中,该线程会获取锁并查询数据库,其他线程则被阻塞。

mermaid

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 模块主要由以下几个核心组件构成:

  1. MapperRegistry:负责注册和管理 Mapper 接口的代理工厂。
  2. MapperProxyFactory:为 Mapper 接口创建代理对象的工厂。
  3. MapperProxy:实现了 InvocationHandler 接口,负责拦截 Mapper 接口的方法调用。
  4. 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 模块的工作流程可以概括为以下几个步骤:

  1. 注册 Mapper 接口:在 MyBatis 初始化阶段,MapperRegistry 会将所有 Mapper 接口注册到 knownMappers 集合中。
  2. 创建代理对象:当调用 getMapper 方法时,MapperProxyFactory 会为 Mapper 接口创建一个动态代理对象。
  3. 拦截方法调用:代理对象的方法调用会被 MapperProxy 拦截,并通过 MapperMethod 执行对应的 SQL 语句。
  4. 执行 SQL:MapperMethod 根据方法的签名和 SQL 命令类型,调用 SqlSession 的相应方法完成数据库操作。

以下是一个流程图,展示了 binding 模块的工作流程:

mermaid

3. binding模块的优势

通过 binding 模块,MyBatis 实现了以下优势:

  1. 类型安全:开发者无需手动编写 SQL 语句的字符串标识符,避免了拼写错误。
  2. 动态代理:通过动态代理技术,实现了 Mapper 接口与 SQL 语句的自动绑定。
  3. 性能优化: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高效、灵活的基础架构,值得开发者深入学习和借鉴。

【免费下载链接】source-code-hunter 😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等 【免费下载链接】source-code-hunter 项目地址: https://gitcode.com/doocs/source-code-hunter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值