Mybatis原理

Mybatis原理

写在前面:好久没有写了,这阵子忙着面试,然后过年,然后过完年交接工作,来到新的公司工作有三周了,新工作也算有点轨迹了;下面就接着搬运吧;

一、回顾JDBC的调用

在分析mybatis原理之前,我们先来回顾一下jdbc是怎么调用的

public class TestJDBC {
    private static String url = "jdbc:mysql://127.0.0.1:3306/hello";
    private static String userName = "root";
    private static String pwd = "root";
    public static void main(String[] args) {
        String sql = "select now() date1 from dual";
        Connection connection =null;
        Statement statement = null;
        ResultSet resultSet =null;
        try {
            //1、加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取数据库连接
            connection = DriverManager.getConnection(url, userName, pwd);
            //3、准备statement
            statement = connection.createStatement();
            //4、执行sql查询
            statement.execute(sql);
            //5、获取查询结果
            resultSet = statement.getResultSet();
            while (resultSet.next()){
                System.out.println(resultSet.getString(1));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //6、关闭资源
            try {
                if (resultSet !=null){
                resultSet.close();
                }
                if(statement !=null){
                    statement.close();
                }
                if (connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

====================================================================================
这里再说一下preparedstatement和statement的区别
1、PreparedStatement继承自Statement,都是接口;
2、preparedStatement可以用占位符,是预编译的,批处理比Statement效率高;
3、由于preparedStatement是预编译的,所以它可以防止sql注入;sql注入问题传送门

二、Mybatis的原理

下面就通过代码的方式看一下从配置文件到SQL执行的调用流程

public class TestMain {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        /**
         * 1、通过XmlConfigBuilder解析配置XML,解析后的信息放入到单例Configuration
         *      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
         *             var5 = this.build(parser.parse());
         * 2、使用Configuration去创建一个DefaultSqlSessionFactory(这是单线程是使用的SqlSessionFactory)
         *  public SqlSessionFactory build(Configuration config) {
         *         return new DefaultSqlSessionFactory(config);
         *     }
         * 3、构建Configuration是一个很重要的过程
         */
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session =null;
        try {
            /**
             *  public SqlSession openSession() {
             *         return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(),                                (TransactionIsolationLevel)null, false);
             *     }
             * private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean  autoCommit) {
             *         Transaction tx = null;
             *         DefaultSqlSession var8;
             *         try {
             *             Environment environment = this.configuration.getEnvironment();
             *             这里的TransactionFactory尤其要注意一下,
             *             在我们做多数据源管理的时候,如果需要重写这里的newTransaction()
             *             而Transaction也要自定义;
             *             TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
             *             tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
             *             这里可以看到关于事务的信息被放到了执行器中,将来获取connection连接的时候,会使用到
             *             Executor executor = this.configuration.newExecutor(tx, execType);
             *             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;
             *     }
             */
             session = sqlSessionFactory.openSession();
            /**
             * sqlSession获取mapper使用了动态代理技术
             *  1、使用Configuration来获取mapper对象
             *  public <T> T getMapper(Class<T> type) {
             *         return this.configuration.getMapper(type, this);
             *     }
             *  2、mapperRegistry是Configuration中的一个mapper的注册器
             *  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
             *         return this.mapperRegistry.getMapper(type, sqlSession);
             *     }
             *  3、通过代理工厂获取代理对象
             *  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
             *          //这个应该是在之前Configuration中创建好的map中的信息
             *         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);
             *             }
             *         }
             *     }
             *  4、这里使用了JDK动态代理,mapperProxy实现了invocationHandler接口
             *  protected T newInstance(MapperProxy<T> mapperProxy) {
             *         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);
             *     }
             *  5、看一下具体怎么运行的
             *   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             *         try {
             *              //这里判断mapper是不是一个类,这里不是,所以不会调用invoke;
             *             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);
             *     }
             *  6、这里根据command命令选择不同的执行
             *    public Object execute(SqlSession sqlSession, Object[] args) {
             *         Object param;
             *         Object result;
             *         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);
             *             }
             *             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;
             *         }
             *     }
             *  7、重点看一下executeForMany方法:这里可以看到最终还是sqlSession去执行查询,这里如果继续跟下去,会发现其实是Executor再干活
             *  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
             *         Object param = this.method.convertArgsToSqlCommandParam(args);
             *         List result;
             *         if (this.method.hasRowBounds()) {
             *             RowBounds rowBounds = this.method.extractRowBounds(args);
             *             result = sqlSession.selectList(this.command.getName(), param, rowBounds);
             *         } else {
             *             result = sqlSession.selectList(this.command.getName(), param);
             *         }
             *
             *         if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
             *             return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
             *         } else {
             *             return result;
             *         }
             *     }
             *
             *
             */
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student student = mapper.getStudentById(1);
            System.out.println(student);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            session.close();
        }
    }
}

=======================================================================================================================

上面说到一直跟下去会跟到SimpleExecutor这个Executor中,下面看一下Executor内部执行原理;
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 handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //这里主要是获取数据库连接,并准备执行查询的Statement;
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        //执行SQL并返回处理结果,这里通过PreparedStatement进行查询,并使用resultSetHandler处理返回结果
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }
    return var9;
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    //这里获取数据库连接
    Connection connection = this.getConnection(statementLog);
    //这里准备执行SQL的statement;
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    //准备参数,这里往下看的话,其实是DefaultParameterHandler在处理
    handler.parameterize(stmt);
    return stmt;
}

这里可以看到获取数据源连接是从transaction中获取的,也就是我们在创建sqlSessionFactory的时候创建的Executor时,注入的transaction
protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = this.transaction.getConnection();
    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}

参考书籍:《JavaEE 互联网轻量级框架整合开发》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值