码迷,mamicode.com
首页 > 其他好文 > 详细

深度分析:mybatis的底层实现原理,看完你学会了吗?

时间:2020-07-05 15:26:13      阅读:40      评论:0      收藏:0      [点我收藏+]

标签:ber   知识   des   omd   hand   resources   delegate   基础   require   

前言

最近在和粉丝聊天的时候被粉丝问到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的底层实现原理,看完你学会了吗?

标签:ber   知识   des   omd   hand   resources   delegate   基础   require   

原文地址:https://www.cnblogs.com/lwh1019/p/13245885.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!