Mybatis-plus 代码分析到那里算那里

MybatisPlusAutoConfiguration

@Configuration  将该类加入到Spring容器中
sqlSessionFactory和SqlSessionFactoryBean类的依赖关系必须存在
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
DataSource类的实例必须存在
@ConditionalOnSingleCandidate(DataSource.class)
加载配置到MyBatisPlusProperties中
@EnableConfigurationProperties({MybatisPlusProperties.class})
在其他类加载完成之后在加载DataSourceAutoConfiguration
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})

底层源码分析

# 获取执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
  #判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE
   executorType = executorType == null ? defaultExecutorType : executorType;  
   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;  
   Executor executor;  
   # 根据执行器类型返回不同类型的执行器
   # 器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor
   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;  
 }

默认的执行器过程

 public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
           # 根据SQL的ID到配置信息中找对应的MappedStatement,
           #在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,
           #并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            # 调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

底层是如何执行的

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            # 获取配置信息对象
            Configuration configuration = ms.getConfiguration();
            # 通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            # 预处理StatementHandler对象,得到Statement对象
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            # 传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

newStatementHandler

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  		#根据相关的参数获取对应的StatementHandler对象。
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        #为StatementHandler对象绑定拦截器插件
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

prepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        //从连接中获取Statement对象
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        //处理预编译的传入参数 
        return stmt;
    }

首先我们从最原始的jdbc操作数据库开始

public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/dynamic?serverTimezone=GMT&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8";
        //获取驱动
        Class.forName("com.mysql.jdbc.Driver");
        //获取于与数据库连接
        Connection connection = DriverManager.getConnection(url, "root","123456");
        //定义sql语句
        String sqlConnect = "SELECT id,name,age,email FROM student";
        //获取执行的对象
        PreparedStatement statement = connection.prepareStatement(sqlConnect);
        //执行
        ResultSet set = statement.executeQuery();
        while (set.next()){
            String string = set.getString(2);
            System.out.println("获取用户名称---->"+string);
        }
    }
		PreparedStatement是Statement的子接口,
		获取PrepareStatement对象是,必须传入一个待封装的SQL语句

什么是sql注入

先看代码

String param = "test or 1=1";
 //什么是sql注入
        sqlConnect = sqlConnect + param;
        PreparedStatement preparedStatement = connection.prepareStatement(sqlConnect);
        //执行sql语句
        ResultSet resultSet = preparedStatement.executeQuery();
        boolean next = resultSet.next();
        System.out.println(next); //结果返回的是true
上面的去数据查询的 
select * from student where name = 'test' or 1=1  条件成立把所有数据都查询出来
String param = "test or 1=1";
 PreparedStatement preparedStatement = connection.prepareStatement(sqlConnect);
        preparedStatement.setString(1,param);
        //执行sql语句
        ResultSet resultSet = preparedStatement.executeQuery();
        boolean next = resultSet.next();
        System.out.println(next);  //false
这段代码执行的查询sql语句: select file from file where name = '\'test\' or 1=1'
我们可以看到输出的SQL文是把整个参数用引号包起来,并把参数中的引号作为转义字符,从而避免了参数也作为条件的一部分

还是研究源码

public void setString(int parameterIndex, String x) throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (x == null) {
                this.setNull(parameterIndex, 1);
            } else {
                this.checkClosed();
                //获取参数的长度
                int stringLength = x.length();
                //字符串缓冲区
                StringBuilder buf;
                if (this.connection.isNoBackslashEscapesSet()) {
                    // 判断是否包含特殊符号
                    boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
                    Object parameterAsBytes;
                    byte[] parameterAsBytes;
                    if (!needsHexEscape) {
                        parameterAsBytes = null;
                        buf = new StringBuilder(x.length() + 2);
                        buf.append('\'');
                        buf.append(x);
                        buf.append('\'');
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(buf.toString());
                        }

                        this.setInternal(parameterIndex, parameterAsBytes);
                    } else {
                        parameterAsBytes = null;
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(x);
                        }

                        this.setBytes(parameterIndex, parameterAsBytes);
                    }

                    return;
                }

                String parameterAsString = x;
                boolean needsQuoted = true;
                if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
                    needsQuoted = false;
                    buf = new StringBuilder((int)((double)x.length() * 1.1D));
                    buf.append('\'');

                    for(int i = 0; i < stringLength; ++i) {
                        char c = x.charAt(i);
                        switch(c) {
                        case '\u0000':
                            buf.append('\\');
                            buf.append('0');
                            break;
                        case '\n':
                            buf.append('\\');
                            buf.append('n');
                            break;
                        case '\r':
                            buf.append('\\');
                            buf.append('r');
                            break;
                        case '\u001a':
                            buf.append('\\');
                            buf.append('Z');
                            break;
                        case '"':
                            if (this.usingAnsiMode) {
                                buf.append('\\');
                            }

                            buf.append('"');
                            break;
                        case '\'':
                            buf.append('\\');
                            buf.append('\'');
                            break;
                        case '\\':
                            buf.append('\\');
                            buf.append('\\');
                            break;
                        case '¥':
                        case '₩':
                            if (this.charsetEncoder != null) {
                                CharBuffer cbuf = CharBuffer.allocate(1);
                                ByteBuffer bbuf = ByteBuffer.allocate(1);
                                cbuf.put(c);
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == 92) {
                                    buf.append('\\');
                                }
                            }

                            buf.append(c);
                            break;
                        default:
                            buf.append(c);
                        }
                    }

                    buf.append('\'');
                    parameterAsString = buf.toString();
                }

                buf = null;
                byte[] parameterAsBytes;
                if (!this.isLoadDataQuery) {
                    if (needsQuoted) {
                        parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    } else {
                        parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    }
                } else {
                    parameterAsBytes = StringUtils.getBytes(parameterAsString);
                }

                this.setInternal(parameterIndex, parameterAsBytes);
                this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
            }

        }
    }
上面代码就是判断参数是否包含特殊的符号,如果包含进行转义,把整个参数用引号包含起来

操作数据库的参数

Connection 是通过数据库的url,username,password获取与数据库连接的接口
创建好连接可以向数据库发送sql语句
Statement作用就是执行静态的sql
statement.execute()返回的是boolean类型,我们只知道sql是否执行成功,但是获取不到数据
如果想要拿到数据,需要执行executeQuery()方法,该方法的返回值是ResultSet
在日常操作时候我们需要用PreparedStatement代替Statement来执行sql语句
为了防止sql注入(就是把字符串类型的参数用引号包裹起来,当成一个参数传入)

了解以上可以开始mybatis的流程分析

SqlSessionFactroyBulider 读取Mybatis-config.xml的配置文件,通过bulider()方法构建一个
SqlSessionFactroy;
获取SqlSessionFacotry之后,可以通过SqlSession对象
**SqlSession可以对数据库进行CRUD操作**

MapperProxy

mapperProxy动态代理mapper(就是操作数据库的层面)
//获取代理对象
public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }
Configuration提供的方法
 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);
            }
        }
    }
具体方法代码如下
protected T newInstance(MapperProxy<T> mapperProxy) {
		//底层就是我们JDK自带的动态代理
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }

怎么具体执行sql语句的

	MapperProxy在执行时会触发此方法
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
兜兜转转代码如下

    private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MapperMethod mapperMethod;

        public PlainMethodInvoker(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }

        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            return this.mapperMethod.execute(sqlSession, args);
        }
    }

    interface MapperMethodInvoker {
        Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable;
    }
最后代码交给MapperMethod来执行
//先判断CRUD类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了
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;
        }
    }

选择其中一个方法分析

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //CRUD实际上是交给Excetor去处理, excutor其实也只是穿了个马甲而已,小样,别以为穿个马甲我就不认识你嘞!
      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();
    }
  }

通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler封装了Statement, 让 StatementHandler 去处理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement)
 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = this.boundSql.getSql();
        statement.execute(sql);
        return this.resultSetHandler.handleResultSets(statement);
    }

到这里已经分析结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值