概要
過度
我們上面介紹了 MyBatis 的使用方法和 Spring MyBatis 的使用方法。本文著重介紹 Spring MyBatis 的封裝橘忱。當然,這里只是著重介紹針對 MyBatis API 的封裝,不介紹包的掃描邏輯。
我們要介紹的主要有兩個地方徙瓶,在xml中的位置分別如下:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:MyBatis-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
其實就是兩個類毛雇。
內(nèi)容簡介
本文主要介紹SqlSessionFactoryBean
、MapperFactoryBean
兩個類的內(nèi)部邏輯侦镇。
所屬環(huán)節(jié)
對 spring-mybatis 的實現(xiàn)詳情的介紹灵疮。
上下環(huán)節(jié)
上文: spring-mybatis 的引入
下文: 對 spring-mybatis 中涉及的高級功能——包掃描及BD注冊的探索
SqlSessionFactoryBean
源碼
入口
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:MyBatis-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
在Spring中注冊了這個BD,沒有額外做其他操作壳繁,我們看一下SqlSessionFactoryBean
的繼承關(guān)系:
我們發(fā)現(xiàn)它實現(xiàn)了三個比較有意思的接口:
-
InitializingBean
:此函數(shù)有初始化鉤子震捣,應(yīng)該是用于配置加載和整合 -
FactoryBean
:他是FactoryBean
,猜測生成目標類型的實例可能存在一些邏輯 -
ApplicationListener
:他監(jiān)聽著ApplicationContext
的事件闹炉,猜測可能根據(jù)事件做出一些變化蒿赢,如更新配置、控制一些生命周期之類的吧
InitializingBean
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();
}
這里僅僅做了一些非空判斷渣触,然后將邏輯繼續(xù)委托羡棵,和Spring的代碼邏輯很像。注意記住這里是configuration
和configLocation
不能同時配置昵观。
我們繼續(xù)看核心邏輯的方法:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
// 如果配置的 Configuration 不為空晾腔,就把我們在 Spring 的xml中配置的屬性填進去
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) {
// 否則如果你配置了 MyBatis 的配置文件地址,就從那里找值啊犬、加載
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
// 如果都沒有配置,那就自己new一個配置對象壁查,然后把你在 Spring 的xml中配置的屬性填進去
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
// 到此為止我們設(shè)置了一些基本屬性觉至,如果配置了MyBatis的配置文件,就從文件中加載了所有的配置
// 但是Spring允許從它的xml中重復(fù)配置睡腿,而且這里的配置優(yōu)先級會更高
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
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 (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
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();
}
}
// 設(shè)置事務(wù)管理
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 這里配置了mapper的xml的具體位置
if (!isEmpty(this.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");
}
}
// 配置完成语御,這里構(gòu)建 SqlSessionFactory 并返回
return this.sqlSessionFactoryBuilder.build(configuration);
}
FactoryBean
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
基本沒有邏輯。
ApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
如果是ApplicationContext
的刷新操作席怪,就刷一遍Statement
应闯。(這里我也不太懂是做些啥)
總結(jié)
這里完成了SqlSessionFactory
的生成。
MapperFactoryBean
源碼
入口
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
我們指定的類型是MapperFactoryBean
挂捻,看樣子是個FactoryBean
碉纺,要根據(jù)內(nèi)部邏輯來判斷它返回BD 的類型。
我們看一下這個類的繼承關(guān)系:
個人感覺InitializingBean
可能涉及一些初始化的問題
FactoryBean
中涉及一些對BD的處理刻撒,還有對Mapper實例的構(gòu)建
InitializingBean
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
@Override
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
protected void initDao() throws Exception {
}
我們發(fā)現(xiàn)初始化沒搞什么特別的東西吧骨田,校驗什么的也不是是因為在configuration
中維護了Class
和Mapper
實例的映射。具體為什么要做這個声怔,如何維護的映射關(guān)系态贤,這里涉及MyBatis內(nèi)部邏輯了,先不盲目深入醋火。
想想也對悠汽,所有的在SqlSessionFactoryBean
都做好了箱吕,這里就等生成Mapper實例了。
FactoryBean
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
總結(jié)
到這里柿冲,完成了對 Mapper 實例的生成茬高。
總結(jié)
整體來說思路比較清晰。沒什么說的姻采。