本文是結合spring-mybatis整合進行的分析spring
一、先看看依賴的jar包:sql
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency>
二、mybatis主要兩個關鍵對象時SqlSessionFactory和SqlSession,接下來主要結合源碼對這兩個對象流程進行分析:數據庫
在分析這兩個對象以前先來看看XML配置狀況:session
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" default-autowire="byName" default-lazy-init="true"> <!-- DataSource數據 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="name" value="souchecar"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="20"/> <property name="minIdle" value="2"/> <property name="initialSize" value="2"/> <property name="validationQuery" value="SELECT 1"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="defaultAutoCommit" value="true"/> <property name="removeAbandoned" value="true"/> <property name="removeAbandonedTimeout" value="60"/> <property name="logAbandoned" value="true"/> <property name="filters" value="stat"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- <property name="configLocation" value="classpath:Configuration.xml" /> --> <property name="mapperLocations"> <list> <value>classpath*:sqlmap/**/*.xml</value> </list> </property> <property name="dataSource" ref="dataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/> </beans>
在配置中主要包含了數據源DruidDataSource,SqlSessionFactoryBean(包含了mybatis的映射文件),事物DataSourceTransactionManager,以及SqlSessionTemplate信息的配置,這裏就不對具體的配置做用作過多的介紹mybatis
三、SqlSessionFactoryapp
1)SqlSessionFactory對象是mybatis中的核心對象之一,主要是經過SqlSessionFactory來建立SqlSession對象,通常在一個數據庫中,最好採用單例的模式,將SqlSessionFactory建立成一個單例的對象;ide
2)結合以上的配置和源碼來分析SqlSessionFactory建立的一個過程:在配置文件中,主要是經過SqlSessionFactoryBean來管理SqlSessionFactory建立過程,由於該類實現了InitializingBean接口,因此在spring初始化改bean的時候,會先執行InitializingBean接口中的afterPropertiesSet()方法,在該方法中會去調用buildSqlSessionFactory()方法,該方法是用來建立Configuration對象,將配置文件中配置項信息加載到該對象中,而後在根據Configuration建立SqlSessionFactory,以下:ui
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (logger.isDebugEnabled()) { logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (logger.isDebugEnabled()) { logger.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (logger.isDebugEnabled()) { logger.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (logger.isDebugEnabled()) { logger.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (logger.isDebugEnabled()) { logger.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (logger.isDebugEnabled()) { logger.debug("Registered type handler: '" + typeHandler + "'"); } } } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (logger.isDebugEnabled()) { logger.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource); configuration.setEnvironment(environment); if (this.databaseIdProvider != null) { try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; }
// 1.獲取配置中映射mapper標籤中的信息,存儲到MappedStatement對象中,並sql存儲到SqlSource對象中 try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (logger.isDebugEnabled()) { logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (logger.isDebugEnabled()) { logger.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } // 2.建立SqlSessionFactory對象
return this.sqlSessionFactoryBuilder.build(configuration); }
在該方法中先來分析一下xmlMapperBuilder.parse()方法,這個方法主要是將映射文件中的信息存儲到相應的對象中:this
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); //咱們關注的是這個方法 configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
在方法中configurationElement()主要是用來解析mapper標籤中的信息,咱們主要關注的是這個方法,下來的方法是用來解析映射文件中其餘屬性信息的url
1 private void configurationElement(XNode context) { 2 try { 3 String namespace = context.getStringAttribute("namespace"); 4 if (namespace.equals("")) { 5 throw new BuilderException("Mapper's namespace cannot be empty"); 6 } 7 builderAssistant.setCurrentNamespace(namespace); 8 cacheRefElement(context.evalNode("cache-ref")); 9 cacheElement(context.evalNode("cache")); 10 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 11 resultMapElements(context.evalNodes("/mapper/resultMap")); 12 sqlElement(context.evalNodes("/mapper/sql")); 13 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //咱們關注的是這個方法 14 } catch (Exception e) { 15 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); 16 } 17 }
在configurationElement()方法中用來解析命名空間,參數集,結果集,select,insert,update,delete,標籤等等,咱們關注的是buildStatementFromContext(context.evalNodes("select|insert|update|delete"))方法
1 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 2 for (XNode context : list) { 3 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); 4 try { 5 statementParser.parseStatementNode(); 6 } catch (IncompleteElementException e) { 7 configuration.addIncompleteStatement(statementParser); 8 } 9 } 10 }
在parseStatementNode()方法中,主要是用來將映射文件中的配置信息存儲到MappedStatement對象中去,並將sql語句存儲到了SqlSource對象中,到時候執行sql的時候,直接從該對象中獲取;
四、在配置文件中sqlSession使用的是SqlSessionTemplate,實現了SqlSession接口,就是一個SqlSession模板類,在加載<bean id = sqlSession>標籤時,會去調用SqlSessionTemplate構造方法,SqlSessionTemplate類有三個構造方法,最終會調用
參數sqlSessionFactory是SqlSessionFactoryBean初始化時生成的DefaultSqlSessionFactory,參數executorType是本身傳入的,默認是simple,在這個構造方法中會初始換sqlSession(標記部分),經過動態代理生成SqlSession代理類,這裏重點介紹
SqlSessionInterceptor,這個是SqlSessionTemplate類中的內部類,實現了InvokerHandler,是一個代理類,因此生成的SqlSession代理類,每一個方法都會去執行SqlSessionInterceptor中的invoke()方法,以下所示:
首先會去初始化sqlSession對象,在getSqlSession(sessionFactory, executorType, exceptionTranslator)方法中會去調用sessionFactory中的openSession(executorType)方法,又經過調用openSessionFromDataSource(execType, level, autoCommit)方法,在該方法中進行初始化,對事物對象,executor進行初始化(Configuration.newExecutor(tx, execType)根據傳入的類型建立對應的Executor對象),並建立且返回defaultSqlSession對象;executor對象對接下來sql的執行很重要,會在下一節進行介紹;
五、SqlSession
1)SqlSession主要有四大核心組件對象:Executor,StatementHandler,ParameterHandler,ResultSetHandler
2)在分析這四個組件對象以前,先介紹一下,爲何在使用mybatis,只須要寫映射文件對應的接口類,不須要寫接口的實現類,這是由於mybatis使用Java中的動態代理,在SqlSession.getMapper(Class<T> type)方法時,本質是調用Configuration類下getMapper(Class<T> type, SqlSession sqlSession),調用關係以下:
SqlSession.getMapper——>Configuration.getMapper——>MapperRegistry.getMapper——>MapperProxyFactory.newInstance
在MapperProxyFactory.newInstance方法中,會使用代理類MapperProxy爲該接口生成一個代理對象,在代理類中的每一個方法中都會調用MapperProxy類中的invoke()方法,在invoke()方法中會爲每一個方法生成一個MapperMethod對象,在去調用MapperMethod類中execute()方法,這個其實就是SqlSession的入口;
1 public class MapperProxyFactory<T> { 2 3 private final Class<T> mapperInterface; 4 private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); 5 6 public MapperProxyFactory(Class<T> mapperInterface) { 7 this.mapperInterface = mapperInterface; 8 } 9 10 public Class<T> getMapperInterface() { 11 return mapperInterface; 12 } 13 14 public Map<Method, MapperMethod> getMethodCache() { 15 return methodCache; 16 } 17 18 @SuppressWarnings("unchecked") 19 protected T newInstance(MapperProxy<T> mapperProxy) { 20 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 21 } 22 23 public T newInstance(SqlSession sqlSession) { 24 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); 25 return newInstance(mapperProxy); 26 } 27 28 }
1 public class MapperProxy<T> implements InvocationHandler, Serializable { 2 3 private static final long serialVersionUID = -6424540398559729838L; 4 private final SqlSession sqlSession; 5 private final Class<T> mapperInterface; 6 private final Map<Method, MapperMethod> methodCache; 7 8 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { 9 this.sqlSession = sqlSession; 10 this.mapperInterface = mapperInterface; 11 this.methodCache = methodCache; 12 } 13 14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 15 if (Object.class.equals(method.getDeclaringClass())) { 16 try { 17 return method.invoke(this, args); 18 } catch (Throwable t) { 19 throw ExceptionUtil.unwrapThrowable(t); 20 } 21 } 22 final MapperMethod mapperMethod = cachedMapperMethod(method); 23 return mapperMethod.execute(sqlSession, args); 24 } 25 26 private MapperMethod cachedMapperMethod(Method method) { 27 MapperMethod mapperMethod = methodCache.get(method); 28 if (mapperMethod == null) { 29 mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); 30 methodCache.put(method, mapperMethod); 31 } 32 return mapperMethod; 33 } 34 35 }
3)在execute方法中會根據sql類型,選擇相應的方法進行執行,其實本質是調用SqlSession接口中相應的方法,SqlSession有三個實現類,其實最後本質仍是調用的是DefaultSqlSession中的實現方法(在上節中介紹會初始化DefaultSqlSession對象),底層的調用是Executor的query()方法,調用的是實現類BaseExecutor的query()方法,在該方法中調用的是doQuery()方法;Executor有三個實現類,SimpleExecutor(默認)、ReuseExecutor、BatchExecutor;具體使用哪一個在初始化SqlSession時肯定,參考上節;在接下來的例子主要是以SimpleExecutor類舉例:
1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 2 Statement stmt = null; 3 try { 4 Configuration configuration = ms.getConfiguration(); 5 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 6 stmt = prepareStatement(handler, ms.getStatementLog()); 7 return handler.<E>query(stmt, resultHandler); 8 } finally { 9 closeStatement(stmt); 10 } 11 }
4)StatementHandler
在doquery()方法中會建立StatementHandler對象,並調用prepareStatement對sql進行預編譯和參數初始化,在prepareStatement方法中又經過調用StatementHandler接口中的prepare()建立Statement對象和調用parameterize()方法進行參數初始化和sql預編譯;
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); handler.parameterize(stmt); return stmt; }
5)ParameterHandler
在調用parameterize()方法,底層是經過ParameterHandler.setParameters(statement)方法進行參數的初始化過程;具體的實現過程能夠參考DefaultParameterHandler.setParameters(statement)方法;
6)ResultSetHandler
經過StatementHandler.query()方法會將sql執行返回的結果進行封裝,根據配置的resultSetType類型進行轉換,具體實現能夠參考DefaultResultSetHandler類中的handleResultSets方法;