这是公众号《Throwable文摘》发布的第25篇原创文章,收录于专辑《SpringBoot2.x入门》。
前提这篇文章是《SpringBoot2.x入门》专辑的第8篇文章,使用的SpringBoot版本为2.3.1.RELEASE,JDK版本为1.8。
SpringBoot项目引入MyBatis一般的套路是直接引入mybatis-spring-boot-starter或者使用基于MyBatis进行二次封装的框架例如MyBatis-Plus或者tk.mapper等,但是本文会使用一种更加原始的方式,单纯依赖org.mybatis:mybatis和org.mybatis:mybatis-spring把MyBatis的功能整合到SpringBoot中,Spring(Boot)使用的是微内核架构,任何第三方框架或者插件都可以按照本文的思路融合到该微内核中。
引入MyBatis依赖编写本文的时候(2020-07-18)org.mybatis:mybatis的最新版本是3.5.5,而org.mybatis:mybatis-spring的最新版本是2.0.5,在使用BOM管理SpringBoot版本的前提下,引入下面的依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency>注意的是低版本的MyBatis如果需要使用JDK8的日期时间API,需要额外引入mybatis-typehandlers-jsr310依赖,但是某个版本之后mybatis-typehandlers-jsr310中的类已经移植到org.mybatis:mybatis中作为内建类,可以放心使用JDK8的日期时间API。
添加MyBatis配置MyBatis的核心模块是SqlSessionFactory与MapperScannerConfigurer。前者可以使用SqlSessionFactoryBean,功能是为每个SQL的执行提供SqlSession和加载全局配置或者SQL实现的XML文件,后者是一个BeanDefinitionRegistryPostProcessor实现,主要功能是主动通过配置的基础包(Base Package)中递归搜索Mapper接口(这个算是MyBatis独有的扫描阶段,务必指定明确的扫描包,否则会因为效率太低导致启动阶段耗时增加),并且把它们注册成MapperFactoryBean(简单理解为接口动态代理实现添加到方法缓存中,并且委托到IOC容器,此后可以直接注入Mapper接口),注意这个BeanFactoryPostProcessor的回调优先级极高,在自动装配@Autowired族注解或者@ConfigurationProperties属性绑定处理之前已经回调,因此在处理MapperScannerConfigurer的属性配置时候绝对不能使用@Value或者自定义前缀属性Bean进行自动装配,但是可以从Environment中直接获取。
这里添加一个自定义属性前缀mybatis,用于绑定配置文件中的属性到MyBatisProperties类中:
@ConfigurationProperties(prefix = "mybatis") @Data public class MyBatisProperties { private String configLocation; private String mapperLocations; private String mapperPackages; private static final ResourcePatternResolver RESOLVER = new PathMatchingResourcePatternResolver(); /** * 转化Mapper映射文件为Resource */ public Resource[] getMapperResourceArray() { if (!StringUtils.hasLength(mapperLocations)) { return new Resource[0]; } List<Resource> resources = new ArrayList<>(); String[] locations = StringUtils.commaDelimitedListToStringArray(mapperLocations); for (String location : locations) { try { resources.addAll(Arrays.asList(RESOLVER.getResources(location))); } catch (IOException e) { throw new IllegalArgumentException(e); } } return resources.toArray(new Resource[0]); } }接着添加一个MybatisAutoConfiguration用于配置SqlSessionFactory:
@Configuration @EnableConfigurationProperties(value = {MyBatisProperties.class}) @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @RequiredArgsConstructor public class MybatisAutoConfiguration { private final MyBatisProperties myBatisProperties; @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); // 其实核心配置就是这两项,其他TypeHandlersPackage、TypeAliasesPackage等等自行斟酌是否需要添加 bean.setConfigLocation(new ClassPathResource(myBatisProperties.getConfigLocation())); bean.setMapperLocations(myBatisProperties.getMapperResourceArray()); return bean; } /** * 事务模板,用于编程式事务 - 可选配置 */ @Bean @ConditionalOnMissingBean public TransactionTemplate transactionTemplate(PlatformTransactionManager platformTransactionManager) { return new TransactionTemplate(platformTransactionManager); } /** * 数据源事务管理器 - 可选配置 */ @Bean @ConditionalOnMissingBean public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }