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

来源:互联网 发布:js \ join 区别 编辑:程序博客网 时间:2024/06/02 11:07
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对象返回。





0 0
原创粉丝点击