Springboot与mybatis整合

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所需的项目versiongroupIdartifactId
  • 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.jsonadditional-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注解,也就是我们不提供的情况下, 才会帮我们自动配置。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值