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继承BaseExecutor,BaseExecutor实现接口Executor。
Executor是定义了各种操作的接口,如update,query,commit,rollback,deferLoad(延时加载)等操作。
BaseExecutor对接口方法做了简单实现,并且使用了模板方法的设计模式,把具体的实现留给子类完成。
BaseExecutor的query方法:
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对象返回。