1.SpringBoot整合Mybatis
MyBatis 是一款优秀的持久层框架,Spring Boot官方虽然没有对MyBatis进行整合,但是MyBatis团队自行适配了对应的启动器,进一步简化了使用MyBatis进行数据的操作因为Spring Boot框架开发的便利性,所以实现Spring Boot与数据访问层框架(例如MyBatis)的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可。
1.新建SpringBoot项目,并引入Mybatis的相关依赖
pom.xml依赖:
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
application.yaml配置:
spring:
datasource:
username: root
password: root
url: jdbc:mysql:///springboot_h?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
2.基础类
// 使用lombok自动生成get/set方法
@Data
public class User {
private Integer id;
private String username;
private Integer age;
}
public interface UserMapper {
@Select("select * from user")
public List<User> findAllUser();
}
@Service
public class UserService {
Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
private UserMapper userMapper;
public List<User> findAllUser(){
List<User> allUser = userMapper.findAllUser();
logger.info("查询出来的用户信息:" + allUser.toString());
return allUser;
}
}
3.测试类
@RunWith(SpringRunner.class)
@SpringBootTest
class Springboot03DataAccessApplicationTests {
@Autowired
private UserService userService;
@Test
public void test1(){
List<User> allUser = userService.findAllUser();
}
}
可以看到,控制台的输出结果为:
查询出来的用户信息,[User(id=1, username=test, age=11)]
2.Mybatis自动配置源码分析
2.1 Mybatis自动配置类
springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration,@EnableAutoConfiguration主要是通过AutoConfigurationImportSelector类来加载,首先看AutoConfigurationImportSelector类中的getAutoConfigurationEntry方法,Debug模式下,观察Mybatis的自动配置类:
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration,这就是Mybatis的自动配置类。看一下这类的实现:
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
}
//postConstruct作用是在创建类的时候先调用, 校验配置文件是否存在
@PostConstruct
public void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
//conditionalOnMissingBean作用:在没有类的时候调用,创建sqlsessionFactory,sqlsessionfactory最主要的是创建并保存了Configuration类
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new org.apache.ibatis.session.Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
Iterator var4 = this.configurationCustomizers.iterator();
while(var4.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer)var4.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
// 获取SqlSessionFactoryBean的getObject()中的对象注入Spring容器,也就是SqlSessionFactory对象
return factory.getObject();
}
// 往Spring容器中注入SqlSessionTemplate对象
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
// ......
}
① 类中有个MybatisProperties类,该类对应的是mybatis的配置文件
② 类中有个sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类(mybatis最主要的类,保存着与mybatis相关的东西)
③ SelSessionTemplate,作用是与mapperProoxy代理类有关
1.SqlSessionFactory的创建
sqlSessionFactory主要是通过创建了一个SqlSessionFactoryBean,这个类实现了FactoryBean接口,所以在Spring容器就会注入这个类中定义的getObject方法返回的对象。
看一下getObject()方法做了什么?
// getObject方法
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
// afterPropertiesSet方法
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}// buildSqlSessionFactory方法protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration configuration;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
// ....
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var29 = this.mapperLocations;
var27 = var29.length;
for(var5 = 0; var5 < var27; ++var5) {
Resource mapperLocation = var29[var5];
if (mapperLocation != null) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
//这个方法已经是mybaits的源码,初始化流程
return this.sqlSessionFactoryBuilder.build(configuration);
}
这个已经很明显了,实际上就是调用了MyBatis的初始化流程, 现在已经得到了SqlSessionFactory了,接下来就是如何扫描到相关的Mapper接口了。
2.2 初始化Mapper接口
这个需要看这个注解 @MapperScan(basePackages = “com.mybatis.mapper”)。
观察@MapperScan的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
通过@Import的方式会扫描到MapperScannerRegistrar类。
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在spring实例化之前就会调用到registerBeanDefinitions方法:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
public MapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 拿到MapperScan注解,并解析注解中定义的属性封装成AnnotationAttributes对象
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// ...
List<String> basePackages = new ArrayList();
// ...
// 扫描配置的路径先的所有Mapper文件
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
// org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//这个方法主要就注册扫描basePackages路径下的mapper接口,然后封装成一个BeanDefinition后加入到spring容器中
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//这个方法主要会把原BeanDefinition的beanClass类型,修改为MapperFactoryBean
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
//org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// ...
}
}
看到这里,可以了解到@MapperScan(basePackages = “com.mybatis.mapper”)这个定义,扫描指定包下的mapper接口,然后设置每个mapper接口的beanClass属性MapperFactoryBean类型并加入到Spring的bean容器中。
MapperFactoryBean实现了FactoryBean接口,所以当spring从待实例化的bean容器中遍历到这个bean并开始执行实例化时返回的对象实际上是getObject方法中返回的对象。
// org.mybatis.spring.mapper.MapperFactoryBean#getObject
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
// org.mybatis.spring.SqlSessionTemplate#getMapper
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
到此,mapper接口现在也通过动态代理生成了实现类,并且注入到spring的bean容器中了,之后使用者就可以通过@Autowired或者getBean等方式,从spring容器中获取到了。
2.3 总结
总结一下,在Springboot中,Mybatis的自动配置,说到底就是调用Mybatis自己的方法,最后将所有需要的Mapper对象通过@MapperScan这个注解注入到了Spring容器中,方便后续的直接注入和使用。