通過實例結合源碼的方式解讀,其中涉及到的文件來自於筆者的Github畢設項目,引用的jar包為
mybatis-spring-1.3.0.jar
Mybatis
Mybatis是基於ORM(Object relation mapping)思想而開發的框架插件,本質原理用一句筆者的話便是使用了JAVA連接數據庫的方式來執行相應的SQL(PreparedStatement),並在此基礎上提供了豐富的動態SQL配置以及緩存等概念。(后續文章會提及)
其基本上是企業與傳統數據庫(Oracle/Mysql等)交互常用的數據庫持久層框架,以JAVA語言來說,其與Spring的搭配使用也是廣為流傳。
本文將從源碼的角度淺析Mybatis與Spring的親密搭配
范例
筆者本文只關注SqlSessionFactoryBean的配置,樣例如下
<!--數據源,引用common-pool包的數據源類-->
<bean id="dataSource-mysql" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="1" />
<property name="maxActive" value="20" />
</bean>
<!--sqlSessionFactoryBean創建-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource-mysql" />
<property name="typeAliasesPackage" value="com.du.wx.model" />
<property name="mapperLocations" value="classpath:com/du/wx/resources/mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- joggle代表接口 -->
<property name="basePackage" value="com.du.wx.mapper.joggle" />
</bean>
由上述的配置可知,在配置SqlSessionFactory數據庫會話工廠Bean對象時必須依賴相應的數據源屬性dataSource
SqlSessionFactoryBean
筆者分層次來解析此類
基礎內部屬性
優先觀察SqlSessionFactoryBean的內部屬性,代碼片段如下
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
//EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();
private boolean failFast;
private Interceptor[] plugins;
private TypeHandler<?>[] typeHandlers;
private String typeHandlersPackage;
private Class<?>[] typeAliases;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
//issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> vfs;
private Cache cache;
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
常用的屬性如下:
configLocation mybatis主配置文件路徑,支持classpath語法
mapperLocations 指定mybatis的mapper配置文件,支持classpath語法
dataSource 數據源
typeAliasesPackage 指定model層類名的別名掃描包,這與mapper配置中的parameterType和resultType搭配使用
InitializingBean復寫方法
緊接着看下其復寫的afterPropertiesSet()方法,代碼如下
@Override
public void afterPropertiesSet() throws Exception {
//datasource not allowed to be null.
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();
}
與前文提及的對應,必須確保dataSource屬性已經得到配置。
實例化步驟
最后查看關鍵實例方法buildSqlSessionFactory()方法,由於片段過長,筆者截取幾段來進行講解
Mybatis主文件加載
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
//如果spring配置中configLocation屬性不為空,則加載指定的Mybatis配置
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
//否則則采用默認的Mybatis配置,@see Configuration
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
通過上述配置表明,內部屬性configLocation是非必須配置項
model別名映射掃描
if (hasLength(this.typeAliasesPackage)) {
//截取typeAliasesPackage屬性的包,支持,或;
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
//對應方法的目的是通過掃描包得到其包以及子包目錄下的所有Class,然后為每個class注冊別名
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
即對內部屬性typeAliasesPackage支持,;作為分隔符以加載多個目錄,主要是為了方便編程人員使用parameterType/resultType等屬性
mapper配置文件加載
if (!isEmpty(this.mapperLocations)) {
//mapperLocations
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
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");
}
}
關鍵處理XML配置的mapper文件,用到了mybatis包中的org.apache.ibatis.builder.xml.XMLMapperBuilder類進行解析。這個后續分析
Configuration-所有關於Mybatis的信息均存儲在Mybatis包中的org.apache.ibatis.session.Configuration類上
return this.sqlSessionFactoryBuilder.build(configuration);
Spring呢,就通過SqlSessionFactoryBuilder類來創建相應的SqlSessionFactory對象。具體的由於篇幅過長,就放在后文分析。
總結
從上文便可以看出來Spring框架是如何整合Mybatis的,就是這個Mybatis的關鍵類Configuration。而相應的開關數據庫等一系列的功能都是通過Spring的SqlSessionFactory對象來操控的。這也是兩者結合最核心的地方
