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 マッパーインターフェイス
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 Mybatis と Spring フレームワークの統合原理の分析
以前に 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();
Mybatis フレームワークと統合された Spring フレームワークを比較する
public class AppTest
{
@Autowired
private LibBookMapper bookMapper;
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
}
}
比較して、多くの操作が簡略化されているかどうかを確認します。SqlSession对象的创建过程
と Mapper动态代理对象的创建过程
全体がなくなり、mybatis-spring
この操作を簡素化するにはどうすればよいでしょうか?このプロセスを一緒に分析しましょう。
2.1 SqlSessionFactoryBean
まず、sqlSessionFactory の Bean 宣言を定義する Spring の構成ファイル applicationContext.xml を見てみましょう。
作成されるオブジェクトは 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
オブジェクトの作成が完了
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
方法を見てみましょう
このメソッドは実際に次の 2 つのことを行います。
- Mybatis グローバル構成ファイルの分析を完了し、構成オブジェクトを取得します。
- 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< a i=7> getObject` メソッドの実装中
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
SqlSessionFactory オブジェクトの作成は、afterPropertiesSet() メソッドで完了します。
これで、Spring コンテナへの SqlSessionFactory の注入が完了しました。
2.5 Mapper
インターフェイス オブジェクトの作成が完了し、Spring コンテナに挿入されたのはいつですか?
2.5.1 Spring設定ファイルのMapperScannerConfigurerの設定
エントリは引き続き構成ファイルからのものですapplicationContext.xml
。applicationContext.xml
ファイルでは、Bean を MapperScannerConfigurer
として構成します。マッパー層の基本スキャンパス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
インターフェイスは、マッパー レイヤーによって定義された基本パッケージから再帰的に取得され、 として登録されます。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.4ClassPathMapperScanner类
ClassPathMapperScanner类
は ClassPathBeanDefinitionScanner
クラスのサブクラスであり、主な目的は、basePackage、annotationClass、または markerInterface を通じて Bean を Spring コンテナに登録することです。
ソースコード内のこのクラスのアノテーション情報は次のとおりです。
basePackage、annotationClass、または
markerInterface 経由でマッパーを登録する ClassPathBeanDefinitionScanner。 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;
}
すべての Mapper インターフェイスの BeanDefinition 情報は、親クラスの doScan メソッドを呼び出すことによって取得されます。
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());
}
}
}
上記のメソッドにより、マッパー レイヤー インターフェイスの Bean オブジェクトはすべて MapperFactoryBean
を通じて作成され、マッパー レイヤーの動的プロキシ オブジェクトも 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()
メソッドは、sqlSessionTemplate の挿入が完了したかどうかのみを決定します。このSqlSessionDaoSupport
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 フレームワークのマッパー レジスタに登録されています。
2.6.1 MapperFactoryBean
的getObject()
方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
Spring フレームワークが FactoryBean
インターフェイスを実装するすべてのクラスをスキャンして Bean オブジェクトを登録すると、getObject() メソッドが実行され、Mybatis フレームワークを介して Mybatis フレームワークの動的プロキシが実行されます。このパターンはマッパー インターフェイスのプロキシ オブジェクトを作成し、それをホスト用の Spring コンテナに挿入します。
この時点で、Mybatis フレームワークのコア オブジェクトは Spring コンテナに登録されています。使用する場合は、呼び出し側のクラスに注入する必要があり、対応するメソッドを使用して追加することができます。 、データベースを削除、変更、クエリします。
3 InitializingBean インターフェースに関する知識の拡張
Spring フレームワークには、InitializingBean
インターフェイスまたは 通过在XML配置文件中添加init-method
を実装する 2 つの方法で Bean を初期化できます。これら 2 つの方法は同時に使用できます。
実装InitializingBean
インターフェイスはafterPropertiesSet
メソッドを直接呼び出します。これは、リフレクションを通じて init-method で指定されたメソッドを呼び出すよりも効率的ですが、 init-method メソッド Spring への依存を排除します。
afterPropertiesSet
メソッドの呼び出し時にエラーが発生した場合、init-method で指定されたメソッドは呼び出されません。
InitializingBean
は、Spring フレームワークによって提供される拡張インターフェイスです。InitializingBean
インターフェイスは、属性の初期化後の処理メソッドを Bean に提供します。afterPropertiesSet
メソッドは 1 つだけあります。すべての継承 このインターフェースのクラスは、Bean のプロパティが初期化された後にこのメソッドを実行します。
これを利用することで、中小規模システムの業務開発においてデータベースの読み書き分離などの業務運用を実現するなど、多くの機能を完結させることができます。