Mybatis 是一個流行的 Java 持久層框架,它提供了一種半自動的 SQL 對映方式,允許開發者在 Java 程式碼中以一種更加直觀和靈活的方式來運算元據庫。當你使用 Mybatis 呼叫 DAO 介面時,背後的工作流程大致如下:
介面定義:首先,你需要定義一個 DAO 介面,這個介面中會包含一些方法,這些方法對應於你想要執行的資料庫操作。
Mapper XML:對於 DAO 介面中的每一個方法,你需要在 Mybatis 的對映檔案(通常是一個 XML 檔案)中定義一個
<mapper>
標籤,裡面包含一個<select>
、<insert>
、<update>
或<delete>
標籤,對應於你想要執行的 SQL 語句。配置檔案:在 Mybatis 的配置檔案(通常是 mybatis-config.xml)中,你需要指定你的 Mapper XML 檔案的位置,這樣 Mybatis 才能找到並載入它們。
SqlSessionFactory:Mybatis 使用 SqlSessionFactory 來建立 SqlSession 物件。SqlSessionFactory 是透過配置檔案和對映檔案構建的,它包含了所有必要的資訊來執行 SQL 語句。
SqlSession:SqlSession 是 Mybatis 中執行 SQL 語句的主要物件。它提供了執行 SQL 語句的方法,例如 selectOne、selectList、insert、update 和 delete 等。
呼叫 DAO 介面:當你呼叫 DAO 介面中的方法時,實際上是 Mybatis 的動態代理機制在起作用。Mybatis 會為 DAO 介面建立一個代理物件,當呼叫介面中的方法時,代理物件會攔截這些呼叫,然後根據方法名找到對應的 SQL 對映語句,並執行。
執行 SQL:Mybatis 透過代理物件,使用 SqlSession 來執行對應的 SQL 語句。執行完成後,SqlSession 會返回結果給呼叫者。
關閉 SqlSession:執行完畢後,應該關閉 SqlSession 以釋放資料庫連線資源。
這個過程涉及到了 Mybatis 的核心元件和工作流程,確保了 SQL 語句的執行和結果的返回。使用 Mybatis 的好處之一就是它允許開發者以一種宣告式的方式來編寫 SQL,同時還能保持程式碼的清晰和易於維護。
Mybatis 的內部實現細節非常豐富,下面我將透過一些關鍵類的原始碼片段來具體展示 Mybatis 的工作原理。
配置解析(XMLConfigBuilder.java)
Mybatis 使用 XMLConfigBuilder 來解析 mybatis-config.xml 檔案:
public Configuration parse() { parseConfiguration(parser.eval(Configuration.class)); return configuration; }
這裏,parseConfiguration 方法會填充 Configuration 物件的屬性,比如資料庫連線資訊、型別別名、型別處理器等。 2. 建立 SqlSessionFactory(SqlSessionFactoryBuilder.java)
使用 XMLConfigBuilder 解析配置後,SqlSessionFactoryBuilder 會建立 SqlSessionFactory:
public SqlSessionFactory build(InputStream inputStream) { try { XMLConfigBuilder xmlParser = new XMLConfigBuilder(inputStream, environment, reporter); return build(xmlParser.parse()); } catch (Exception e) { throw new BuilderException("Error building SqlSession.", e); } }
SqlSession 管理(DefaultSqlSessionFactory.java)
DefaultSqlSessionFactory 提供了建立 SqlSession 的方法:
public SqlSession openSession() { return openSessionFromDataSource(configuration.getEnvironment().getDataSource(), null, false); }
Executor 執行器(BaseExecutor.java)
BaseExecutor 是 Executor 介面的抽象實現,提供了事務和快取管理的框架:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) { ErrorContext.instance().resource(ms.getResource()).activity("querying").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 && resultHandler != null) { resultHandler.handleRows(list); } if (list == null) { list = doQuery(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } return list; }
動態代理(MapperProxy.java)
MapperProxy 使用 JDK 動態代理來攔截 Mapper 介面方法的呼叫:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw new RuntimeException("Error when attempting to invoke method '" + method.getName() + "' on Mapper Proxy.", t); } } else { return mapperMethod.execute(sqlSession, args); } }
對映器 XML 解析(XMLMapperBuilder.java)
XMLMapperBuilder 負責解析 Mapper 的 XML 檔案:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } }
快取機制(PerpetualCache.java)
PerpetualCache 是 Mybatis 快取的基本實現:
public V get(Object key, CacheProvider provider) { V value = (V) cache.get(key); if (value == null) { value = provider.apply(key); cache.put(key, value); } return value; }
TypeHandler 和 ParameterHandler
TypeHandler 介面定義了 Java 型別和 JDBC 型別之間的轉換邏輯:
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
ParameterHandler 負責將方法引數對映到 SQL 語句的引數上:
public void setParameters(PreparedStatement ps) throws SQLException { for (ParameterMapping parameter : boundSql.getParameterMappings()) { if (parameter.getMode() != ParameterMode.OUT) { Object value = parameter.getObject(value); TypeHandler typeHandler = parameter.getTypeHandler(); typeHandler.setParameter(ps, parameter.getI(), value, parameter.getJdbcType()); } } }
結果對映(DefaultResultSetHandler.java)
DefaultResultSetHandler 負責將 ResultSet 中的資料對映到 Java 物件:
public <E> List<E> handleRowValues(ResultSet rs, List<E> resultList, RowBounds rowBounds) throws SQLException { if (hasResultSetData(rs)) { int offset = rowBounds == null || rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET ? 0 : rowBounds.getOffset(); int limit = rowBounds == null || rowBounds.getLimit() == RowBounds.NO_ROW_LIMIT ? Integer.MAX_VALUE : rowBounds.getLimit(); int rowNumber = 0; while (rs.next()) { if (offset > 0) { offset--; continue; } if (limit > 0) { limit--; if (limit == 0) { break; } } Object rowValue = getRowValue(rs, null); resultList.add((E) rowValue); rowNumber++; } } return resultList; }
這些程式碼片段展示了 Mybatis 核心元件的工作原理。然而,由於 Mybatis 的複雜性,這裏只提供了部分關鍵程式碼的簡要概述。要完全理解 Mybatis 的內部實現,需要深入研究每個類和介面的實現細節,以及它們之間的互動。關注威哥愛程式設計,一起向全棧出發。