<返回更多

Mybatis工作流程和原理!

2023-04-03  今日头条  程序猿怪咖
加入收藏

 

一、前言

来看这篇文章的应该都知道,在没有出现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,如果其二级缓存出现脏数据,会抛出异常提示。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>