当前位置: 首页 > news >正文

源码学习:MyBatis源码深度解析与实战

一、MyBatis基础概念

1.1 MyBatis简介

MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。

1.2 MyBatis的核心组件

  • SqlSessionFactory:会话工厂,是MyBatis的核心,负责创建SqlSession实例
  • SqlSession:会话,用于执行持久化操作的对象
  • Executor:执行器,SqlSession内部通过它来执行SQL语句
  • MappedStatement:映射语句,包含SQL语句和映射规则
  • Configuration:配置信息,包含MyBatis的所有配置

二、MyBatis架构设计

2.1 分层架构

MyBatis采用分层架构设计,主要分为以下几层:

  1. API层:提供给外部使用的接口,如SqlSession
  2. 核心处理层:处理SQL的解析、执行和结果映射
  3. 基础支持层:提供日志、事务等基础功能支持

2.2 核心流程

  1. 初始化阶段:加载配置文件,创建SqlSessionFactory
  2. SQL解析阶段:解析映射文件,生成MappedStatement
  3. 参数处理阶段:处理参数,生成BoundSql
  4. SQL执行阶段:执行SQL,获取结果集
  5. 结果映射阶段:将结果集映射为Java对象

三、MyBatis源码解析

3.1 SqlSessionFactory的创建过程

// 核心代码示例:SqlSessionFactory的创建
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

核心源码解析:

// SqlSessionFactoryBuilder.build()方法
public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);
}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 创建XML配置构建器XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 解析配置文件,返回Configuration对象// 构建DefaultSqlSessionFactoryreturn build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}
}public SqlSessionFactory build(Configuration config) {// 创建DefaultSqlSessionFactory实例return new DefaultSqlSessionFactory(config);
}

3.2 SqlSession的获取与使用

// 获取SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {// 使用SqlSession执行操作UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectUser(1);
} finally {session.close();
}

核心源码解析:

// DefaultSqlSessionFactory.openSession()方法
@Override
public SqlSession openSession() {// 获取默认的执行器类型(SIMPLE)return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// 获取环境配置final Environment environment = configuration.getEnvironment();// 获取事务工厂final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 创建事务tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 创建执行器final Executor executor = configuration.newExecutor(tx, execType);// 创建DefaultSqlSessionreturn new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}

3.3 Mapper接口的代理实现

MyBatis使用JDK动态代理为Mapper接口创建代理对象:

// DefaultSqlSession.getMapper()方法
@Override
public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);
}// Configuration.getMapper()方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}// MapperRegistry.getMapper()方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 创建Mapper代理对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}

Mapper代理工厂的实现:

// MapperProxyFactory.newInstance()方法
public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) {// 使用JDK动态代理创建代理对象return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

3.4 SQL执行过程

当调用Mapper接口的方法时,实际上是调用了MapperProxy的invoke方法:

// MapperProxy.invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 如果是Object类的方法,直接调用if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}// 处理接口默认方法else if (method.isDefault()) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}// 获取MapperMethodfinal MapperMethod mapperMethod = cachedMapperMethod(method);// 执行SQL操作return mapperMethod.execute(sqlSession, args);
}

MapperMethod执行SQL的核心逻辑:

// MapperMethod.execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT:{ // 处理插入操作Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE:{ // 处理更新操作Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE:{ // 处理删除操作Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:// 处理查询操作,根据返回类型执行不同的查询方法if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {// 处理单个对象查询Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;
}

3.5 执行器(Executor)

MyBatis提供了三种执行器:

  1. SimpleExecutor:简单执行器,每执行一次SQL就创建一个Statement
  2. ReuseExecutor:复用执行器,会重用预处理语句
  3. BatchExecutor:批量执行器,用于批量执行SQL

执行器的创建逻辑:

// Configuration.newExecutor()方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;// 根据执行器类型创建相应的执行器if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 如果开启了二级缓存,使用CachingExecutor包装执行器if (cacheEnabled) {executor = new CachingExecutor(executor);}// 应用插件executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

四、MyBatis中的设计模式

4.1 工厂模式

MyBatis中大量使用了工厂模式:

  • SqlSessionFactory:创建SqlSession的工厂
  • ObjectFactory:创建结果对象的工厂
  • MapperProxyFactory:创建Mapper代理对象的工厂

4.2 代理模式

MyBatis使用JDK动态代理实现Mapper接口:

  • MapperProxy:实现InvocationHandler接口,处理Mapper方法的调用

4.3 建造者模式

MyBatis使用建造者模式创建复杂对象:

  • SqlSessionFactoryBuilder:构建SqlSessionFactory
  • XMLConfigBuilder:构建Configuration
  • XMLMapperBuilder:构建Mapper映射

4.4 装饰器模式

MyBatis使用装饰器模式增强执行器功能:

  • CachingExecutor:为Executor添加缓存功能

4.5 模板方法模式

MyBatis在SQL执行过程中使用模板方法模式:

  • BaseExecutor:定义SQL执行的骨架,子类实现具体逻辑

五、MyBatis配置体系

5.1 配置文件结构

MyBatis配置文件包括以下主要部分:

  • properties:属性配置
  • settings:全局配置项
  • typeAliases:类型别名
  • typeHandlers:类型处理器
  • objectFactory:对象工厂
  • plugins:插件配置
  • environments:环境配置
  • mappers:映射器配置

5.2 配置加载过程

配置文件的加载由XMLConfigBuilder完成:

// XMLConfigBuilder.parse()方法
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// 解析根节点configurationparseConfiguration(parser.evalNode("/configuration"));return configuration;
}// 解析各个配置节点
private void parseConfiguration(XNode root) {try {// 解析properties节点propertiesElement(root.evalNode("properties"));// 解析settings节点Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);// 解析typeAliases节点typeAliasesElement(root.evalNode("typeAliases"));// 解析plugins节点pluginElement(root.evalNode("plugins"));// 解析objectFactory节点objectFactoryElement(root.evalNode("objectFactory"));// 解析objectWrapperFactory节点objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 解析reflectorFactory节点reflectorFactoryElement(root.evalNode("reflectorFactory"));// 设置settings属性settingsElement(settings);// 解析environments节点environmentsElement(root.evalNode("environments"));// 解析databaseIdProvider节点databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 解析typeHandlers节点typeHandlerElement(root.evalNode("typeHandlers"));// 解析mappers节点mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

六、MyBatis缓存机制

6.1 一级缓存

MyBatis的一级缓存是SqlSession级别的缓存,默认开启:

  • 同一个SqlSession中执行相同的SQL语句,会缓存第一次执行的结果
  • SqlSession关闭或提交事务后,一级缓存会被清空

6.2 二级缓存

MyBatis的二级缓存是Mapper级别的缓存,需要手动开启:

  1. 在MyBatis配置文件中开启二级缓存
  2. 在Mapper映射文件中添加`<cache/>`标签

二级缓存的实现原理:

// CachingExecutor.query()方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 获取MappedStatement的缓存Cache cache = ms.getCache();if (cache != null) {// 如果需要刷新缓存flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// 确保没有输出参数ensureNoOutParams(ms, boundSql);// 从缓存中获取结果@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// 缓存中没有,委托给底层执行器执行查询list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将结果放入缓存tcm.putObject(cache, key, list);}return list;}}// 没有缓存,直接执行查询return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

七、MyBatis插件开发

7.1 插件原理

MyBatis插件基于拦截器模式实现,可以拦截以下四大核心对象:

  1. Executor:执行器
  2. StatementHandler:SQL语句处理器
  3. ParameterHandler:参数处理器
  4. ResultSetHandler:结果集处理器

7.2 插件开发步骤

  1. 实现Interceptor接口
  2. 使用@Intercepts和@Signature注解配置拦截的方法
  3. 在MyBatis配置文件中注册插件

插件示例:

@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class, Integer.class})
})
public class SqlLogInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取被代理对象StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// 获取BoundSql对象,包含SQL语句BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();// 记录SQL日志System.out.println("SQL: " + sql);// 执行原方法return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 使用Plugin.wrap创建代理对象return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 设置插件属性}
}

八、MyBatis与Spring集成

8.1 集成原理

MyBatis与Spring集成主要通过以下组件实现:

  • SqlSessionFactoryBean:创建SqlSessionFactory
  • MapperScannerConfigurer:扫描Mapper接口并注册为Spring Bean
  • SqlSessionTemplate:线程安全的SqlSession实现

8.2 核心代码解析

SqlSessionTemplate的实现:

// SqlSessionTemplate的部分实现
public class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;// 创建SqlSession代理this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());}// 内部拦截器类private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 从Spring事务管理器获取SqlSession或创建新的SqlSessionSqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {// 执行原方法Object result = method.invoke(sqlSession, args);// 如果不是Spring管理的事务,提交事务if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}return result;} catch (Throwable t) {// 异常处理Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {// 使用Spring的异常转换器转换异常SpringManagedTransaction springManagedTransaction = (SpringManagedTransaction) sqlSession.getConnection().getTransaction();if (springManagedTransaction.isConnectionTransactional() && springManagedTransaction.isRollbackOnly()) {// 事务已经标记为回滚} else {// 转换异常unwrapped = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);}}throw unwrapped;} finally {// 关闭SqlSessionif (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}}
}

九、MyBatis最佳实践

9.1 性能优化

  1. 合理使用缓存:根据业务场景选择合适的缓存策略
  2. 延迟加载:使用延迟加载减少不必要的查询
  3. 批量操作:使用批量执行器提高批量操作性能
  4. 参数映射:使用#{}代替${}避免SQL注入

9.2 常见问题及解决方案

  1. N+1查询问题:使用嵌套查询或联合查询解决
  2. 事务管理:合理配置事务隔离级别和传播行为
  3. 缓存失效:了解缓存的工作原理,避免不必要的缓存刷新

十、总结与展望

MyBatis作为一款优秀的持久层框架,通过其灵活的配置和强大的映射能力,为Java开发者提供了便捷的数据库访问方式。通过深入理解MyBatis的源码实现,我们可以更好地使用和扩展MyBatis,提高开发效率和系统性能。

未来,随着Java技术的发展,MyBatis也在不断演进,引入更多现代化的特性,如响应式编程支持等。掌握MyBatis的核心原理,对于Java开发者来说是一项重要的技能。    

http://www.dtcms.com/a/540664.html

相关文章:

  • RAG项目中知识库的检索优化
  • Java IO 流之转换流:InputStreamReader/OutputStreamWriter(字节与字符的桥梁)
  • 熊掌号做网站推广的注意事项品牌网页
  • shell脚本curl命令发送钉钉通知(加签方式)——筑梦之路
  • [无人机sdk] AdvancedSensing | 获取实时视频流 | VGA分辨率
  • 海康相机通过透明通道控制串口收发数据
  • 建网站科技公司做校服的网站
  • 设计模式简介
  • PyTorch torch.unique() 基础与实战
  • 【图像处理基石】图像滤镜的算法原理:从基础到进阶的技术解析
  • 信宜网站建设网站开发配置表格
  • 提示词(Prompt)——指令型提示词在大模型中的调用(以 Qwen 模型为例)
  • python-88-实时消费kafka数据批量追加写入CSV文件
  • 提示词(Prompt)——链式思维提示词(Chain-of-Thought Prompting)在大模型中的调用(以 Qwen 模型为例)
  • 用三个面中心点求解长方体位姿:从几何直觉到线性代数实现
  • 网站备案ip查询网站做网站首页ps分辨率多少
  • 免费建一级域名网站千锋教育广州校区
  • CSS3属性(三)
  • 开源底盘+机械臂机器人:Lekiwi驱动链路分析
  • 通过 useEventBus 和 useEventCallBack 实现与原生 Android、鸿蒙、iOS 的事件交互
  • iOS 26 iPhone 使用记录分析 多工具组合构建全方位设备行为洞察体系
  • 【Unity】HTModuleManager(三)Markdown语法的Unity编辑器方言
  • 如何将安卓手机备份到电脑?7种方法
  • 基于SpringBoot+Vue的购物商城(支付宝沙盒支付、物流快递API、WebSocket及时通讯、协同过滤算法、Echarts图形化分析)
  • MYSQL-超全基础以及用法--仅个人的速记笔记(1)
  • 31、LangChain开发框架(八)-- LangChain 数据分析智能体实战
  • 建设局域网网站盐城市亭湖区城乡建设网站
  • 6.2 大数据方法论与实践指南-任务元数据
  • MongoDB中全文索引基础篇
  • SSM浪漫烘焙屋z73z2(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。