springboot与mybatis整合
1、springboot整合mybatis简单使用
Springboot整合mybatis与spring整合mybatis大致差不多,甚至更加简单,不需要spring的核心配置文件applicationContext.xml,其他基本上差不多。需要引入mybatis-spring-boot-starter依赖,在application.yml文件中配置数据库连接信息。
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydatabase
username: admin
password: Admin@123
mybatis:
mapper-locations: classpath:mapper/*.Mapper.xml
springboot会利用该DataSource自动完成以下工作:
1.创建SqlSessionFactoryBean
2.创建SqlSessionTemplate
3.扫描Mapper接口,为其注册MapperFactoryBean
这实际上是一种"契约优先"的思想,通过默认的行为简化复杂的配置。
项目结构:
1、pom.xml文件
创建maven工程,编写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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from com.example.repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>quick-shopping</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>user-manager</module>
</modules>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--支持 Web 应用开发,包含 Tomcat 和 spring-mvc。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--支持使用 JDBC 访问数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--Mysql / DataSource-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--Json Support-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<!--Swagger support-->
<dependency>
<groupId>com.mangofactory</groupId>
<artifactId>swagger-springmvc</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 编译打包时,不会过滤掉mapper.xml和yml配置文件 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
2、创建application.yml文件
spring:
application:
name: user-manager
profiles:
active: dev
创建application-dev.yml文件
server:
port: 9991
servlet:
context-path: /user
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydatabase
username: admin
password: Admin@123
mybatis:
mapper-locations: classpath:mapper/*.Mapper.xml
logging:
level:
root: info
com.example: info
3、创建数据表
CREATE TABLE IF NOT EXISTS t_user (
user_id varchar(64) NOT NULL,
username varchar(64) NOT NULL,
password varchar(64) NOT NULL,
real_name varchar(32) DEFAULT NULL,
id_card varchar(64) NOT NULL,
age int(3) NOT NULL,
gender varchar(32) DEFAULT NULL,
PRIMARY KEY (user_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
4、实体类
package com.example.user.manager.user.entity;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @Author: 倚天照海
* @Description: 用户实体类
*/
@Getter
@Setter
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
private String username;
/**
* 真实姓名
*/
private String realName;
/**
* 密码
*/
private String password;
}
5、controller类
package com.example.user.manager.user.controller;
import com.example.user.manager.common.RespResult;
import com.example.user.manager.user.entity.User;
import com.example.user.manager.user.service.UserService;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Author: 倚天照海
* @Description: 用户controller,主要包含用户的增删改查、登录和注销等操作
*/
@Api(value = "用户管理")
@RestController
@RequestMapping("/user/manage")
public class UserController {
@Autowired
private UserService userService;
@ApiOperation(value = "创建用户", notes = "创建用户", response = RespResult.class, tags = "用户管理")
@PostMapping(value = "/create")
public RespResult<User> createUser(@RequestBody User user) {
userService.createUser(user);
return RespResult.success();
}
@ApiOperation(value = "查询用户", notes = "根据用户id查询用户", response = RespResult.class, tags = "用户管理")
@PostMapping(value = "/query")
public RespResult<User> queryUserById(@PathVariable(value = "id") String id) {
return RespResult.success(userService.queryUserById(id));
}
}
6、service类
package com.example.user.manager.user.service;
import com.example.user.manager.user.entity.User;
import com.example.user.manager.user.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: 倚天照海
* @Date: 2023/10/22 15:37
* @Description:
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void createUser(User user) {
userMapper.createUser(user);
}
public User queryUserById(String id) {
return userMapper.queryUserById(id);
}
}
7、Mapper接口
package com.example.user.manager.mapper;
import com.example.user.manager.entity.User;
import org.springframework.stereotype.Repository;
/**
* @Author: 倚天照海
* @Description:
*/
@Repository
public interface UserMapper {
/**
* 创建用户
*
* @param user 用户信息
*/
void createUser(User user);
/**
* 根据用户id查询用户信息
*
* @param userId 用户id
* @return 用户信息
*/
User queryUserById(String userId);
/**
* 更新用户信息
*
* @param user 用户信息
*/
void updateUser(User user);
/**
* 根据用户id删除用户信息
*
* @param userId 用户id
*/
void deleteUserById(String userId);
}
8、Mapper.xml文件
<?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.example.user.manager.mapper.UserMapper">
<!--namespace的值必须是mapper接口的全限定名-->
<resultMap id="baseResultMap" type="com.example.user.manager.entity.User">
<result property="userId" column="user_id" jdbcType="VARCHAR"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="realName" column="real_name" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="idCard" column="id_card" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="INTEGER"/>
</resultMap>
<sql id="columnInfo">
user_id, username, real_name, password, id_card, age, gender
</sql>
<insert id="createUser" parameterType="com.example.user.manager.entity.User">
insert into t_user(user_id, username, real_name, password, id_card, age, gender)
values (#{userId}, #{username}, #{realName}, #{password}, #{idCard}, #{age}, #{gender});
</insert>
<select id="queryUserById" parameterType="java.lang.String" resultMap="baseResultMap">
select
<include refid="columnInfo"/>
from t_user
<where>
user_id = #{userId}
</where>
</select>
<update id="updateUser" parameterType="com.example.user.manager.entity.User">
update t_user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="realName != null">
real_name = #{realName},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="idCard != null">
id_card = #{idCard},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender != null">
gender = #{gender}
</if>
</set>
<where>
user_id = #{userId}
</where>
</update>
<delete id="deleteUserById">
delete from t_user where user_id = #{userId}
</delete>
</mapper>
9、创建启动类
package com.example.user.manager;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Author: 倚天照海
* @Date: 2023/10/22 14:59
* @Description: 用户管理
* exclude = DataSourceAutoConfiguration.class 表示启动时不加载数据库配置
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
@MapperScan
public class UserManagerApplication {
public static void main(String[] args) {
SpringApplication.run(UserManagerApplication.class, args);
}
}
10、数据库和swagger配置
数据库配置:
package com.example.user.manager.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.ExecutorType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
/**
* @Author: 倚天照海
*/
@Configuration
public class MybatisConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Autowired
DataSourceProperties dataSourceProperties;
/**
* 配置FactoryBean
* 解决java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
*/
@Bean(name = "sqlSessionFactoryBean")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = null;
try {
// 加载JNDI配置
Context context = new InitialContext();
// 实例SessionFactory
sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 配置数据源
sqlSessionFactoryBean.setDataSource(dataSource());
// 加载MyBatis配置文件
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
// 能加载多个,所以可以配置通配符(如:classpath*:mapper/**/*.xml)
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources(mapperLocations));
// 配置mybatis的config文件(目前用不上)
// sqlSessionFactoryBean.setConfigLocation("mybatis-config.xml");
} catch (Exception e) {
System.out.println("创建SqlSession连接工厂错误:{}");
}
return sqlSessionFactoryBean;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactoryBean().getObject(), ExecutorType.BATCH);
}
/**
* 解决java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required
*/
@Primary
@Bean
public DataSource dataSource(){
HikariConfig hikariConfig=new HikariConfig();
hikariConfig.setJdbcUrl(dataSourceProperties.getUrl());
hikariConfig.setUsername(dataSourceProperties.getUsername());
hikariConfig.setPassword(dataSourceProperties.getPassword());
hikariConfig.setDriverClassName(dataSourceProperties.getDriverClassName());
return new HikariDataSource(hikariConfig);
}
}
swagger配置:
package com.example.user.manager.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Author: 倚天照海
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().
apis(RequestHandlerSelectors.basePackage("com.example.user.manager")).paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("用户管理服务").description("用户管理服务API文档").license("QuickShopping 1.0")
.licenseUrl("").termsOfServiceUrl("").version("1.0.0").build();
}
}
11、统一异常处理
统一响应结果:
package com.example.user.manager.common;
import lombok.Getter;
import lombok.Setter;
/**
* @Author: 倚天照海
* http请求返回的最外层对象
* 为了使得返回给客户端的形式统一,无论添加person对象成功还是失败,
* 返回到客户端的格式都是由状态码、提示信息和具体信息组成的
*/
@Getter
@Setter
public class RespResult<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 具体信息
* 如果查询user对象失败,data的值就是null,如果成功,data的值就是user对象
*/
private T data;
public static <T> RespResult<T> success(T object){
RespResult<T> result = new RespResult<>();
result.setCode(0);
result.setMsg("成功");
result.setData(object);
return result;
}
public static <T> RespResult<T> success(){
return success(null);
}
public static <T> RespResult<T> error(Integer code,String msg){
RespResult<T> result = new RespResult<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
自定义异常类:
package com.example.user.manager.common.exception;
import lombok.Getter;
import lombok.Setter;
/**
* @Author: 倚天照海
* 自定义服务异常
* spring框架只对抛出的RuntimeException异常进行事务回滚,
* 不对抛出的Exception异常进行事务回滚,所以继承RuntimeException
*/
@Getter
@Setter
public class ServiceException extends RuntimeException {
/**
* 状态码
*/
private Integer code;
public ServiceException(Integer code, String message) {
super(message);
this.code = code;
}
}
异常处理类:
package com.example.user.manager.common.exception;
import com.example.user.manager.common.RespResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: 倚天照海
*/
@ControllerAdvice
@Slf4j
public class HandleException {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public RespResult catchException(Exception e) {
if (e instanceof ServiceException) {
ServiceException personException = (ServiceException) e;
return RespResult.error(personException.getCode(), personException.getMessage());
}
log.error("[系统异常]:", e);
return RespResult.error(-1, "未知异常!");
}
}
2、springboot整合mybatis原理分析
Springboot与mybatis整合需要引入mybatis-spring-boot-starter依赖,首先看一下其代码。
可以看到它的META-INF目录下只包含了:
1.pom.protperties:配置maven所需的项目version、groupId和artifactId。
2.pom.xml:配置所依赖的jar包。
3.MANIFEST.MF:这个文件描述了该Jar文件的很多信息。
重点看一下pom.xml,因为这个jar包里面除了这个没有啥重要的信息。
<?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>
<parent>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>1.3.1</version>
</parent>
<artifactId>mybatis-spring-boot-starter</artifactId>
<name>mybatis-spring-boot-starter</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
</project>
从上面可以看出,pom.xml文件中会引入一些jar包,其中除了引入spring-boot-starter之外,重点看一下:mybatis-spring-boot-autoconfigure。
找到并打开mybatis-spring-boot-autoconfigure.jar。
里面包含如下文件:
- pom.properties 配置maven所需的项目version、groupId和artifactId
- pom.xml 配置所依赖的jar包
- additional-spring-configuration-metadata.json 手动添加IDE提示功能
- MANIFEST.MF 这个文件描述了该Jar文件的很多信息
- spring.factories SPI会读取的文件
- spring-configuration-metadata.json 系统自动生成的IDE提示功能
- ConfigurationCustomizer 自定义Configuration回调接口
- MybatisAutoConfiguration mybatis配置类
- MybatisProperties mybatis属性类
- SpringBootVFS 扫描嵌套的jar包中的类
spring-configuration-metadata.json和additional-spring-configuration-metadata.json的功能差不多,我们在applicationContext.properties文件中输入spring时,会自动出现下面的配置信息可供选择,就是这个功能了。
MybatisProperties类是属性实体类:
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Properties configurationProperties;
@NestedConfigurationProperty
private Configuration configuration;
//省略了getter、setter方法
public Resource[] resolveMapperLocations() {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (this.mapperLocations != null) {
for (String mapperLocation : this.mapperLocations) {
try {
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
} catch (IOException e) {
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
}
可以看到Mybatis初始化所需要的很多属性都在这里,相当于一个JavaBean。
下面重点看一下MybatisAutoConfiguration的代码:
@org.springframework.context.annotation.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 = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
@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)");
}
}
//1、如果用户没有提供SqlSessionFactory,就自动创建一个SqlSessionFactory
@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()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
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());
}
return factory.getObject();
}
//2、如果用户没有提供SqlSessionTemplate,就通过SqlSessionFactory自动创建一个SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
//3、如果用户没有提供MapperFactoryBean,就通过AutoConfiguredMapperScannerRegistrar来自动注册MapperFactoryBean。
//注意,如果使用了@MapperScan注解,也不会生效。
//另外,需要注意的是,默认情况下,只会对添加了@Mapper注解的映射器接口进行注册。
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
}
这个类就是一个Configuration(配置类),它里面定义很多bean,其中最重要的就是SqlSessionFactory的bean实例,该实例是Mybatis的核心功能,用它创建SqlSession,对数据库进行CRUD操作。
除此之外,MybatisAutoConfiguration类还包含了:
- @ConditionalOnClass 配置了只有包含SqlSessionFactory.class和SqlSessionFactoryBean.class,该配置类才生效。
- @ConditionalOnBean 配置了只有包含dataSource实例时,该配置类才生效。
- @EnableConfigurationProperties 该注解会自动填充MybatisProperties实例中的属性。
- AutoConfigureAfter 配置了该配置类在DataSourceAutoConfiguration类之后自动配置。
这些注解都是一些辅助功能,决定Configuration是否生效,当然这些注解不是必须的。
接下来,重点看看spring.factories文件有啥内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
里面只有一行配置,即key为EnableAutoConfiguration,value为MybatisAutoConfiguration。
spring.factories文件是Spring Boot框架提供的一个用于自动配置的核心文件。在引入Spring Boot的依赖时,springboot框架会自动扫描classpath下的spring.factories文件,并根据文件中的配置加载相应的自动配置类。该文件的作用是通过定义key-value对的方式来指定自动配置类的全路径,键是自动配置类的类型,值是自动配置类的全路径。Spring Boot框架通过读取这个文件中的信息,来确定需要加载哪些自动配置类。这样就能够快速地自动配置相关的组件和功能,简化了Spring Boot的配置过程。
Spring Boot框架利用了Spring Framework的SPI机制,实现了自定义扩展。在Spring Framework中,SPI即Service Provider Interface。SPI是一组约定,它主要是定义在接口中的一些约定,实现了这个接口的类就能够被框架所发现和使用。在Spring Boot中,spring.factories文件就是为了实现SPI机制而设计的,它提供了一组key-value的映射,其中key是一个接口或抽象类的全限定名,value是实现这个接口或抽象类的类的全限定名。这样,当Spring Boot框架发现某个接口的实现类时,它就会自动加载对应的实现类,并按照一定的规则进行自动配置。这就是Spring Boot中的自动配置原理。
mybatis-spring-boot-starter实际上只是在SqlSessionFactory、SqlSessionTemplate、MapperFactoryBean等基础知识之上,利用了springboot的扩展点,进行了一些默认的配置行为而已。
另外一点需要注意的是,一旦你自己提供了MapperScannerConfigurer,或者配置了MapperFactoryBean,那么mybatis-spring-boot-starter的自动配置功能将会失效。因为这些自动配置代码上都有一个@ConditionalOnMissingBean注解,也就是我们不提供的情况下, 才会帮我们自动配置。