1.配置applicationContext.xml
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://122.152.131.141:3306/school" />
<property name="username" value="zw121" />
<property name="password" value="122334" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
<property name="mapperLocations">
<array>
<!-- 配置單個映射文件 -->
<!-- <value>classpath:mappers/UserMapper.xml</value> -->
<!-- 配置多個映射文件使用 * 通配符 -->
<value>classpath:mappers/*Mapper.xml</value>
</array>
</property>
<!-- 配置別名,使用包掃描 -->
<property name="typeAliasesPackage" value="com.qhf.pojo"></property>
</bean>
<!-- 掃描接口文件,其2選1 -->
<!-- 1.掃描接口文件,只能單個文件 -->
<!--
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.qhf.dao.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
-->
<!-- 2.掃描包下的接口文件,此方法簡便 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qhf.dao"></property>
</bean>
- 主要就是配置 SqlSessionFactoryBean 的初始化 bean 對象
- 還有 MapperFactoryBean 的初始化 bean 對象,此 bean 針對一個 mapper 接口
- MapperScannerConfigurer 的初始化 bean 對象,掃描包下的全部接口文件
2.SqlSessionFactoryBean
- 實現接口 FactoryBean 接口,getBean() 方法返回 sqlSessionFactory 工廠類對象。
- 實現 InitializingBean 接口,則 bean 初始化時會調用此類的 afterPropertiesSet() 方法。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
//...
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
- 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();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
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;
}
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");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
- 總體來說:
- 生成一個 Configuration 對象,此對象是 mybatis 的核心。
- 把配置的 configLocation(mybatis原始配置文件,整合之後基本不用配置),objectFactory,objectWrapperFactory,typeAliasesPackage(別名,省了configLocation 裏別名的配置),typeAliases,plugins,typeHandlersPackage,typeHandlers,transactionFactory,databaseIdProvider,mapperLocations(掃描 xml 映射文件) 初始化和解析。
- 最後通過 sqlSessionFactoryBuilder.build(configuration) 方法把此 configuration 對象傳給了 DefaultSqlSessionFactory 實例對象,其中 DefaultSqlSessionFactory 實現了 SqlSessionFactory 接口,是生產 SqlSession 的工廠。
public class SqlSessionFactoryBuilder {
//...
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//...
}
- 當單獨使用 mybatis 時通過 sqlSessionFactory.openSession() 來獲取 sqlSession,才能操作數據庫。
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//...
- 當交給 Spring 管理,可以通過下面方式獲取 userMapper 對象
UserMapper userMapper = (UserMapper)context.getBean("userMapper");
或
@Autowired
UserMapper userMapper;
- 先看此配置,是對單個 mapper 接口的 bean 對象的創建。
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.qhf.dao.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
3.MapperFactoryBean
- 配置 MapperFactoryBean 的 bean 時的屬性是 mapperInterface,sqlSessionFactory
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.qhf.dao.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
- <property name="sqlSessionFactory" ref="sqlSessionFactory"/>屬性初始化時會調用 MapperFactoryBean 從父類 SqlSessionDaoSupport 中繼承下來的 setSqlSessionFactory() 方法,並使用 SqlSessionTemplate 對 sqlSessionFactory 進行加工生產 sqlSession(通過動態代理)
-
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
- MapperFactoryBean 實現了 InitializingBean 接口,則 bean 初始化時會調用此類的 afterPropertiesSet() 方法,此方法在其父類中。
public abstract class DaoSupport implements 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);
}
}
}
- checkDaoConfig() 方法在 DaoSupport 的子類 MapperFactoryBean 中實現。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
protected void checkDaoConfig() {
super.checkDaoConfig(); // notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
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();
}
}
}
}
- getSession() 方法返回 this.sqlSession,getConfiguration() 返回 Configuration 對象
- 再檢查 Configuration 對象中是否已配置了 <property name="mapperInterface" value="com.qhf.dao.UserMapper"/> 此mapper 接口(因爲手動配置,所以還是要檢查。重點是 mybatis 中讀取映射文件時解析到<mapper namespace="xxx.UserMapper">時會自動進行類型的註冊)
- 如果沒有的話便會通過 configuration.addMapper(this.mapperInterface) 方法進行 mapper 接口的註冊
- 回到1中最後的問題:UserMapper userMapper = (UserMapper)context.getBean("userMapper"); 爲什麼能自動返回了 userMapper 對象?MapperFactoryBean 實現 FactoryBean 接口,重寫了其接口方法來獲取 mapper 對象。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
4.MapperScannerConfigurer
- 同樣是繼承 InitializingBean,但是此方法沒作用;重點在父接口 BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor實現了BeanFactoryPostProcessor接口,是Spring框架的BeanDefinitionRegistry的後處理器,用來註冊額外的BeanDefinition。postProcessBeanDefinitionRegistry方法會在所有的BeanDefinition已經被加載了,但所有Bean還沒有被創建前調用。BeanDefinitionRegistryPostProcessor經常被用來註冊BeanFactoryPostProcessor的BeanDefinition。MapperScannerConfigurer 重寫了其 postProcessBeanDefinitionRegistry() 方法。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
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, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
- 後續加上