mybatis的底层实现原理

    技术2023-05-27  34

    最近发现自己对jdbc和mybatis底层实现了解很浅,特此写一篇关于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();

    关于jdbc更详细内容可以看我另一篇博客:jdbc原理详解,这里就不细讲了

    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经过了以下几个主要步骤:

    从核心配置文件mybatis-config.xml中获取Environment(这里面是数据源); 从Environment中取得DataSource; 从Environment中取得TransactionFactory; 从DataSource里获取数据库连接对象Connection; 在取得的数据库连接上创建事务对象Transaction; 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的); 创建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上来,例如下一步的userMapper.insertUser(user),将调用此方法,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就真正的关联起来了。 最后不得不提下遇到之前一个没想到过的问题。mapper.xml文件的作用在这里体现的是将声明的sql语句与类产生映射,并放入statements,在进行执行接口方法时,能通过statements的这个map拿到方法进行反射执行,然后将结果通过映射形成结果类型返回。到这里,可以发现没有mapper.xml其实对于整个流程是没有影响的,因为excutor有3个,其中simpleExcutor被默认封装了很多方法,这里通过simpleExcutor去绑定sql执行,然后返回默认类型,这种情况则对于mapper.xml文件来说,就显得无用了。

    关于反射,可以看下这篇文章关于动态代理invoke()方法的理解,可以有更好的理解

    Processed: 0.044, SQL: 8