前言

最近在和粉丝聊天的时候被粉丝问到jdbc和mybatis底层实现这一块的问题,而且还不止一个小伙伴问到,于是我似乎认识到了问题的严重性,我花了两天时间整理了一下自己的认识和网上查阅的资料写了这篇文章,话不多说,满满的干货都在下面了。

在说mybatis底层实现之前,先看下基本的知识点jdbc

jdbc是连接数据库的最基本实现,任何对数据库的操作都是基于jdbc

1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
2.获取数据库连接
Connection conn =DriverManager.getConnection(url,user,p); 3.创建向数据发送sql 的statement对象
Statement stmt = conn.CreateStatement(); 4. 向数据库发送sql
ResultSet rs = stmt.executeQuery(sql)//select语句
int updateaSum = stmt.executeUpdate(sql)//insert,update delete语句 5. 处理结果集
while(rs.next()){
rs.getString(列名)
rs.getInt(列名)
}
6. 关闭资源
rs.close();
stmt.close();
conn.close();

  

Mybatis之Sqlsession、Connection和Transaction解析关系与原理

Connection
JDBC 是我们用来与数据库交互最基础的API。
Connection 作为一个特定数据库的会话,在一个连接的上下文中,sql语句被执行,然后结果被返回。
我们先看下使用sqlsession进行数据库操作的基本流程
SqlSession
可以看作是对Connection 更加高级的抽象,从其方法上更加可以看出他具有更加明显的操作特征。
Transaction
事务(Transaction) ,正是对N(N>=1)个操作执行时,同时成功或同时失败的 关系 的具象。

我们先看下sqlsession是如何进行数据库操作的:

 String resource = "mybatis-config.xml";
//获取数据配置流
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//对数据库执行操作
try {
TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
TbUser user = new TbUser("liybk", "liybk","186..","123");
userMapper.insertUser(user);
sqlSession.commit();// 这里一定要提交,不然数据进不去数据库中
} finally {
sqlSession.close();
}

  

我门先看SqlSessionFactoryBuilder().build(inputStream)方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset(); try {
inputStream.close();
} catch (IOException var13) {
} } return var5;
} public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

  

最终执行通过一系列的xml文件解析,返回了DefaultSqlSessionFactory,进入DefaultSqlSessionFactory构造函数

public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}

  

构造函数只是初始化了其configuration 属性,这个configuration 里面包含了一个Environment属性,而Environment属性里又有数据源,connect,事务等等一系列关于数据库操作的基本属性

public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
....
}

  

好的,我们回到主步骤SqlSession sqlSession = sqlSessionFactory.openSession()方法
其执行类是DefaultSqlSessionFactory,目的是获取sqlSession

public SqlSession openSession() {
return
//调用该类的openSessionFromDataSource方法
this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
} //openSessionFromDataSource方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null; DefaultSqlSession var8;
try {
//获取Environment
Environment environment = this.configuration.getEnvironment();
//从Environment中取得TransactionFactory;
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//在取得的数据库连接上创建事务对象Transaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建Executor对象
Executor executor = this.configuration.newExecutor(tx, execType);
//创建sqlsession对象。
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
} return var8;
}

  

可以看到mybatis创建sqlsession经过了以下几个主要步骤:

  1. 从核心配置文件mybatis-config.xml中获取Environment(这里面是数据源);
  2. 从Environment中取得DataSource;
  3. 从Environment中取得TransactionFactory;
  4. 从DataSource里获取数据库连接对象Connection;
  5. 在取得的数据库连接上创建事务对象Transaction;
  6. 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
  7. 创建sqlsession对象。

我们对Executor对象着重讲下,因为该对象是执行sql的实现类:
进入 Executor executor = this.configuration.newExecutor(tx, execType)方法

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

  

可以看出,如果开启cache的话,会创建CachingExecutor,否则创建普通Executor,普通Executor有3个基础类型,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。而CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。
Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象。
我们简单的看下其3个基本基础类型中最简单的SimpleExecutor 是怎么执行sql的

public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
} public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null; int var6;
try {
//拿到Configuration 属性
Configuration configuration = ms.getConfiguration();
//拿到StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
//拿到prepareStatement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//prepareStatement执行sql
var6 = handler.update(stmt);
} finally {
this.closeStatement(stmt);
} return var6;
}

  

StatementHandler

可以看出,Executor本质上也是个甩手掌柜,具体的事情原来是StatementHandler来完成的。
当Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。

publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,
ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);
statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
returnstatementHandler;
}

  

可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。例如像实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的

看完了Executor具体执行过程,还没结束,我们还不知道在执行前一步,就是代码块前俩部,到底做了什么关联,再一次贴出来:

        .....
//通过sqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
1、 TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
2、 TbUser user = new TbUser("liybk", "liybk","186..","123");
3、 userMapper.insertUser(user);

  

那么这个mapper作用到底是什么呢,它是如何创建的呢,它又是怎么与sqlsession等关联起来的呢?
我们进入方法:

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

  

最终调用的是configuration的mapperRegistry方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
//mapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
} //mapperProxyFactory
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

  

可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。
我们进入Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy)方法,注意这个方法传入的参数mapperProxy

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//拿到mapper接口类
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//进行权限检查
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
} /*
* Look up or generate the designated proxy class.
*/
//查找/生成代理类
Class<?> cl = getProxyClass0(loader, intfs); /*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//将mapperProxy参数转为InvocationHandler 传入
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}

  

mapperProxy是InvocationHandler的子类,再进入cons.newInstance(new Object[]{h})方法

 public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

  

结果是将mapperProxy作为构造参数返回了一个代理实现类,我们再看下mapperProxy这个类的主要方法
invoke
我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来,MapperProxy的invoke如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
} MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}

  

可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
} if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}

  

可以看到,MapperMethod就像是一个分发者,他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。

最后:

大家有什么不懂的欢迎在下方留言讨论,或者也可以私信问我,一般我在下班后看到都会回复的,有时候比较忙的时候没看到希望谅解,也可以关注我的公众号:前程有光!

深度分析:mybatis的底层实现原理,看完你学会了吗?的更多相关文章

  1. 动态代理之投鞭断流!看一下MyBatis的底层实现原理

    转:https://mp.weixin.qq.com/s?__biz=MzI1NDQ3MjQxNA==&mid=2247486856&idx=1&sn=d430be5d14d1 ...

  2. 源码学习之MyBatis的底层查询原理

    导读 本文通过MyBatis一个低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查询流程,从配置文件的解析到一个查询的完整执行过程详细解读MyBatis的一次查询流程,通过本 ...

  3. 深度分析:java8的新特性lambda和stream流,看完你学会了吗?

    1. lambda表达式 1.1 什么是lambda 以java为例,可以对一个java变量赋一个值,比如int a = 1,而对于一个方法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变 ...

  4. MyBatis温故而知新-底层运行原理

    准备工作 public class MainClass { public static void main(String[] args) throws Exception { String resou ...

  5. 深度分析:面试腾讯,阿里面试官都喜欢问的String源码,看完你学会了吗?

    前言 最近花了两天时间,整理了一下String的源码.这个整理并不全面但是也涵盖了大部分Spring源码中的方法.后续如果有时间还会将剩余的未整理的方法更新到这篇文章中.方便以后的复习和面试使用.如果 ...

  6. 深度分析:SpringBoot异常捕获与封装处理,看完你学会了吗?

    SpringBoot异常处理 简介 ​ 日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管.这种 ...

  7. Mybatis拦截器实现原理深度分析

    1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...

  8. 深度剖析HashMap的数据存储实现原理(看完必懂篇)

    深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...

  9. MyBatis底层实现原理: 动态代理的运用

    转载:https://mp.weixin.qq.com/s/_6nyhaWX15mh3mkj8Lb0Zw Mybatis中声明一个interface接口,没有编写任何实现类,Mybatis就能返回接口 ...

随机推荐

  1. 【git冲突解决】: Please commit your changes or stash them before you merge.

    刚刚使用 git pull 命令拉取代码时候,遇到了这样的问题: error: Your local changes to the following files would be overwritt ...

  2. BeanCopier的使用

    BeanCopier进行的是bean之间的copy,从一个类到另一个类,进行属性值的拷贝. 成功copy的条件: 1.属性的类型和名称都相同 2.目标类的setter缺少或缺失会导致拷贝失败,名称相同 ...

  3. 第二个 SignalR,可以私聊的聊天室

    一.简介 上一次,我们写了个简单的聊天室,接下来,我们来整一个可以私聊的聊天室. SignalR 官方 API 文档 需求简单分析: 1.私聊功能,那么要记录用户名或用户ID,用于发送消息. 2.怎么 ...

  4. Linux入门到放弃之七《进程管理》

    进程管理 1.查看所有进程,并用全格式显示: 命令:ps -ef 2.用ps命令查看系统当前的进程,并把系统当前的进程保存到文件process中: 命令:ps aux >> process ...

  5. spring-shiro 安全框架配置

    <!--创建自定义域对象--><bean id="authRealm" class="com.aaa.ssm.shiro.AuthRealm" ...

  6. 记一次py交易

    讲一个故事 以下故事真实性不保证(你们懂的) 我没说这个是真的 所以不能当做以后别人挑我刺的证据 我只是讲个故事罢了 故事可以是fake 我不会承认这个故事是真的罢了 朋友是某c9高校工科专业 学校培 ...

  7. 使用Vue简单的写组件的UI库

    初始化项目vue create nature-ui 在根目录下面创建一个文件目录放置组件(我这里的创建packages) packages 目录下面创建个个组件的名称并创建index.js(用于输出所 ...

  8. PCB layout注意事项

    1.信号线一般12mil以上,选15或20左右及以上 via内外径选2倍关系的,如内径10mi外径20mil,但不是绝对,内10外15.18也可. 2.mil与mm单位转换,即100mil=2.54m ...

  9. SQL SERVER迁移--更换磁盘文件夹

    默认情况下SQL SERVER的安装路径与数据库的默认存放路径是在C盘的--这就很尴尬. 平时又不注意,有天发现C盘的剩余空间比较吃紧了,于是着手想办法迁移文件夹. 一.环境准备 数据库版本--SQL ...

  10. viewpage和tablayout导航栏

    引入material库: implementation 'com.google.android.material:material:1.2.1' <?xml version="1.0& ...