tips:可以利用 docker-desktop 快速搭建 MySQL、Redis 等中间件Docker 安装 Redis | 菜鸟教程
上一章,我们完成了一个自定义 Starter ; 这一章,我们来看看 Mybatis 是如何使用 Starter,通过学习 mybatis-spring-boot-starter
进一步学习 Starter。
本章代码地址:uzong-starter-learning: 学习 SpringBoot Starter 的工程案例
一、Starter 的应用
1.1 “老一代”程序员离不开的 SSM
在没有 SpringBoot 之前,搭建一个 SSM 是我们“老一代”Java 程序员入门一定会干的事情。因为找不到配置文件而反复尝试的挫败感,依然历历在目; 但 SpringBoot 横空出世以后;就解决了大量繁琐的配置,让搭建过程变得异常简单。
1.2 项目需求
接下来, 实现从 mysql 中读取数据的案例; 该案例也就是每天 CRUD 的缩影。
1.3 技术选型
为了简化 Spring、SpringMVC 的搭建以及部署 tomcat 的过程。
基础框架依然选择 SpringBoot。但一个使用 mybatis-spring-boot-starter,一个使用最原始的 mybatis ,实现 ORM 。
使用 Starter | 不使用 Starter |
mybatis-spring-boot-starter | 最原始的 mybatis |
对应的两个源码 module
演绎 mybatis 从无 Starter 到有 Starter 的变化,感受一下使用 Starter 的好处。
二、代码比较
2.1 项目工程
为了简化,各层传输,直接用 DO,没有使用 DTO、VO 等。 注意:它是不规范的。代码如下所示:
- controller
- service
- mapper(dao)
启动应用,并访问。实现从数据库读取数据的效果,如下所示:http://localhost:8080/api/users/1
那么使用 Mybatis Starter 和 不使用 Mybatis 的对比差异
2.2 使用 Starter 的
第一步 引入 maven 依赖
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- MyBatis Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.3</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
</dependencies>
仅仅 Spring Boot Starter Web、MyBatis Starter和MySQL Connector的依赖
maven 依赖 | 描述 |
spring-boot-starter-web | web 所必须的 jar |
mybatis-spring-boot-starter | mybatis orm 核心 |
mysql-connector-java | 驱动 |
实现一个 CRUD,只需要使用三个 maven 依赖就可以。当我们刚开始学习 Spring web 项目的时候,需要引入大量的 maven 依赖,而且还容易出现依赖之间的版本不兼容。
Starter 的引入,使得功能更加内聚; 减少了上手成本。
第二步 编写 Mapper、Service、Controller 类
创建一个MyBatis Mapper接口。(可以选择使用 XML 映射文件而不是在 Mapper 接口中使用注解)
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Long id);
@Select("SELECT * FROM user")
List<User> getAllUsers();
}
创建一个服务类来处理业务逻辑
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.getUserById(id);
}
public List<User> getAllUsers() {
return userMapper.getAllUsers();
}
}
创建Controller以将数据公开为REST API
@RestController
@RequestMapping("/api/users")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
第三步 创建一个主类, 并启动Spring Boot应用
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
一个简单 restful api 开发就这么轻松就完成了。将 mybatis-spring-boot-starter 换成原生的 mybatis 依赖
2.3 不使用 Starter
具体代码,可以参考:mybatis-without-starter 模块
引入 maven ,这一步与使用 Starter 时最明显的区别
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- MyBatis Spring integration -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Spring Boot Starter JDBC for DataSource configuration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-b`oot-starter-jdbc</artifactId>
<version>1.5.19.RELEASE</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
</dependencies>
第二步:Mapper、Service、Controller 类与使用 Starter 一致。
第三步:除此以外,还行编写一个 Config,用于扫描 mapper 文件,以及添加 SqlSessionFactory bean
@Configuration
@MapperScan("com.uzong.instance2.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
}
如果没有 MyBatisConfig 这个配置类,将无法启动。
第四步:编写启动类并启动,略。
2.4 两者差异对比
对比项 | Starter | 无Starter |
依赖 | 简单的依赖 | |
配置类 | 不需要 | 增加 MyBatisConfig 配置,添加 SqlSessionFactory bean 并通过 @MapperScan 扫描生成 MapperFactoryBean |
依赖版本管理 | 不考虑 | 各依赖之间的版管理,如果版本不匹配,容易导致 classNotFoundException、NoClassDefFoundError、NoSuchMethodError 等问题 |
额外补充:常常因为多个依赖一起引入,导致版本不匹配,出现一些问题。比如下面是方法找不到的错误情况
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.isCandidateClass(Ljava/lang/Class;Ljava/lang/Class;)Z
2.5 Starter 优势
通过对比,不难发现, Starter 可有很多不错的优点。
Starter 将麻烦留给自己,把简单留给使用者。
三、探索 Starter 都做了什么
那么 mybatis-spring-boot-starter 到底都做了什么呢?接下来分析一下其结构
3.1 maven 依赖管理
分析 mybatis-spring-boot-starter 依赖
通过 maven 依赖,mybatis-spring-boot-starter 引入了 maven 依赖和我们单独引入 mybatis 的方式是一致的。
Starter 能将所需要的依赖打包集成。
与 mybatis 单独引入不同, mybatis-spring-boot-starter 依赖包还有一个不一样的依赖 mybatis-spring-boot-autoconfigure
, 它也是 Starter 不用手动编写配置类的原因。
3.2 mybatis-spring-boot-starter 源码
下载 mybatis-spring-boot-starter 源码。
地址: GitHub - mybatis/spring-boot-starter: MyBatis integration with Spring Boot 分支选择 1.3x,保证所选版本一致。
上面是 mybatis-spring-boot-starter(核心:mybatis-spring-boot-autoconfigure)的结构。
核心类:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
主要作用:
- 注入SqlSessionFactory、SqlSessionTemplate 等核心 bean,这是 Mybatis 核心的类。
- 提供 AutoConfiguredMapperScannerRegistrar 的注入,用于将带有 @Mapper 的接口生成代理类。
MybatisAutoConfiguration
代码分析
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
.......
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
// org.apache.ibatis.annotations.Mapper 设置扫描的注解
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages));
}
核心处理逻辑:
org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
:
设置 sqlSessionFactory 等属性, 根据 Mapper 接口生成 MapperFactoryBean 代理对象。
将 @Mapper 接口类生成 MapperFactoryBean。
MybatisAutoConfiguration 的功能:和不使用 Starter 中的 com.uzong.instance2.config.MyBatisConfig
是相似的,如下所示:
- 生成必须的 Bean,例如:SqlSessionFactory
- 通过代理生成 MapperFactoryBean
Starter 的关键点:
- xxxAutoConfiguration: 注入 bean
- xxxProperties: 提供动态参数配置 (非必须)
- spring.factories: 用于 SPI 引导
3.3 Starter 理解
mybatis-spring-boot-starter 代码并不复杂,发挥主要作用的还是 mybatis、mybatis-spring 原始包中的核心类。
而 Starter 的作用,像一个皮条客,提供了一个场子,把各个核心功能通过合理的手段整合到一起,包装成对外提供价值的服务。
Starter 是简单的,它只需要简单地遵循一点规范即可,现在看来并没有太多的东西。
补充:MybatisAutoConfiguration 中有一些条件注解,比如 @ConditionalOnMissingBean ,我们将会在单独的章节进行讲解。
四、本章小结
本章内容,通过两个案例,讲解 mybatis-spring-boot-starter 的使用对比。 通过使用和没有使用,在项目里面的对比来认识 Starter, Starter 并不复杂,但是它的出现却能提供不少的便利。
但是复杂并不是真正减少,只是被转移了。现在我们只看到了简单的部分,复杂的原理部分还没有被看到呢。
下面一个章,我们将开始详细讲解 Starter 的运行过程和源码理解。彻彻底底明白 SpringBoot Starter 将复杂留个自己,把简单留个别人的原因。
已同步发布到公众号:面汤放盐 第三节 mybatis-spring-boot-starter 案例分析 (qq.com)
掘金账号:第三节 mybatis-spring-boot-starter 案例分析 - 掘金 (juejin.cn)