1 Spring框架整合Mybatis示例
1.1 创建演示项目
1.2 项目目录结构
1.3 依赖配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kkarma</groupId>
<artifactId>spring-mybatis-app</artifactId>
<version>1.0.0</version>
<name>spring-mybatis-app</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
1.4 项目配置文件
1.4.1 数据库连接配置文件
druid.driver=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://10.10.3.95:3306/sys-library?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone\
=Asia/Shanghai
druid.userName=adsion
druid.password=MTIzNDU2
# 初始化连接数
druid.pool.init=3
# 高峰期过后,保留连接吃的个数
druid.pool.minIdle=5
# 高峰期,最大能创建连接的个数
druid.pool.MaxActive=20
# 等待的时间
durid.pool.timeout=60
1.4.2 Mybatis框架全局配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 二级缓存开启, 默认为true -->
<setting name="cacheEnabled" value="true" />
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
</configuration>
1.4.3 Spring框架配置文件
<?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:Context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 通过配置文件方式注册bean -->
<bean id="myBook01" class="com.kkarma.pojo.LibBook" >
<property name="bookId" value="6"></property>
<property name="bookIndexNo" value="xxooooxx"></property>
<property name="bookName" value="java高级程序设计"></property>
<property name="bookAuthor" value="张三"></property>
<property name="bookPublisher" value="清华大学出版社"></property>
<property name="bookCateId" value="1"></property>
<property name="bookStock" value="2"></property>
</bean>
<!--声明使用注解配置-->
<Context:annotation-config />
<!--声明Spring工厂注解的扫描范围-->
<Context:component-scan base-package="com.kkarma"/>
<!--引用外部文件-->
<Context:property-placeholder location="db.properties"/>
<!--配置DruidDataSources-->
<bean id="DruidDataSources" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.driver}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.userName}"/>
<property name="password" value="${druid.password}"/>
<property name="initialSize" value="${druid.pool.init}"/>
<property name="minIdle" value="${druid.pool.minIdle}"/>
<property name="maxActive" value="${druid.pool.MaxActive}"/>
<property name="maxWait" value="${durid.pool.timeout}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="DruidDataSources"/>
<!--配置mapper的路径-->
<property name="mapperLocations" value="classpath*:mappers/**/*Mapper.xml">
</property>
<!--配置需要定义别名的实体类的包-->
<property name="typeAliasesPackage" value="com.kkarma.pojo"/>
<!--配置需要mybatis的主配置文件-->
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
<!-- 配置MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
<property name="basePackage" value="com.kkarma.mapper"/>
</bean>
<!-- 将spring事务管理配置给spring -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DruidDataSources"/>
</bean>
<!-- 通过Spring jdbc提供的<tx>标签声明事物的管理策略,并给事务设置隔离级别以及传播机制 -->
<tx:advice id="MyAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="Insert*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Update*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Delete*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Query*" isolation="REPEATABLE_READ" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--将事务管理以Aop配置,应用于ServiceI方法(ServiceImp)-->
<aop:config>
<aop:pointcut id="MyManager" expression="execution(* com.kkarma.service.*.*(..))"/>
<aop:advisor advice-ref="MyAdvice" pointcut-ref="MyManager" />
</aop:config>
</beans>
1.4.4 **Mapper.xml文件
1.4.5 Mapper接口
package com.kkarma.mapper;
import com.kkarma.pojo.LibBook;
import java.util.List;
/**
* @Author: karma
* @Date: 2023/3/17 0017 - 03 - 17 - 10:18
* @Description: com.kkarma.mapper
* @version: 1.0
*/
public interface LibBookMapper {
List<LibBook> selectAllBook();
}
1.4.6 实体类
package com.kkarma.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* @Author: karma
* @Date: 2023/3/17 0017 - 03 - 17 - 10:09
* @Description: com.kkarma.pojo
* @version: 1.0
*/
@Data
@NoArgsConstructor
@ToString
public class LibBook implements Serializable {
private Long bookId;
private String bookIndexNo;
private String bookName;
private String bookAuthor;
private String bookDescription;
private String bookPublisher;
private Integer bookCateId;
private Integer bookStock;
}
1.5 测试
package com.kkarma;
import static org.junit.Assert.assertTrue;
import com.kkarma.mapper.LibBookMapper;
import com.kkarma.pojo.LibBook;
import com.kkarma.service.ILibBookService;
import lombok.AllArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* Unit test for simple App.
*/
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class AppTest
{
@Autowired
// private ILibBookService bookService;
private LibBookMapper bookMapper;
/**
* Rigorous Test :-)
*/
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
libBooks.forEach( System.out::println);
}
}
2 Spring框架整合Mybatis原理分析
看看, 之前如果我们单独只用Mybatis框架的时候, 测试代码是怎么写的
1) 创建一个SqlSessionFactoryBuilder对象:
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
2) 创建一个SqlSessionFactory对象:
SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
3) 创建一个SqlSession对象:
SqlSession sqlSession = factory.openSession();
4) 获取Mapper层指定接口的动态代理对象:
LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
5) 调用接口方法获取返回结果即可:
List<LibBook> list = mapper.selectAllBook();
对比一下spring框架集成Mybatis框架之后
public class AppTest
{
@Autowired
private LibBookMapper bookMapper;
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
}
}
对比一下,发现是不是很多操作就被简化了, 整个SqlSession对象的创建过程
和Mapper动态代理对象的创建过程
都没有了, 那么mybatis-spring
到底是怎么实现这个操作的简化的呢, 下面我们一起来分析一下这个过程。
2.1 SqlSessionFactoryBean
首先我们看看Spring的配置文件applicationContext.xml, 其中定义了sqlSessionFactory的bean声明,
创建的对象是SqlSessionFactoryBean
对象,前面已经详细说过Mybatis框架的核心内容了,我们应该都知道在Mybatis中我们都是通过SqlSessionFactoryBuilder
创建SqlSessionFactory
对象,然后再通过SqlSessionFactory
对象创建SqlSession
对象,从而获取数据库连接等等操作。
见名知意,SqlSessionFactoryBean
对象很定也是跟创建SqlSessionFactory
紧密相关的。到源码里面去看看
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
/** 在创建SqlSessionFactoryBean的时候,已经创建了sqlSessionFactoryBuilder对象 */
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;
@SuppressWarnings("rawtypes")
private Class<? extends TypeHandler> defaultEnumTypeHandler;
private Class<?>[] typeAliases;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private LanguageDriver[] scriptingLanguageDrivers;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> vfs;
private Cache cache;
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
this.objectWrapperFactory = objectWrapperFactory;
}
public DatabaseIdProvider getDatabaseIdProvider() {
return databaseIdProvider;
}
public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
this.databaseIdProvider = databaseIdProvider;
}
public Class<? extends VFS> getVfs() {
return this.vfs;
}
public void setVfs(Class<? extends VFS> vfs) {
this.vfs = vfs;
}
public Cache getCache() {
return this.cache;
}
public void setCache(Cache cache) {
this.cache = cache;
}
public void setPlugins(Interceptor... plugins) {
this.plugins = plugins;
}
public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
}
public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
this.typeAliasesSuperType = typeAliasesSuperType;
}
public void setTypeHandlersPackage(String typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}
public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
this.typeHandlers = typeHandlers;
}
public void setDefaultEnumTypeHandler(
@SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {
this.defaultEnumTypeHandler = defaultEnumTypeHandler;
}
public void setTypeAliases(Class<?>... typeAliases) {
this.typeAliases = typeAliases;
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
public void setMapperLocations(Resource... mapperLocations) {
this.mapperLocations = mapperLocations;
}
public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
this.configurationProperties = sqlSessionFactoryProperties;
}
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}
public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
}
public void setTransactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
}
public void setEnvironment(String environment) {
this.environment = environment;
}
public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
this.scriptingLanguageDrivers = scriptingLanguageDrivers;
}
public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
}
@Override
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();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new IOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
Set<Class<?>> classes = new HashSet<>();
String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packagePattern : packagePatternArray) {
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
classes.add(clazz);
}
} catch (Throwable e) {
LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
}
}
}
return classes;
}
}
还是带着以下问题来看源码:
- 什么时候完成的
SqlSessionFactoryBuilder
对象的创建? - 什么时候完成的
SqlSessionFactory
对象的创建? - Mapper层的动态代理对象是何时创建并注入到Spring容器进行管理的?
2.2 什么时候完成的SqlSessionFactoryBuilder
对象的创建?
通过SqlSessionFactoryBean
类源码我们可以看到,SqlSessionFactoryBuilder
作为SqlSessionFactoryBean
类的成员属性在对象创建的时候会进行创建。
2.3 什么时候完成的SqlSessionFactory
对象的创建并注入Spring容器的?
SqlSessionFactoryBean
类实现InitializingBean
接口,实现了afterPropertiesSet
方法,那么这个类肯定在该方法中实现了某些初始化的逻辑代码, 一块儿看看这个方法
@Override
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");
// 对SqlSessionFactory对象进行赋值,治理是通过buildSqlSessionFactory实现了SqlSessionFactory对象的创建
this.sqlSessionFactory = buildSqlSessionFactory();
}
再来看看那这个buildSqlSessionFactory
方法
这个方法其实就干了下面两件事
- 完成Mybatis全局配置文件的解析, 得到Configuration对象
- 调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new IOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 当前方法之前的代码其实就是在完成Mybatis全局配置文件的解析, 得到Configuration对象
// 然后调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
2.4 SqlSessionFactory
对象是怎么注入Spring容器的?
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
}
我们可以看到SqlSessionFactoryBean
实现了FactoryBean
接口,
我们都知道有很多种方法可以将Bean对象注入到Spring容器中, 实现FactoryBean
接口重写getObject方法就是其中一种, 这里就是利用该方法完成的
SqlSessionFactory对象的注入。 看看
SqlSessionFactoryBean中
getObject`方法的实现
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
afterPropertiesSet()方法中完成了SqlSessionFactory对象的创建。
这样就完成了SqlSessionFactory注入Spring容器。
2.5 什么时候完成的Mapper
接口对象的创建并注入Spring容器的?
2.5.1 spring配置文件中关于MapperScannerConfigurer的配置
还是从配置文件applicationContext.xml
文件为入口,在applicationContext.xml
文件中我们配置了一个Bean为MapperScannerConfigurer
这里我们配置了mapper层的基础扫描路径basePackage
2.5.2 MapperScannerConfigurer类
看看MapperScannerConfigurer
类的注释信息, 我给大家翻译了一下, 勉强能说明是什么意思凑合看一下
/**
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
* registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
* concrete classes will be ignored.
* BeanDefinitionRegistryPostProcessor,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean。
* 请注意,将仅注册至少具有一个方法的接口;具体实现类将被忽略。
* <p>
* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
* {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
* details.
* 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的,
* 但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.
* <p>
* The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
* <p>
* basePackage这个属性可包含多个基础包路径名称,使用逗号或分号分隔
* This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
* {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
* specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
* match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
* {@code basePackage} are added as mappers.
* 此类支持筛选通过指定标记接口或注释创建的映射器。属性annotationClass指定要搜索的注释。markerInterface属性指定要搜索的父接口。
* 如果同时指定了这两个属性,则会为与任一条件匹配的接口添加映射器。
* 默认情况下,这两个属性为 null,因此给定 basePackage中的所有接口都添加为Mapper映射器。
* <p>
* This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
* proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
* in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
* {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
* are used rather than actual objects because Spring does not initialize property placeholders until after this class
* is processed.
* 此配置器为其创建的所有 bean 启用自动装配,以便它们自动装配到正确的SqlSessionFactory或SqlSessionTemplate。
* 但是,如果应用程序中有多个SqlSessionFactory,则无法使用自动装配。
* 在这种情况下,您必须显式指定SqlSessionFactory或SqlSessionTemplate以通过 Bean Name属性使用。
* 使用 Bean Name而不是实际对象,因为 Spring 在处理此类之前不会初始化属性占位符。
* <p>
* Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
* actual object creation until later in the startup process, after all placeholder substitution is completed. However,
* note that this configurer does support property placeholders of its <em>own</em> properties. The
* <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
* 传入可能需要占位符的实际对象(即数据库用户密码)会失败。
* 使用 Bean Name会将实际的对象创建推迟到启动过程的稍后阶段,在所有占位符替换完成后。
* 但是,请注意,此配置器确实支持其自己的属性的属性占位符。basePackage 和 Bean name 属性都支持 ${property} 样式替换。
* <p>
* Configuration sample:
* 配置示例
* 之前在applicationContext.xml我为什么之后可以这样配置, 就是通过这里的注释说明
* <pre class="code">
* {@code
* <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
* <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
* <!-- optional unless there are multiple session factories defined -->
* <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
* </bean>
* }
* </pre>
*
* @author Hunter Presnall
* @author Eduardo Macarron
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
*/
注意
:
直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的.
但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.
我这里使用的是mybatis-spring
的2.1.0版本
, 请注意版本问题。
看一下UML类图
在MapperScannerConfigurer
类的注释信息明确告诉我们了,这个类实现了BeanDefinitionRegistryPostProcessor
接口,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
接口,那么他必然实现了postProcessBeanDefinitionRegistry
。
2.5.3 MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
方法**
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// ClassPathBeanDefinitionScanner类的子类,就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中用的。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 下面一堆set方法就是设置ClassPathMapperScanner或从父类ClassPathBeanDefinitionScanner继承的属性值的, 这没啥好说的
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
// 核心代码就是这一行,扫描我们配置的包路径下的所有Mapper接口并注册成MapperFactoryBean对象
// 这里我们配置的basePackage可能有多个,类似这样basePackage = "com.xxxx.mapper,com.xxxx.dao"
// StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)的作用就是将
// basePackage = "com.xxxx.mapper,com.xxxx.dao"分割成 String[]{"com.xxxx.mapper", "com.xxxx.dao"}数组
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
2.5.4 ClassPathMapperScanner类
ClassPathMapperScanner类
是ClassPathBeanDefinitionScanner
类的子类, 主要用途就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中。
源码中对于该类的注释信息如下:
一个 ClassPathBeanDefinitionScanner,它通过 basePackage、annotationClass 或
markerInterface 注册映射器。如果指定了 annotationClass 和/或
markerInterface,则仅搜索指定的类型(将禁用搜索所有接口)。 此功能以前是 MapperScannerConfigurer
的私有类,但在 1.2.0 版中被分解。
2.5.4.1 scan()方法
看看scanner.scan()
方法
实际调用的是ClassPathBeanDefinitionScanner
类的scan方法
2.5.4.2 doScan()方法
doScan()方法, 因为ClassPathMapperScanner
类对该方法进行了重写, 这里调用的是ClassPathMapperScanner
类的doScan()
方法, 又在doScan()
方法调用了父类的doScan()
方法
/**
* Calls the parent search that will search and register all the candidates. Then the registered objects are post
* processed to set them as MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
父类ClassPathBeanDefinitionScanner
类的doScan()方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
// 扫描basePackage路径下的java类文件,先全部转为Resource,然后再判断拿出符合条件的bd
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
Iterator var8 = candidates.iterator();
while(var8.hasNext()) {
BeanDefinition candidate = (BeanDefinition)var8.next();
// 解析scope属性
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 获取beanName
// 先判断注解上有没有显示设置beanName
// 没有的话,就以类名小写为beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 如果这个类是AbstractBeanDefinition类型
// 则为他设置默认值,比如lazy/init/destroy
// 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
if (candidate instanceof AbstractBeanDefinition) {
this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
}
// 如果这个类是AnnotatedBeanDefinition类型
// 处理加了注解的类
// 把常用注解设置到AnnotationBeanDefinition中
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
if (this.checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
通过父类doScan方法的调用获取到了所有的Mapper的接口的BeanDefinition信息。
2.5.4.3 processBeanDefinitions()方法
下面继续执行 processBeanDefinitions(beanDefinitions)
, 接着看看这个方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 设置BeanDefinition的构造函数参数值为当前BeanDefinition的类名,也就是***Mapper接口的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
// for spring-native
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
// 设置BeanDefinition的beanClass为MapperFactoryBean.class
// 这里一修改, 就相当于在创建Bean对象的时候都是通过MapperFactoryBean来创建的
definition.setBeanClass(this.mapperFactoryBeanClass);
// 添加属性addToConfig为true, 因为ClassPathMapperScanner类的addToConfig属性默认为true
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
// 设置属性按类型自动注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
有上述方法我们可以知道,Mapper层接口的Bean对象都是通过MapperFactoryBean
来创建的, mapper层的动态代理对象肯定也是借助MapperFactoryBean
去完成了.
2.6 MapperFactoryBean
是如何完成代理对象创建并注入Spring容器的?
首先看下 MapperFactoryBean
的类图
发现这个类即实现了FactoryBean
接口, 也实现了InitializingBean
接口, 那就一起来看看分别在
getObject()
和afterPropertiesSet()
中都做了什么逻辑处理
2.6.1 MapperFactoryBean
与SqlSessionFactoryBean是如何关联的
2.6.1.1 MapperFactoryBean
的afterPropertiesSet
方法
MapperFactoryBean
类没有该方法, 肯定是在它的父类中实现的,在DaoSupport
类中可以找到该方法
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()
方法在SqlSessionDaoSupport
类中紧紧只是进行了sqlSessionTemplate是否注入完成的判断, 在SqlSessionDaoSupport
类的子类MapperFactoryBean
中又进行了重写
MapperFactoryBean
类中的checkDaoConfig方法
@Override
protected void checkDaoConfig() {
// 调用了父类的该方法检测sqlSessionTemplate是否已经注入
super.checkDaoConfig();
// 检查mapperInterface属性是否已经填充
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 这里就是调用父类的sqlSessionTemplate对象的getConfiguration方法获取Configuration对象
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 这里不就是Mybatis框架里面的addMapper方法么, 到这是不是就明白了。
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();
}
}
}
在MapperFactoryBean注入到容器之后,MapperFactoryBean
类中继承自父类的的afterPropertiesSet
方法被自动执行, 当前MapperFactoryBean对象映射的接口就被注册到Mybatis框架的mapper注册器中了。
2.6.1 MapperFactoryBean
的getObject()
方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
在Spring框架扫描所有实现FactoryBean
接口的类进行Bean对象的注册时, 这个getObject()方法会被执行,会调用Mybatis框架的通过Mybatis框架的动态代理模式创建mapper接口的代理对象注入到Spring容器中进行托管。
到此,Mybatis框架中的核心对象都注册到Spring容器中, 使用的时候是需要注入到调用的类中, 就可以使用对应的方法实现对数据库的增删改查了。
3 关于InitializingBean接口的拓展知识
Spring框架为bean提供了两种初始化bean的方式,实现InitializingBean
接口或者通过在XML配置文件中添加init-method
的方式,这两种方式可以同时使用。
实现InitializingBean
接口是直接调用afterPropertiesSet
方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
如果调用afterPropertiesSet
方法时出错,则不调用init-method指定的方法。
InitializingBean
是Spring框架提供的拓展接口,InitializingBean
接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet
方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
我们可以利用这个来完成很多功能, 比如在中小型系统业务开发中实现数据库的读写分离等业务操作。