您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Mybatis工作流程和原理!

时间:2023-04-03 14:53:00  来源:今日头条  作者:程序猿怪咖

 

一、前言

来看这篇文章的应该都知道,在没有出现Hibernate和MyBatis框架时,我们要访问数据库底层,都得使用JDBC来连接及操作数据库。

用过JDBC的都知道使用很繁杂,所以就诞生了Hibernate和Mybatis这种ORM(对象映射关系)框架,其实他们都是对操作数据库底层(JDBC)的二次封装,要使用ORM框架只需要引入对应的jar包即可。

这篇文章我们来详细的分析一下Mybatis的底层实现原理和工作流程。

二、Mybatis核心组件

1、SqlSessionFactoryBuilder (构造器)

用Builder模式根据mybatis-config.xml配置或者代码来生成SqISessionFactory。

2、SqlSessionFactory (工厂接口)

采用工厂模式生成SqlSession。

3、SqlSession (会话)

一个既可以发送 SQL 执行返回结果,也可以获取MApper的接口。

4、SQL Mapper (映射器)

它由一个JAVA接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则,它负责发送SQL去执行,并返回结果。

5、Executor(执行器)

 

三、Mybatis的工作流程

Mybatis工作流程简述:

1、通过SqlSessionFactoryBuilder构建SqlSessionFactory工厂。

2、通过SqlSessionFactory构建SqlSession会话对象。

3、通过SqlSession拿到Mapper代理对象(用到了动态代理)。

4、通过MapperProxy调用Mapper中增删改查的方法,然后将编译后的sql拿到数据库执行。

Mybatis工作流程附图:

 

四、Mybatis源码解析

1、SqlSessionFactoryBuilder创建SqlSessionFactory工厂对象

SqlSessionFactoryBuilder中的build()方法分析

public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析mybatis-config.xml
return 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.
}
}
}

//返回SqlSessionFactory,默认使用的是实现类DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//获取根节点configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

//开始解析mybatis-config.xml,并把解析后的数据存放到configuration中
private void parseConfiguration(XNode root) {
try {
//保存mybatis-config.xml中的标签setting,本例中开启全局缓存cacheEnabled,设置默认执行器defaultExecutorType=REUSE
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
//解析是否配置了外部properties,例如本例中配置的jdbc.propertis
propertiesElement(root.evalNode("properties"));
//查看是否配置了VFS,默认没有,本例也没有使用
loadCustomVfs(settings);
//查看是否用了类型别名,减少完全限定名的冗余,本例中使用了别名User代替了com.ctc.Model.User
typeAliasesElement(root.evalNode("typeAliases"));
//查看是否配置插件来拦截映射语句的执行,例如拦截Executor的Update方法,本例没有使用
pluginElement(root.evalNode("plugins"))
//查看是否配置了ObjectFactory,默认情况下使用对象的无参构造方法或者是带有参数的构造方法,本例没有使用
objectFactoryElement(root.evalNode("objectFactory"));
//查看是否配置了objectWrapperFatory,这个用来或者ObjectWapper,可以访问:对象,Collection,Map属性。本例没有使用
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//查看是否配置了reflectorFactory,mybatis的反射工具,提供了很多反射方法。本例没有使用
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//放入参数到configuration对象中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//查看数据库环境配置
environmentsElement(root.evalNode("environments"));
//查看是否使用多种数据库,本例没有使用
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//查看是否配置了新的类型处理器,如果跟处理的类型跟默认的一致就会覆盖。本例没有使用
typeHandlerElement(root.evalNode("typeHandlers"));
//查看是否配置SQL映射文件,有四种配置方式,resource,url,class以及自动扫包package。本例使用package
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

从源码中可以看出,最终会返回一个DefautSqlSessionFactory对象。

2、通过SqlSessionFactory构建SqlSession会话对象。

@Override
public SqlSession openSession() {//打开会话
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//拿到从mybatis中解析到的数据库环境配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

  //拿到jdbc的事务管理器,有两种一种是jbc,一种的managed。
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  
//这里返回的是ReuseExecutor并把事务传入对象中
final Executor executor = configuration.newExecutor(tx, execType);
return 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();
}
}


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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChAIn.pluginAll(executor);
return executor;
}

//返回一个SqlSession,默认使用DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}

这段源码显示最终会返回一个SqlSession会话对象。

3、通过SqlSession拿到Mapper代理对象(用到了动态代理)。

@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//knownMapper是一个HashMap在存放mapperRegistry的过程中,以每个Mapper对象的类型为Key, MapperProxyFactory 为value保存。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

//这一块就用到了动态代理
public T newInstance(SqlSession sqlSession) {
//生成一个mapperProxy对象,这个对象实现了InvocationHandler, Serializable。就是JDK动态代理中的方法调用处理器
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过JDK动态代理生成一个Mapper的代理,在本例中的就是UserMapper的代理类,它实现了UserMapper接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

这段源码就释义了怎么拿到Mapper代理对象。

4、通过MapperProxy调用Mapper中增删改查的方法,然后将编译后的sql拿到数据库执行。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断当前调用的method是不是Object中声明的方法,如果是的话直接执行。
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

//把当前请求放入一个HashMap中,一旦下次还是同样的方法进来直接返回。
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

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;
}

@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

//这边调用的是CachingExecutor类的query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果缓存中没有数据则查询数据库
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//结果集放入缓存
tcm.putObject(cache, key, list); 
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

五、Hibernate和Mybatis的区别

1、Hibernate是全自动,而Mybatis是半自动

Hibernate已经帮我们封装好了绝大多数的sql基本操作,直接调用其方法就可以了。Mybatis需要我们手动在xml中编写sql。

注意:很复杂的场景Hibernate的注解方式也可以实现自定义sql;

2、Hibernate数据库移植性远大于Mybatis

简单的理解就是,Hibernate已经帮我们封装好了大部分sql,降低了对象和数据库之间的耦合,也就是对多种数据库的支持;Mybatis则完全需要编程者写sql,这样对多种类型数据库的耦合性自然没那么高了

3、如果优化sql,那么Mybatis肯定比Hibernate更具优势

很好理解,因为Mybatis的sql都是编程者手写在xml中,优化起来更方便;

4、Hibernate的缓存机制比mybatis更具有优势

MyBatis的二级缓存需要针对具体对象映射配置。Hibernate有它自己的管理机制,不用去管sql,如果其二级缓存出现脏数据,会抛出异常提示。



Tags:Mybatis   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Mybatis参数映射搞不明白?来试试这个工具吧!
之前在《使用技巧-Mybatis参数映射》《使用技巧-Mybatis参数映射(2)》提到了Mybatis的一些参数映射技巧,但是平时使用的时候有些小伙伴可能不知道自己写的#{}表达式能不能获取...【详细内容】
2024-02-28  Search: Mybatis  点击:(33)  评论:(0)  加入收藏
如何在Spring项目中配置MP(MyBatis-Plus)集成?
在Spring项目中集成MP,需要进行以下配置:1. 引入依赖:在项目的pom.xml文件中添加MP相关依赖,例如:```xml<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plu...【详细内容】
2024-01-09  Search: Mybatis  点击:(87)  评论:(0)  加入收藏
Mybatis占位符#和$的区别?源码解读
本文针对笔者日常开发中对 Mybatis 占位符 #{} 和 ${} 使用时机结合源码,思考总结而来 &bull; Mybatis 版本 3.5.11 &bull; Spring boot 版本 3.0.2 &bull; mybatis-spring...【详细内容】
2023-10-27  Search: Mybatis  点击:(399)  评论:(0)  加入收藏
看完这篇文章,你也可以手写MyBatis部分源码(JDBC)
一、持久化机制持久化(persistence): 把数据保存到可调电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化...【详细内容】
2023-10-09  Search: Mybatis  点击:(332)  评论:(0)  加入收藏
Mybatis-Flex初体验
本篇文章内容主要包括: MyBatis-Flex 介绍MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何...【详细内容】
2023-09-24  Search: Mybatis  点击:(196)  评论:(0)  加入收藏
MyBatis简单易用的背后隐藏的挑战
MyBatis,作为一款备受欢迎的持久层框架,它的简单易用以及灵活的配置吸引了无数的开发者。然而,随着项目的不断发展,规模的逐渐扩大,MyBatis的一些挑战也开始逐渐浮出水面。首先,由...【详细内容】
2023-09-15  Search: Mybatis  点击:(242)  评论:(0)  加入收藏
MyBatis缓存机制
MyBatis 的缓存机制属于本地缓存,适用于单机系统,它的作用是减少数据库的查询次数,提高系统性能。MyBaits 中包含两级本地缓存: 一级缓存:SqlSession 级别的,是 MyBatis 自带的缓...【详细内容】
2023-09-12  Search: Mybatis  点击:(229)  评论:(0)  加入收藏
对比 MyBatis 和 MyBatis-Plus 批量插入、批量更新的性能和区别
1 环境准备1.1 搭建 MyBatis-Plus 环境 创建 maven springboot 工程 导入依赖:web 启动器、jdbc、、java 连接 mysql、Lombok、druid 连接池启动器、mybatis-plus 启动器 编...【详细内容】
2023-09-08  Search: Mybatis  点击:(191)  评论:(0)  加入收藏
Spring Data JPA 和 MyBatis 谁更强?
我无法明确的告诉你JPA和MyBatis在国内哪个会更流行,我本人更喜欢JPA,但是我本人日常开发用MyBatis多。但是我的回答绝对不是在划水,而是我多年来自己的一点小小的思考。MyBati...【详细内容】
2023-08-22  Search: Mybatis  点击:(335)  评论:(0)  加入收藏
Mybatis-Plus可能会导致数据库死锁
一、场景还原1.版本信息MySQL版本:5.6.36-82.1-logMybatis-Plus的starter版本:3.3.2存储引擎:InnoDB2.死锁现象A同学在生产环境使用了Mybatis-Plus提供的com.baomidou.mybatisp...【详细内容】
2023-08-14  Search: Mybatis  点击:(171)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(54)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(88)  评论:(0)  加入收藏
站内最新
站内热门
站内头条