从一次查询看MyBatis对JDBC的封装

MyBatis是半ORM的持久层框架,把SQL的书写留给了开发人员,然后利用JDBC的API对数据库进行操作。在这里面,MyBatis对JDBC进行了封装,本文从一次查询的执行来跟踪MyBatis的执行过程,了解对JDBC封装的大体结构。
首先看传统的JDBC编码,注意其中主要的元素。
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("url","username", "password");
PreparedStatement preparedStatement = connection.prepareStatement("sql");
preparedStatement.setObject(1, "param");
ResultSet resultSet = preparedStatement.executeQuery();
List<Object> result = dealResultSet(resultSet);
System.out.println(result);
JDBC中首先需要取得数据库的链接connection,然后创建预编译语句preparedStatement对象并向其注入参数,然后返回ResultSet结果集,从结果集中获取数据构造对象。
再看一下MyBatis执行一次查询的代码,然后跟踪代码分析。
InputStream is = Resources.getResourceAsStream("config/mybatis_config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlSessionFactory.openSession();
List<Object> result = session.selectList("namespace.statement", "parameter");
System.out.println(result);
session.close();
MyBatis首先创建sqlSessionFactory,然后打开sqlSession,然后调用sqlSession的API对数据库进行操作并返回结果,并且是已经处理过的List,最后关闭SqlSession结束操作。
1.创建sqlSessionFactory。读取配置文件的XML信息,创建Configuration对象,并将这个对象设置给sqlSessionFactory使用,这个是MyBatis的核心,所有的行为都由Configuration对象控制。创建的过程,就是将XML对象装换成JAVA对象的过程。
2.打开 sqlSession ,打开DefaultSqlSessionFactory的openSession方法,默认从数据源取链接。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
      return new DefaultSqlSession(configuration, executor);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
在这个方法中,从configuration取得配置文件中的environment,并再从中取得配置的事务管理器transactionFactory(本文不深入探讨),最中意的就是返回一个新的executor,这个executor用来执行相关的操作。然后将configuration, executor设置给sqlSession,打开一个sqlSession,实质上就是新建一个sqlSession对象。接下来看 executor的创建过程。
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
创建 executor的方法需要三个参数, tx, execType, autoCommit。tx事务由事务管理器产生。execType默认是 SIMPLE,还有批量BATCH,可继续执行的Reuse。 autoCommit是否自动提交默认为false。
SimpleExecutor继承BaseExecutorBaseExecutor实现接口Executor。
Executor是定义了各种操作的接口,如update,query,commit,rollback,deferLoad(延时加载)等操作。
BaseExecutor接口方法做了简单实现,并且使用了模板方法的设计模式,把具体的实现留给子类完成。
BaseExecutorquery方法:
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }
可以看到会先从缓存中取,取不到就到数据库中取。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
从数据库中取完后,会将结果List缓存。具体的操作使用模板方法doQuery,在子类中实现。接下来看 SimpleExecutor怎么 doQuery。
 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(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
在这个方法中,又出现了一个新对象StatementHandler,调用他的query方法来执行数据库的查询,参数是Statement和resultHandler。Statement就是JDBC包下的接口了,resultHandler结果处理器默认为空。具体看一下prepareStatement方法。
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }
Connection出现了!这个是真正与数据库的链接,getConnetion方法中取得链接是这样的:Connection connection = transaction.getConnection();从事务中获取,因为一开始的时候创建了事务管理器,新建事务的时候就已经创建了链接,而且将链接与事务管理,所以这边就可以通过当前事务取得当前的链接。
JDBC中链接取完知乎第二个对象是Statement,这边的handler.prepare()方法就是传入connection,获取Statement对象的过程。看一下PrepareStatement对象的获取过程:
 protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }
connection.prepareStatement(sql);和jdbc一样,只不过已经是通过层层包装。最后调用handler.<E>query(stmt, resultHandler);来返回查询的结果。
 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
然后通过resultSetHandler来处理结果,将返回的ResultSet处理成List对象返回。





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值