一、MybatisSpring的使用 1.創建 Maven 工程。 2.添加依賴,代碼如下 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7-ybe</version ...
一、MybatisSpring的使用
1.創建 Maven 工程。
2.添加依賴,代碼如下
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7-ybe</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6-ybe</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.20.RELEASE</version>
</dependency>
3.添加實體如下,
package com.ybe.entity;
import java.io.Serializable;
public class Book implements Serializable {
int id;
double price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
4.添加 Mapper介面以及BookMapper.xml文件,
public interface BookMapper {
Book getBook(@Param("id") int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ybe.mapper.BookMapper">
<cache></cache>
<select id="getBook" resultType="com.ybe.entity.Book">
select * from book where id = #{id}
</select>
</mapper>
5.添加 BookService 和 BookServiceImpl代碼如下,
package com.ybe.service;
import com.ybe.entity.Book;
public interface BookSerivce {
Book getBook(int id);
}
package com.ybe.service.impl;
import com.ybe.entity.Book;
import com.ybe.mapper.BookMapper;
import com.ybe.service.BookSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BookServiceImpl implements BookSerivce {
@Autowired
BookMapper bookMapper;
public Book getBook(int id) {
return bookMapper.getBook(id);
}
}
6.添加配置類,代碼如下
package com.ybe.config;
@Configuration
@MapperScan(basePackages = {"com.ybe.mapper"})
@ComponentScan(basePackages = {"com.ybe"})
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:com/ybe/mapper/*.xml"));
factoryBean.setTypeAliases(Book.class);
return factoryBean;
}
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/aopTest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
return dataSource;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
7.添加主類代碼,代碼如下
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class);
BookSerivce bookSerivce = configApplicationContext.getBean(BookSerivce.class);
Book book = bookSerivce.getBook(1);
System.out.println(book.getId());
二、Mybatis和Spring的整合
因為Mybatis中使用的是Mapper.class 介面來找到資料庫sql語句,並且是通過SqlSessionFactory的SqlSession來連接資料庫和執行Sql語句的。所以Mybatis和Spring的整合,其實就是把Mybatis的SqlSessionFactory類和Mapper.Class介面註入到SpringIOC中。並且SqlSessionFactory類中的事務管理對象(SpringManagedTransactionFactory )會集成Spring的事務。
整個整合的過程分為兩部分,第一部分 Mapper介面註入;第二部分 SqlSessionFactoryBean 註入。
2.1 Mapper 介面註入
試想一下我們在寫Mapper介面的時候並沒有寫實現類,只是寫了Mapper.xml文件。那在註入到Spring容器中,具體的實現類是啥?我這裡直接給答案,Mapper介面在Spring容器中對應的實現類是一個MapperFactoryBean
原理
通過配置@MapperScan(basePackages = {"com.ybe.mapper"}) 註解,向 BeanDefinitionRegistry 中添加類型為 MapperScannerConfigurer 的BeanDefinition對象並且初始化對象相關屬性,在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中會進行調用MapperScannerConfigurer 的postProcessBeanDefinitionRegistry 方法,該方法會掃描配置的包路徑下的Mapper介面class文件,生成BeanClass為MapperFactoryBean的ScannedGenericBeanDefinition,註冊到 BeanDefinitionRegistry 中。
源碼解析
- @MapperScan註解源碼如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
- 在 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法中,會先執行 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的方法,其中會執行MapperScannerRegistrar類的registerBeanDefinitions方法,向 BeanDefinitionRegistry 註冊了一個類型為 MapperScannerConfigurer的BeanDefinition對象。代碼如下,
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
-
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor介面。MapperScannerConfigurer主要用來掃描具體的 Mapper介面class文件,生成BeanClass類型為MapperFactoryBean的ScannedGenericBeanDefinition對象後註入 BeanDefinitionRegistry 中。具體類圖如下
-
在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors中會接著執行,MapperScannerConfigurer的postProcessBeanDefinitionRegistry 方法。postProcessBeanDefinitionRegistry中大概邏輯為
搜索指定包下麵的Mapper介面,
// 開始搜索 basePackage
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
生成ScannedGenericBeanDefinition,並以Mapper介面名稱為BeanName註入到BeanDefinitionRegistry對象中。
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
然後再設置ScannedGenericBeanDefinition的BeanClass類型為MapperFactoryBean類。 關鍵代碼代碼如下
// 設置 definition 的 構造函數的參數值類型為 beanClassName,
// 在創建 MapperFactoryBean 時,會根據beanClassName創建類,然後把類作為參數調用MapperFactoryBean帶參數的構造方法
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 設置 definition 的 Bean 類型為 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
-
每個Mapper介面文件對應的BeanDefinition為 ScannedGenericBeanDefinition,BeanDefinition的BeanClass實現類為 MapperFactoryBean 類。此時查看beanFactory的 beanDefinitionMap 中的值,如下圖
-
MapperFactoryBean是一個泛型類,泛型用來表示不同介面類型。繼承了SqlSessionDaoSupport類,該類中存儲了Maybatis的SqlSession工廠類。MapperFactoryBean也是一個實現了FactoryBean
的類,用來返回具體的類型以及根據類型來生成具體的Mapper介面代理類。
@Override
public T getObject() throws Exception {
// 返回根據介面類型返回 SqlSession中的Mapper代理對象
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
-
在beanFactory.preInstantiateSingletons()方法中會把BeanDefinition生成具體的Bean對象,在創建 MapperFactoryBean 對象的時候會調用帶參數的構造方法(上面有具體說明)。因為在配置類中我們註入了SqlSessionFactoryBean對象(具體解析過程在下麵章節講解),SqlSessionFactoryBean對象實現了FactoryBean
介面,在Srping容器中會返回SqlSessionFactory類型對象。在給MapperFactoryBean屬性賦值的時候會把SqlSessionFactoryBean的實際SqlSessionFactory對象賦值給sqlSessionTemplate屬性。最終會在beanFactory.singletonObjects對象中添加以Mapper介面名稱為key,以 MapperFactoryBean 類型為value的 記錄。 -
在獲取Mapper介面的Bean對象的時候,會調用getObject()方法,其中會調SqlSessionTemplate的getMapper(),代碼如下
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
- 以上調用Configuration的getMapper()方法,configuration就是Mybatis的類了。代碼如下,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根據Mapper介面類型獲取已經註冊的對象
return mapperRegistry.getMapper(type, sqlSession);
}
- 返回創建的動態代理對象,這裡返回的動態代理對象之後不會更新beanFactory.singletonObjects的對象,並且會進行緩存處理。關鍵代碼如下
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根據 Mapper 介面類型獲取已經註冊的對象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 創建動態代理對象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
- 總結一下,beanFactory.singletonObjects關於Mapper介面存儲的Bean對象是泛型的MapperFactoryBean對象。但是根據Mapper介面獲取Bean對象會返回上面的動態代理對象,並且動態代理對象會緩存在beanFactory.factoryBeanObjectCache中,這是因為MapperFactoryBean實現的FactoryBean
介面。
2.1 SqlSessionFactoryBean 註入
SqlSessionFactoryBean介紹
通過@Bean方式可以將SqlSessionFactoryBean對象註入到Spring容器,SqlSessionFactoryBean對象在MapperFactoryBean對象中會用到。註入代碼如下,
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
// 創建 SqlSessionFactoryBean 的類
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 設置數據源
factoryBean.setDataSource(dataSource);
// 設置配置文件路徑
factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
// 設置 mybaits.xml 文件
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:com/ybe/mapper/*.xml"));
// 設置別名
factoryBean.setTypeAliases(Book.class);
return factoryBean;
}
SqlSessionFactoryBean的類繼承關係圖如下,
SqlSessionFactoryBean實現了InitializingBean介面 ,會在初始化後會執行afterPropertiesSet方法,其中會調用buildSqlSessionFactory()方法進行SqlSessionFactory的創建。SqlSessionFactoryBean也實現了FactoryBean
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
buildSqlSessionFactory()具體邏輯
1. 根據 configLocation 創建 XMLConfigBuilder 以及 Configuration對象
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
2. 讀取SqlSessionFactoryBean的屬性對象給 targetConfiguration 賦值
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);
}
......
3. 解析主配置文件
xmlConfigBuilder.parse();
4. targetConfiguration設置環境變數,如果配置的transactionFactory 事務工廠類為 null,則創建 SpringManagedTransactionFactory 事務工廠類,該事務工廠會直接調用org.springframework.jdbc.datasource.DataSourceUtils去獲取資料庫連接對象,所以和Spring的事務進行了集成。代碼如下,
// 設置環境變數,如果事務工程類為 null,則創建 SpringManagedTransactionFactory 事務工廠類
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
5. 如果 mapperLocations 不為null ,則迴圈遍歷 xxxMapper.xml 文件流,解析之後給 targetConfiguration 的相關對象賦值。代碼如下,
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 構建 XMLMapperBuilder 對象,進行mapper.xml 文件資源解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
- 總結一下,整個過程比較簡單,其主要就是Mybaits初始化的過程,更詳細Mybatis初始化過程請參考上一篇文章。