原文地址
mybatis是一種很是流行的ORM框架,能夠經過一些靈活簡單的配置,大大提高咱們操做數據庫的效率,固然,我以爲它如此受歡迎的緣由更主要的是,它的源碼設計的很是簡單。接下來咱們就來聊聊使用mybatis作一次數據庫查詢操做背後都經歷了什麼。java
首先咱們先上一段很是簡單的代碼,這是原始的JDBC方式的數據庫操做。sql
// 1. 建立數據源
DataSource dataSource = getDataSource();
// 2. 建立數據庫鏈接
try (Connection conn = dataSource.getConnection()) {
try {
conn.setAutoCommit(false);
// 3. 建立Statement
PreparedStatement stat = conn.prepareStatement("select * from std_addr where id=?");
stat.setLong(1, 123456L);
// 4. 執行Statement,獲取結果集
ResultSet resultSet = stat.executeQuery();
// 5. 處理結果集,這一步每每是很是複雜的
processResultSet(resultSet);
// 6.1 成功提交,對於查詢操做,步驟6是不須要的
conn.commit();
} catch (Throwable throwable) {
// 6.2 失敗回滾
conn.rollback();
}
}
複製代碼
下面這段是mybatis鏈接數據庫以及作一樣的查詢操做的代碼。數據庫
DataSource dataSource = getDataSource();
TransactionFactory txFactory = new JdbcTransactionFactory();
Environment env = new Environment("test", txFactory, dataSource);
Configuration conf = new Configuration(env);
conf.setMapUnderscoreToCamelCase(true);
conf.addMapper(AddressMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
AddressMapper mapper = sqlSession.getMapper(AddressMapper.class);
Address addr = mapper.getById(123456L);
}
複製代碼
這是mybatis的Mapper,也很是簡單緩存
@Mapper
public interface AddressMapper {
String TABLE = "std_addr";
@Select("select * from " + TABLE + " where id=#{id}")
Address getById(long id);
}
複製代碼
從上面的代碼能夠看出,經過mybatis查詢數據庫須要如下幾個步驟:mybatis
下面咱們從源碼逐步分析mybatis在一次select查詢中這幾個步驟的詳細狀況。app
Environment有兩個核心屬性,dataSource和transactionFactory,下面是源碼框架
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
}
複製代碼
其中,dataSource用來獲取數據庫鏈接,transactionFactory用來建立事務。
咱們詳細看一下mybatis的JdbcTransactionFactory的源碼,這裏能夠經過數據源或者數據庫鏈接來建立JdbcTransaction。ui
public class JdbcTransactionFactory implements TransactionFactory {
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
複製代碼
我把JdbcTransaction的源碼精簡了一下,大概是這個樣子的。這裏實際上就是把JDBC的DataSource或者一個Connection託管給了mybatis的Transaction對象,由Transaction來管理事務的提交與回滾。this
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommmit;
public Connection getConnection() throws SQLException {
if (connection == null) {
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
if (connection.getAutoCommit() != autoCommmit) {
connection.setAutoCommit(autoCommmit);
}
}
return connection;
}
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.commit();
}
}
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.rollback();
}
}
}
複製代碼
到這裏,運行環境Environment已經準備完畢,咱們能夠從Environment中獲取DataSource或者建立一個新的Transaction,從而建立一個數據庫鏈接。spa
Configuration類很是複雜,包含不少配置信息,咱們優先關注如下核心屬性
public class Configuration {
protected Environment environment;
protected boolean cacheEnabled = true;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 保存着全部Mapper的動態代理對象
protected final MapperRegistry mapperRegistry;
// 保存着全部類型處理器,處理Java類型和JDBC類型的轉換
protected final TypeHandlerRegistry typeHandlerRegistry;
// 保存配置的Statement信息,能夠是XML或註解
protected final Map<String, MappedStatement> mappedStatements;
// 保存二級緩存信息
protected final Map<String, Cache> caches;
// 保存配置的ResultMap信息
protected final Map<String, ResultMap> resultMaps;
}
複製代碼
build
方法能夠看出,mybatis提供了兩種解析配置信息的方式,分別是XMLConfigBuilder和MapperAnnotationBuilder。解析配置的過程,其實就是填充上述Configuration核心屬性的過程。// 根據XML構建
InputStream xmlInputStream = Resources.getResourceAsStream("xxx.xml");
SqlSessionFactory xmlSqlSessionFactory = new SqlSessionFactoryBuilder().build(xmlInputStream);
// 根據註解構建
Configuration configuration = new Configuration(environment);
configuration.addMapper(AddressMapper.class);
SqlSessionFactory annoSqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
複製代碼
public interface TypeHandlerRegistry<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
複製代碼
總而言之,Configuration對象包含了mybatis的Statement、ResultMap、Cache等核心配置,這些配置信息是後續執行SQL操做的關鍵。
咱們提供new SqlSessionFactoryBuilder().build(conf)
構建了一個DefaultSqlSessionFactory,這是默認的SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
複製代碼
DefaultSqlSessionFactory的核心方法有兩個,代碼精簡事後是下面這個樣子的。其實都是一個套路,經過數據源或者鏈接建立一個事務(上面提到的TransactionFactory建立事務的兩種方式),而後建立執行器Executor,最終組合成一個DefaultSqlSession,表明着一次數據庫會話,至關於一個JDBC的鏈接週期。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
複製代碼
下面這段代碼是Configuration對象建立執行器Executor的過程,默認的狀況下會建立SimpleExecutor,而後在包裝一層用於二級緩存的CachingExecutor,很明顯Executor的設計是一個典型的裝飾者模式。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
複製代碼
進行一次數據庫查詢操做的步驟以下:
SqlSession sqlSession = sqlSessionFactory.openSession(true);
複製代碼
AddressMapper mapper = sqlSession.getMapper(AddressMapper.class);
複製代碼
DefaultSqlSession的getMapper
方法參數是咱們定義的Mapper接口的Class對象,最終是從Configuration對象的mapperRegistry
註冊表中獲取這個Mapper的代理對象。
下面是MapperRegistry的getMapper
方法的核心代碼,可見這裏是經過MapperProxyFactory建立代理
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
複製代碼
而後是MapperProxyFactory的newInstance方法,看上去是否是至關熟悉。很明顯,這是一段JDK動態代理的代碼,這裏會返回Mapper接口的一個代理類實例。
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
複製代碼
Address byId = mapper.getById(110114);
複製代碼
這裏其實是調用到Mapper對應的MapperProxy,下面是MapperProxy的invoke
方法的一部分。可見,這裏針對咱們調用的Mapper的抽象方法,建立了一個對應的代理方法MapperMethod。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
複製代碼
我精簡了MapperMethod的execute
方法的代碼,以下所示。其實最終動態代理爲咱們調用了SqlSession的select
方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
break;
}
return result;
}
複製代碼
SqlSession的selectOne
方法最終是調用的selectList
,這個方法也很是簡單,入參statement其實就是咱們定義的Mapper中被調用的方法的全名,本例中就是x.x.AddressMapper.getById
,經過statement獲取對應的MappedStatement,而後交由executor執行query操做。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
複製代碼
前面咱們提到過默認的執行器是SimpleExecutor再裝飾一層CachingExecutor,下面看看CachingExecutor的query代碼,在這個方法以前會先根據SQL和參數等信息建立一個緩存的CacheKey。下面這段代碼也很是明瞭,若是配置了Mapper級別的二級緩存(默認是沒有配置的),則優先從緩存中獲取,不然將調用被裝飾者也就是SimpleExecutor(實際上是BaseExecutor)的query
方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
// cache不爲空,表示當前Mapper配置了二級緩存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
List<E> list = (List<E>) tcm.getObject(cache, key);
// 緩存未命中,查庫
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list);
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
複製代碼
BaseExecutor的query
方法的核心代碼以下所示,這裏有個一級緩存,是開啓的,默認的做用域是SqlSession級別的。若是一級緩存未命中,則調用queryFromDatabase
方法從數據庫中查詢。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> 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);
}
return list;
}
複製代碼
而後將調用子類SimpleExecutor的doQuery
方法,核心代碼以下。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
複製代碼
經過源碼發現Configuration建立的是一個RoutingStatementHandler,而後根據MappedStatement的statementType
屬性建立一個具體的StatementHandler(三種STATEMENT、PREPARED或者CALLABLE)。終於出現了一些熟悉的東西了,這不就是JDBC的三種Statement嗎。咱們選擇其中的PreparedStatementHandler來看一看源碼,這裏就很清晰了,就是調用了JDBC的PreparedStatement的execute
方法,而後將結果交由ResultHandler處理。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
複製代碼
從上面doQuery
的代碼能夠看出,執行的Statement是由prepareStatement
方法建立的,能夠看出這裏是調用了StatementHandler的prepare方法建立Statement,其實是經過MappedStatement的SQL、參數等信息,建立了一個預編譯的PrepareStatement。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
複製代碼
最終,這個PrepareStatement的執行結果ResultSet,會交由DefaultResultSetHandler來處理,而後根據配置中的類型、Results、返回值等信息,生成對應的實體對象。
到這裏咱們就分析完了mybatis作一次查詢操做所經歷的所有流程。固然,這裏面還有一些細節沒有提到,好比說二級緩存、參數和結果集的解析等,這些具體的內容可能會在後續的mybatis源碼解析文章中詳細描述。