mybatis源碼解析
從配置文件(spring-mybatis.xml)讀起
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close" >
<property name="driverClassName">
<value>${jdbc_driverClassName}</value>
</property>
<property name="url">
<value>${jdbc_url}</value>
</property>
<property name="username">
<value>${jdbc_username}</value>
</property>
<property name="password">
<value>${jdbc_password}</value>
</property>
<!-- 連接池最大使用連接數 -->
<property name="maxActive">
<value>${max_active}</value>
</property>
<!-- 初始化連接大小 -->
<property name="initialSize">
<value>${initial_size}</value>
</property>
<!-- 獲取連接最大等待時間 -->
<property name="maxWait">
<value>${max_wait}</value>
</property>
<!-- 連接池最大空閒
<property name="maxIdle">
<value>20</value>
</property>-->
<!-- 連接池最小空閒 -->
<property name="minIdle">
<value>${min_idle}</value>
</property>
<!-- 自動清除無用連接 -->
<property name="removeAbandoned">
<value>${remove_abandoned}</value>
</property>
<!-- 清除無用連接的等待時間 -->
<property name="removeAbandonedTimeout">
<value>${remove_abandoned_timeout}</value>
</property>
<!-- 連接屬性 -->
<property name="connectionProperties">
<value>${connection_properties}</value>
</property>
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis">
<value>${time_between_eviction_runs_millis}</value>
</property>
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis">
<value>${min_evictable_idle_time_millis}</value>
</property>
<!-- 申請連接的時候檢測 -->
<property name="testWhileIdle">
<value>${test_while_idle}</value>
</property>
<!-- 用來檢測連接是否有效的sql,要求是一個查詢語句-->
<property name="validationQuery">
<value>${validation_query}</value>
</property>
<property name="filters">
<value>stat,wall</value>
</property>
</bean>
<!-- mybatis文件配置,掃描所有mapper文件 -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:mybatis-config.xml"
p:mapperLocations="classpath*:com/**/mapper/*.xml"/>
<!-- configLocation爲mybatis屬性 mapperLocations爲所有mapper-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<!-- <constructor-arg index="1" value="BATCH" /> 如果想要進行批量操作可加入這個屬性 -->
</bean>
<!-- spring與mybatis整合配置,掃描所有dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
p:basePackage="*.dao"
p:sqlSessionFactoryBeanName="sqlSessionFactory"/>
<!-- 對數據源進行事務管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
SqlSessionFactoryBean
SqlSessionFactoryBean是一個實現了FactoryBean接口的工廠bean,在獲取bean實例時會調用getObject方法,同時實現了InitializingBean,在spring容器實例化該工廠bean後會調用afterPropertiesSet方法
- afterPropertiesSet()
//spring容器實例化SqlSessionFactoryBean後調用afterPropertiesSet創建sqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
- buildSqlSessionFactory
首先解析配置文件中的configLocation="classpath:mybatis-config.xml
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
然後解析配置文件中的mapperLocations="classpath*:com/**/mapper/*.xml
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
xmlMapperBuilder.parse()中的configurationElement方法追蹤下去,最後到MapperBuilderAssistant的
addMappedStatement,它會將mapper.xml中每個sql語句維護到Configuration的Map<String, MappedStatement> mappedStatements中,其中key是mapper.xml中sql語句的id也是Dao中的方法名
this.configuration.addMappedStatement(statement);
xmlMapperBuilder.parse()中的bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException var4) {
}
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);
}
}
}
configuration.addMapper()
public <T> void addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
Configuration的MapperRegistry維護了一個Dao和其代理對象工廠的map:knownMappers
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
MapperScannerConfigurer
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,在spring容器註冊BeanDefinition的時候會自動調用postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
最後調用ClassMapperScanner的doScan方法將basePackages下的Dao接口封裝成beanDefinations
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
上一步的processBeanDefinitions方法中將beanDefination的BeanClass(原始值是Dao)設置成MapperFactoryBean,最後spring將beanDefination實例化成MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
MapperFactoryBean集成DaoSupport實現了InitializingBean,在spring實例化MapperFactoryBean的時後會調用afterPropertiesSet()方法,該方法調用checkDaoConfig方法
public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
public DaoSupport() {
}
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
MapperProxyFactory的checkDaoConfig方法中生成最終的Dao代理對象工廠MapperProxyFactory(Dao)註冊到DefaultSqlSession.Configuration.MapperRegistry.knownMappers中
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
SqlSessionTemplate
當調用Dao的方法查詢數據庫時,實際是MapperProxy代理對象執行
MapperProxy是JDK的自動代理,實現了InvocationHandler接口,實現了invoke方法,當被代理的目標類是接口時執行mapperMehtod.execute
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 var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
excucte方法中調用sqlSession的方法,而SqlSessionTemplate正是sqlSession的實現類
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else if (SqlCommandType.SELECT == this.command.getType()) {
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
以DefaultSqlSession的selectOne爲例:
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
this.configuration.getMapperStatement(statement)就是從第一步初始化SqlSesissonFactoryBean時的Map(Configuration的Map<String, MappedStatement> mappedStatements,其中key是mapper.xml中sql語句的id也是Dao中的方法名)中取出MappedStatement對象,然後執行query方法
Mybatsi源碼走讀到這裏就沒往下看了,這裏的var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
this.executor是CacheExecutor對象,另起一篇走讀MyBatis的二級緩存原理