1 Ejemplo de Mybatis de integración de Spring Framework
1.1 Crear un proyecto de demostración
1.2 Estructura del directorio del proyecto
1.3 Archivo pom.xml de configuración de dependencia
<?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 Archivo de configuración del proyecto
1.4.1 Archivo de configuración de conexión a la base de datos
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 Archivo de configuración global del marco 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 Archivo de configuración del marco 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 **Archivo Mapper.xml
1.4.5 Interfaz del asignador
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 Clase de entidad
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 Pruebas
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 Análisis del principio de integración de Mybatis con Spring framework
Veamos cómo se escribió el código de prueba si antes solo usábamos el marco 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();
Compare el marco de primavera integrado con el marco Mybatis
public class AppTest
{
@Autowired
private LibBookMapper bookMapper;
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
}
}
Compare y descubra si se han simplificado muchas operaciones. Las SqlSession对象的创建过程
y Mapper动态代理对象的创建过程
han desaparecido, luego mybatis-spring
¿Cómo lograr la simplificación de esta operación?Analicemos este proceso juntos.
2.1 SqlSessionFactoryBean
Primero, echemos un vistazo al archivo de configuración de Spring applicationContext.xml, que define la declaración del bean de sqlSessionFactory.
El objeto creado es SqlSessionFactoryBean
objeto , Como se muestra arriba El contenido central del marco Mybatis se ha discutido en detalle. Todos deberíamos saber que en Mybatis todos creamos objetos a través de SqlSessionFactoryBuilder
, y luego pasar Creación de objetosObjeto para obtener conexión a la base de datos y otras operaciones. SqlSessionFactory
SqlSessionFactory
SqlSession
Al conocer el significado después de ver el nombre, SqlSessionFactoryBean
la determinación del objeto también está estrechamente relacionada con la creaciónSqlSessionFactory
. Mira el código fuente
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;
}
}
Veamos el código fuente con las siguientes preguntas:
- ¿Cuándo se completó la creación del objeto
SqlSessionFactoryBuilder
? - ¿Cuándo se completó la creación del objeto
SqlSessionFactory
? - ¿Cuándo se crea e inyecta el objeto proxy dinámico de la capa Mapper en el contenedor Spring para su administración?
2.2 ¿Cuándo se completó la creación del objetoSqlSessionFactoryBuilder
?
A través del código fuente de la clase SqlSessionFactoryBean
, podemos ver que SqlSessionFactoryBuilder
se utiliza como atributo miembro de SqlSessionFactoryBean
. clase cuando se crea el objeto.
2.3 ¿Cuándo se completó la creación del y se inyectó en el contenedor Spring? SqlSessionFactory
SqlSessionFactoryBean
La clase implementa laInitializingBean
interfaz e implementa el métodoafterPropertiesSet
, entonces esta clase debe haber implementado algún código lógico de inicialización en el método, juntos Verificar este método
@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();
}
Echemos un vistazo a estebuildSqlSessionFactory
método
Este método en realidad hace las dos cosas siguientes:
- Complete el análisis del archivo de configuración global de Mybatis y obtenga el objeto de Configuración
- Llame a sqlSessionFactoryBuilder.build (targetConfiguration) para crear un objeto 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
¿Cómo se inyectan los objetos en el contenedor Spring?
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
}
Podemos ver queSqlSessionFactoryBean
implementa la interfaz FactoryBean
,
Todos sabemos que hay muchas formas de convertir Beans El objeto se inyecta en el contenedor Spring, 实现FactoryBean
La interfaz anula el método getObject就是其中一种, 这里就是利用该方法完成的
SqlSessionFactory对象的注入。 看看
SqlSessionFactoryBean< a i=7> Implementación del método getObject`中
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
La creación del objeto SqlSessionFactory se completa en el método Esto completa la inyección de SqlSessionFactory en el contenedor Spring.
2.5 ¿Cuándo se completó y se inyectó en el contenedor Spring la creación del objeto de interfaz Mapper
2.5.1 Configuración de MapperScannerConfigurer en el archivo de configuración de Spring
La entrada aún es del archivo de configuraciónapplicationContext.xml
. En el archivo applicationContext.xml
configuramos un Bean como MapperScannerConfigurer
aquí Configuramos el paquete base de la ruta de escaneo básica de la capa del mapeador
2.5.2 MapperScannerConfigurer类
Echa un vistazo La información de anotación de la clase MapperScannerConfigurer
, la traduje para ti, apenas puedo explicar lo que significa, solo échale un vistazo
/**
* 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)来完成这个功能.
Estoy usando . Preste atención al problema de la versión. mybatis-spring
2.1.0版本
Mire el diagrama de clases UML
La información de anotación en la clase MapperScannerConfigurer
nos dice claramente que esta clase implementa BeanDefinitionRegistryPostProcessor
Las interfaces se recuperarán recursivamente del paquete base definido por la capa Mapper y registrado comoMapperFactoryBean
MapperScannerConfigurer
Si implementa la interfaz BeanDefinitionRegistryPostProcessor
, entonces debe haber implementado postProcessBeanDefinitionRegistry
.
2.5.3 MapperScannerConfigurer
ObjetivopostProcessBeanDefinitionRegistry
Método**
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.4ClassPathMapperScanner类
ClassPathMapperScanner类
es una subclase de la clase ClassPathBeanDefinitionScanner
. Su objetivo principal es registrar beans en el contenedor Spring a través de basePackage, annotationClass o MarkerInterface.
La información de anotación para esta clase en el código fuente es la siguiente:
Un ClassPathBeanDefinitionScanner que registra un asignador a través de basePackage, annotationClass o
MarkerInterface. Si se especifican annotationClass y/o MarkerInterface, solo se buscará el tipo especificado (la búsqueda en todas las interfaces estará deshabilitada). Esta funcionalidad era anteriormente una clase privada de MapperScannerConfigurer pero se incluyó en la versión 1.2.0.
2.5.4.1 método de escaneo ()
Verscanner.scan()
Método
Lo que realmente se llama es el método de escaneo de la clase ClassPathBeanDefinitionScanner
2.5.4.2 Método doScan()
Método doScan(), debido a que la clase ClassPathMapperScanner
anula este método, lo que se llama aquí es el ClassPathMapperScanner
=3> método, y el método de la clase principal se llama en el método 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;
}
clase principal método doScan() de ClassPathBeanDefinitionScanner
clase
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;
}
La información BeanDefinition de todas las interfaces Mapper se obtiene llamando al método doScan de la clase principal.
2.5.4.3 método ProcessBeanDefinitions()
Continúe la ejecución a continuación processBeanDefinitions(beanDefinitions)
, luego observe este método
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());
}
}
}
Con el método anterior, podemos saber que todos los objetos Bean de la interfaz de la capa Mapper se crean a través de MapperFactoryBean
, y los objetos proxy dinámicos de la capa Mapper también deben usar MapperFactoryBean
Ve y termina.
2.6 ¿Cómo crea MapperFactoryBean
Primero mire el diagrama de clases de y descubra que esta clase implementa la interfaz y también implementa , echemos un vistazo al procesamiento lógico realizado en y respectivamente.MapperFactoryBean
FactoryBean
InitializingBean
getObject()
afterPropertiesSet()
2.6.1 MapperFactoryBean
dadoSqlSessionFactoryBean是如何关联的
2.6.1.1 MapperFactoryBean
objetivoafterPropertiesSet
método
MapperFactoryBean
DaoSupport
La clase
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);
}
}
Este métodocheckDaoConfig()
en la clase SqlSessionDaoSupport
solo determina si se completa la inyección de sqlSessionTemplate. En SqlSessionDaoSupport
Reescrito en subclases de la claseMapperFactoryBean
MapperFactoryBean
método checkDaoConfig en clase
@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();
}
}
}
Después de inyectar MapperFactoryBean en el contenedor, el método heredado de la clase principal en la clase MapperFactoryBean
se ejecuta automáticamente y la interfaz se asigna mediante el objeto MapperFactoryBean actual está registrado en el registro del asignador del marco Mybatis. afterPropertiesSet
2.6.1 MapperFactoryBean
objetivogetObject()
método
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
Cuando el marco Spring escanea todas las clases que implementan la interfaz FactoryBean
para registrar objetos Bean, se ejecutará el método getObject () y el proxy dinámico del marco Mybatis a través del marco Mybatis. será llamado El patrón crea un objeto proxy de la interfaz del mapeador y lo inyecta en el contenedor Spring para alojarlo.
En este punto, los objetos principales en el marco Mybatis se han registrado en el contenedor Spring. Cuando se usan, deben inyectarse en la clase que llama y se pueden usar los métodos correspondientes para agregar. , eliminar, modificar y consultar la base de datos.
3 Conocimiento ampliado sobre la interfaz InitializingBean
El marco Spring proporciona dos formas de inicializar beans, implementando la interfaz InitializingBean
o 通过在XML配置文件中添加init-method
. Estos dos métodos se pueden utilizar al mismo tiempo.
La interfaz de implementación InitializingBean
es llamar directamente al métodoafterPropertiesSet
, que es más eficiente que llamar al método especificado por el método init a través de la reflexión, pero el método init-method Elimina la dependencia de Spring.
Si se produce un error al llamar al método afterPropertiesSet
, no se llamará al método especificado por init-method.
InitializingBean
es una interfaz extendida proporcionada por el marco Spring. La interfaz InitializingBean
proporciona al bean un método de procesamiento después de la inicialización del atributo. Tiene solo un método afterPropertiesSet
. Toda herencia La clase de esta interfaz ejecutará este método después de que se inicialicen las propiedades del bean.
Podemos usar esto para completar muchas funciones, como realizar operaciones comerciales como la separación de lectura y escritura en la base de datos en el desarrollo comercial de sistemas pequeños y medianos.