Mybatis原理及源碼分析
本文轉(zhuǎn)載自微信公眾號(hào)「三不猴子」,作者sanbuhouzi。轉(zhuǎn)載本文請(qǐng)聯(lián)系三不猴子公眾號(hào)。
作為Java程序員Mybatis應(yīng)該是一個(gè)必會(huì)框架了,其源碼體量只有Spring 的1/5,也是Hibernate的1/5 ,相比于其他流行框架Mybatis源碼無(wú)疑是學(xué)習(xí)成本最低的,當(dāng)做年輕人看的第一個(gè)框架源碼,無(wú)疑是非常好的。
整體架構(gòu)
對(duì)于一個(gè)陌生的事物切勿一頭扎進(jìn)細(xì)節(jié)里,我們先要觀其大概看看架構(gòu)脈絡(luò),MyBatis 分為三層架構(gòu),分別是基礎(chǔ)支撐層、核心處理層和接口層。
Mybatis 整體架構(gòu)
基礎(chǔ)支撐層
基礎(chǔ)支撐層是這個(gè)Mybatis框架的基建,為整個(gè)Mybatis框架提供非常基礎(chǔ)的功能。(篇幅有限下面我們只對(duì)部分模塊做簡(jiǎn)單的分析)
1.類型轉(zhuǎn)換模塊,我們?cè)贛ybatis中使用< typeAliase >標(biāo)簽定義一個(gè)別名就是使用類型轉(zhuǎn)換模塊實(shí)現(xiàn)的。類型轉(zhuǎn)換模塊最重要的功能還是實(shí)現(xiàn)了Mybatis中JDBC類型和Java類型之間的轉(zhuǎn)換。主要體現(xiàn)在:
- 在SQL模板綁定用戶參入實(shí)參的場(chǎng)景中,將Java類型轉(zhuǎn)換成JDBC類型
- 在結(jié)果集中,將JDBC類型轉(zhuǎn)換成Java類型。
Mybatis類型轉(zhuǎn)換
2.日志模塊,產(chǎn)生日志,定位異常。
3.反射工具,對(duì)原生的Java反射作了一些封裝。
4.Binding模塊,我們?cè)趫?zhí)行Mybatis中的方法時(shí)都是通過(guò)SqlSession來(lái)獲Mapper接口中的代理,這個(gè)代理將Mapper.xml文件中SQL進(jìn)行關(guān)聯(lián)就是通過(guò)Binding模塊來(lái)實(shí)現(xiàn)的。值得注意點(diǎn)是這個(gè)過(guò)程是發(fā)生在編譯期。可以將錯(cuò)誤提前到編譯期。
5.數(shù)據(jù)源模塊,數(shù)據(jù)源對(duì)于一個(gè)ORM來(lái)說(shuō)算是非常核心的組件之一了。Mybatis默認(rèn)的數(shù)據(jù)源是非常出色的,Mybatis同時(shí)也支持集成第三方的數(shù)據(jù)源。
6.緩存模塊,緩存模塊的好壞直接影響這個(gè)ORM的性能。Mybatis提供了兩級(jí)緩存,同時(shí)也支持第三方緩存中間件集成。
Mybatis緩存
7.解析器模塊,主要是config.xml配置文件解析,和mapper.xml文件解析。
8.事務(wù)管理模塊,事務(wù)模塊對(duì)數(shù)據(jù)庫(kù)的事務(wù)機(jī)制進(jìn)行控制,對(duì)數(shù)據(jù)庫(kù)事務(wù)進(jìn)行了抽象,同時(shí)也對(duì)spring框架進(jìn)行了整合。詳細(xì)的處理邏輯下文會(huì)結(jié)合源碼詳細(xì)解析。
核心處理層
核心處理層是我們?cè)趯W(xué)習(xí)Mybatis原理的時(shí)候需要花80%時(shí)間的地方。核心處理層是 MyBatis 核心實(shí)現(xiàn)所在,其中涉及 MyBatis 的初始化以及執(zhí)行一條 SQL 語(yǔ)句的全流程。
配置解析
MyBatis 的初始化以及執(zhí)行一條 SQL 語(yǔ)句的全流程中也包含了配置解析,我們?cè)诂F(xiàn)實(shí)開(kāi)發(fā)中一般都是使用spring boot starter的自動(dòng)配置。我們一項(xiàng)目啟動(dòng)為起點(diǎn)一層一層剝開(kāi)Mybatis的流程。先打開(kāi)org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration首先明確一點(diǎn)就是MybatisAutoConfiguration的目的就是要得到一個(gè)SqlSessionFactory。
- @Bean
- @ConditionalOnMissingBean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
- SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
- factory.setDataSource(dataSource);
- factory.setVfs(SpringBootVFS.class);
- if (StringUtils.hasText(this.properties.getConfigLocation())) {
- factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
- }
- Configuration configuration = this.properties.getConfiguration();
- if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
- configuration = new Configuration();
- }
- if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
- for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
- customizer.customize(configuration);
- }
- }
- factory.setConfiguration(configuration);
- if (this.properties.getConfigurationProperties() != null) {
- factory.setConfigurationProperties(this.properties.getConfigurationProperties());
- }
- if (!ObjectUtils.isEmpty(this.interceptors)) {
- factory.setPlugins(this.interceptors);
- }
- if (this.databaseIdProvider != null) {
- factory.setDatabaseIdProvider(this.databaseIdProvider);
- }
- if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
- factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
- }
- if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
- factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
- }
- if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
- factory.setMapperLocations(this.properties.resolveMapperLocations());
- }
- return factory.getObject();
- }
這里是通過(guò)MybatisProperties里面的配置并放入到SqlSessionFactoryBean中,再由SqlSessionFactoryBean得到SqlSessionFactory。看到最后一行return factory.getObject();我們進(jìn)去看看這個(gè)factory.getObject()的邏輯是如何得到一個(gè)SqlSessionFactory。
- @Override
- public SqlSessionFactory getObject() throws Exception {
- if (this.sqlSessionFactory == null) {
- afterPropertiesSet();
- }
- return this.sqlSessionFactory;
- }
這一步?jīng)]什么好說(shuō)的,看看afterPropertiesSet()方法
- @Override
- public void afterPropertiesSet() throws Exception {
- notNull(dataSource, "Property 'dataSource' is required");
- notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
- state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
- "Property 'configuration' and 'configLocation' can not specified with together");
- this.sqlSessionFactory = buildSqlSessionFactory();
- }
重點(diǎn)來(lái)了,看看這個(gè)buildSqlSessionFactory()方法這里的核心目的就是將configurationProperties解析到Configuration對(duì)象中。代碼太長(zhǎng)了我就不貼出來(lái)了, buildSqlSessionFactory()的邏輯我畫(huà)了個(gè)圖,有興趣的小伙伴自行看一下。
Mybatis配置解析1
我們不要陷入細(xì)節(jié)之中,我們看看中點(diǎn)看看buildSqlSessionFactory() 方法的最后一行this.sqlSessionFactoryBuilder.build(configuration)點(diǎn)進(jìn)去
- public SqlSessionFactory build(Configuration config) {
- return new DefaultSqlSessionFactory(config);
- }
通過(guò)buildSqlSessionFactory()解析得到的Configuration對(duì)象創(chuàng)建一個(gè)DefaultSqlSessionFactory(config),到此我們就得到了SqlSessionFactory同時(shí)被配置成一個(gè)bean了。
我們最終操作都是SqlSession,什么時(shí)候會(huì)通過(guò)SqlSessionFactory得到一個(gè)SqlSession呢?
要解決這個(gè)問(wèn)題我們回到最開(kāi)始的MybatisAutoConfiguration的sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)這個(gè)方法,點(diǎn)開(kāi)SqlSessionTemplate發(fā)現(xiàn)它是一個(gè)實(shí)現(xiàn)了SqlSession到這里我們猜測(cè)就是在這里SqlSessionFactory會(huì)構(gòu)建一個(gè)SqlSession出來(lái)。我們進(jìn)入new SqlSessionTemplate(sqlSessionFactory)看看源碼。
- public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
- this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
- }
再往下看,我們就看到了
- public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
- PersistenceExceptionTranslator exceptionTranslator) {
- notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
- notNull(executorType, "Property 'executorType' is required");
- this.sqlSessionFactory = sqlSessionFactory;
- this.executorType = executorType;
- this.exceptionTranslator = exceptionTranslator;
- this.sqlSessionProxy = (SqlSession) newProxyInstance(
- SqlSessionFactory.class.getClassLoader(),
- new Class[] { SqlSession.class },
- new SqlSessionInterceptor());
- }
這里通過(guò)動(dòng)態(tài)代理創(chuàng)建了一個(gè)SqlSession。
參數(shù)映射、SQL解析
我們先看一下MapperFactoryBean類,這個(gè)類實(shí)現(xiàn)了FactoryBean在bean初始化的時(shí)候會(huì)調(diào)用getObject()方法我們看看這個(gè)類下重寫(xiě)的getObject()方法里的內(nèi)容。
- @Override
- public T getObject() throws Exception {
- return getSqlSession().getMapper(this.mapperInterface);
- }
這里調(diào)用了sqlSession的getMapper()方法。一層一層點(diǎn)進(jìn)去里面返回的是一個(gè)代理對(duì)象。最后的執(zhí)行是由MapperProxy執(zhí)行。
- public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
- final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
- if (mapperProxyFactory == null) {
- throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
- }
- try {
- return mapperProxyFactory.newInstance(sqlSession);
- } catch (Exception e) {
- throw new BindingException("Error getting mapper instance. Cause: " + e, e);
- }
- }
接下來(lái)的流程我還是畫(huà)個(gè)流程圖,防止小伙伴們走丟。我這里的內(nèi)容可能未必完全和小標(biāo)題一樣,我主要按照sql執(zhí)行的流程講解的。
Mybatis參數(shù)綁定
先看一下MapperProxy中的invoke方法,cachedMapperMethod()方法將MapperMethod緩存起來(lái)了。
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- if (Object.class.equals(method.getDeclaringClass())) {
- return method.invoke(this, args);
- } else if (isDefaultMethod(method)) {
- return invokeDefaultMethod(proxy, method, args);
- }
- } catch (Throwable t) {
- throw ExceptionUtil.unwrapThrowable(t);
- }
- final MapperMethod mapperMethod = cachedMapperMethod(method);
- return mapperMethod.execute(sqlSession, args);
- }
- private MapperMethod cachedMapperMethod(Method method) {
- MapperMethod mapperMethod = methodCache.get(method);
- if (mapperMethod == null) {
- mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
- methodCache.put(method, mapperMethod);
- }
- return mapperMethod;
- }
我們?cè)谕驴磎apperMethod.execute(sqlSession, args)方法。
- public Object execute(SqlSession sqlSession, Object[] args) {
- Object result;
- switch (command.getType()) {
- case INSERT: {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.insert(command.getName(), param));
- break;
- }
- case UPDATE: {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.update(command.getName(), param));
- break;
- }
- case DELETE: {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.delete(command.getName(), param));
- break;
- }
- case SELECT:
- if (method.returnsVoid() && method.hasResultHandler()) {
- executeWithResultHandler(sqlSession, args);
- result = null;
- } else if (method.returnsMany()) {
- result = executeForMany(sqlSession, args);
- } else if (method.returnsMap()) {
- result = executeForMap(sqlSession, args);
- } else if (method.returnsCursor()) {
- result = executeForCursor(sqlSession, args);
- } else {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = sqlSession.selectOne(command.getName(), param);
- }
- break;
- case FLUSH:
- result = sqlSession.flushStatements();
- break;
- default:
- throw new BindingException("Unknown execution method for: " + command.getName());
- }
- if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
- throw new BindingException("Mapper method '" + command.getName()
- + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
- }
- return result;
- }
method.convertArgsToSqlCommandParam(args)這里就是處理參數(shù)轉(zhuǎn)換的邏輯。還有很多細(xì)節(jié)由于篇幅有限以及時(shí)間倉(cāng)促我們不做過(guò)多的贅述,感興趣的小伙伴可以結(jié)合上面的圖自己看看。下面我們看SQL的執(zhí)行流程是怎么樣的。整體流程如下圖。
Mybatis執(zhí)行流程
我們就不對(duì)每一個(gè)執(zhí)行器都分析,我只挑一個(gè)SimpleExecutor來(lái)具體跟一下源碼。我們還是先看看圖吧,防止自己把自己搞蒙。
以simpleExecutor為例的執(zhí)行流程
- @Override
- 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(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
- stmt = prepareStatement(handler, ms.getStatementLog());
- return handler.<E>query(stmt, resultHandler);
- } finally {
- closeStatement(stmt);
- }
- }
這里獲取了Configuration,創(chuàng)建了一個(gè)StatementHandler,預(yù)處理操作,具體執(zhí)行的根據(jù)創(chuàng)建的預(yù)處理方法,最后執(zhí)行query方法
- @Override
- public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
- String sql = boundSql.getSql();
- statement.execute(sql);
- return resultSetHandler.<E>handleResultSets(statement);
- }
到此我們整理了整個(gè)Mybatis的執(zhí)行流程,分析了其中的源碼,由于篇幅有限很多地方都沒(méi)有細(xì)致的分析,但是也貼出了圖,希望能幫助到你。