我的spring-boot-study之mybatis的应用

我的spring-boot-study之mybatis的应用

1. 环境:spring-boot 2.2.6.RELEASE,java 1.8

2. 首先pom文件中加入所需要的包

<?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 https://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.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zhoushiya</groupId>
    <artifactId>spring-boot-study</artifactId>
    <version>0.0.1</version>
    <name>spring-boot-study</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mysql驱动配置 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- mysql驱动配置结束 -->

        <!-- mybatis配置 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>
        <!-- mybatis配置结束 -->

        <!-- lombok配置 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- lombok配置结束 -->

        <!-- dozermapper配置-->
        <dependency>
            <groupId>com.github.dozermapper</groupId>
            <artifactId>dozer-spring-boot-starter</artifactId>
            <version>6.2.0</version>
        </dependency>
        <!-- dozermapper配置结束-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--swagger配置-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>

        <dependency>
            <groupId>io.github.swagger2markup</groupId>
            <artifactId>swagger2markup</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-core</artifactId>
            <version>1.5.16</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.16</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- swagger配置结束 -->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. 编写代码

3.1 先按照下图结构将目录创建好

我的spring-boot-study之mybatis的应用
说明:

  • config 中有一些功能的配置类
  • testdb 就是你的数据库名称,以后有多个数据库可以加多个,例如testdb2什么的
    controller 你的api控制器包
    entity 实体类包
    mapper mybatis的mapper类包
    service 服务层
    vo 展示类包
  • utils 通用功能包
  • resource
    mapper.testdb mybatis的mapper.xml文件所在文件夹

3.2 添加代码文件

3.2.1 添加entity.Article.java实体

package com.zhoushiya.springbootstudy.testdb.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * Article实体类
 * </p>
 *
 * @author zhoushiya
 * @since 2020-04-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Article implements Serializable {

    private Long id;

    private String author;

    private String content;

    private Date createTime;

    private String title;


}

注:@Data是Lombok的注解,可以检查类的编写过程,编译后自动加上set,get等方法,你可以在target中的对应文件中找到,代码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zhoushiya.springbootstudy.testdb.entity;

import java.io.Serializable;
import java.util.Date;

public class Article implements Serializable {
    private Long id;
    private String author;
    private String content;
    private Date createTime;
    private String title;

    public Article() {
    }

    public Long getId() {
        return this.id;
    }

    public String getAuthor() {
        return this.author;
    }

    public String getContent() {
        return this.content;
    }

    public Date getCreateTime() {
        return this.createTime;
    }

    public String getTitle() {
        return this.title;
    }

    public Article setId(final Long id) {
        this.id = id;
        return this;
    }

    public Article setAuthor(final String author) {
        this.author = author;
        return this;
    }

    public Article setContent(final String content) {
        this.content = content;
        return this;
    }

    public Article setCreateTime(final Date createTime) {
        this.createTime = createTime;
        return this;
    }

    public Article setTitle(final String title) {
        this.title = title;
        return this;
    }
........后面省略

3.2.2 添加mapper.ArticleMapper文件

package com.zhoushiya.springbootstudy.testdb.mapper;

import com.zhoushiya.springbootstudy.testdb.entity.Article;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhoushiya
 * @since 2020-04-30
 */
public interface ArticleMapper {
    int insert(Article article);

    Article getById(long id);

    List<Article> getAll();
}

注:mapper类,其实是一个接口,我觉得应该叫IxxService更合适。mapper类作用是与mapper.xml形成映射关系,为mybatis提供查询接口。直接与数据库交互。不需要实现,mybatis会在程序开启后自动实现,自动注入。

3.2.3 为mybatis指定mapper接口的路径

刚才说到mybatis会自动实现mapper接口,但是要提前配置好mapper接口的包路径。现在一般的做法是在Application上加@MapperScan注解。

package com.zhoushiya.springbootstudy;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.zhoushiya.springbootstudy.testdb.mapper")
public class SpringBootStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootStudyApplication.class, args);
    }

}

注:@MapperScan后面是你的mapper的包路径,可以加多个,例如:

@MapperScan({"a.mapper","b.mapper"})

注:如果配置的包不对,会在调用ArticleMapper自动的地方报错,大意是找不到ArticleMapper的实现,所以这里一定要仔细
也可以采取另一种方式,直接在mapper接口上加@Mapper注解

@Mapper
public interface ArticleMapper {
}

这种方式太过零散不便于维护,现在一般不用了

3.2.4 加上mapper.xml文件

mapper.xml文件与mapper接口对应,相当于mapper接口的实现类,后面由mybatis读取调用。resource/mapper/testdb下加入ArticleMapper.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.zhoushiya.springbootstudy.testdb.mapper.ArticleMapper">
    <select id="getAll" resultType="com.zhoushiya.springbootstudy.testdb.entity.Article">
        SELECT * FROM testdb.article
    </select>
    <select id="getById" parameterType="long" resultType="com.zhoushiya.springbootstudy.testdb.entity.Article">
        SELECT * FROM testdb.article where id=#{id}
    </select>
    <insert id="insert" parameterType="com.zhoushiya.springbootstudy.testdb.entity.Article">
        insert into testdb.article(id,title,author,create_time) values (#{id},#{title},#{author},#{createTime})
    </insert>
</mapper>

注:mapper的详细写法这里不重复,可以去官方文档查看。这里说几个重要的部分:

  • mapper namespace必须和你要映射的mapper接口严格一致
  • id 必须和mapper接口的方法严格一致
  • resultType 必须和mapper接口方法返回值严格一致,如果返回是集合,只需要写集合内部类型即可
  • parameterType 必须和mapper接口方法参数严格一致
  • sql语句中#{xx},即代表参数中的实体的xx属性值
    这些地方如果出现问题,都不会在编译的时候出错,运行会出错,所以一定要仔细配置。

3.2.5 定义mybatis对mapper.xml文件的扫描路径

上面讲了mapper.xml文件的写法,mybatis对于mapper.xml文件扫描是有自动配置的,例如mapper接口在com.zhoushiya.springbootstudy.testdb.mapper下,那么同样目录下的xml文件是会自动扫描到的。但是本教程中的xml文件并不是和mapper接口在同一文件夹下,需要手动配置。application.yml中配置:

mybatis:
  mapper-locations: classpath:mapper/testdb/*.xml

注:因为编译后resources/mapper/testdb自动到了classes/mapper/testdb下,所以这里这样配置。多个路径配置,中间加入逗号即可

mybatis:
  mapper-locations: classpath:mapper/testdb/*.xml,classpath:mapper/testdb2/*.xml

3.2.6 加上vo,展示类

vo目录下加上ArticleVO,后面会交给controller层使用

package com.zhoushiya.springbootstudy.testdb.vo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * Article展示类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonPropertyOrder(value={"content","title"})
public class ArticleVO {

    @JsonIgnore
    private Long id;

    private String author;

    private String title;
    private String content;

    private Date createTime;
}

3.2.7 加上工具类DozerUtils

package com.zhoushiya.springbootstudy.utils;

import com.google.common.collect.Lists;
import org.dozer.DozerBeanMapperBuilder;
import org.dozer.Mapper;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class DozerUtils {

    static Mapper mapper = DozerBeanMapperBuilder.buildDefault();

    public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass){
        List destinationList = Lists.newArrayList();
        for (Iterator i$ = sourceList.iterator(); i$.hasNext();){
            Object sourceObject = i$.next();
            Object destinationObject = mapper.map(sourceObject, destinationClass);
            destinationList.add(destinationObject);
        }
        return destinationList;
    }
}

这个类的作用是,让List<A>通过DozerMapper自动转换到List<B>,后面会用到。

3.2.8 加上service.IArticleService,接口层

package com.zhoushiya.springbootstudy.testdb.service;

import com.zhoushiya.springbootstudy.testdb.vo.ArticleVO;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhoushiya
 * @since 2020-04-30
 */
public interface IArticleService{
    /**
     * 更新或者插入数据
     * @param articleVO
     * @return
     */
    ArticleVO insert(ArticleVO articleVO);

    ArticleVO getById(long id);

    List<ArticleVO> getAll();
}

3.2.9 加上IArticleService实现类service.impl.ArticleServiceImpl

package com.zhoushiya.springbootstudy.testdb.service.impl;

import com.zhoushiya.springbootstudy.testdb.entity.Article;
import com.zhoushiya.springbootstudy.testdb.mapper.ArticleMapper;
import com.zhoushiya.springbootstudy.testdb.service.IArticleService;
import com.zhoushiya.springbootstudy.testdb.vo.ArticleVO;
import com.zhoushiya.springbootstudy.utils.DozerUtils;
import org.dozer.Mapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 *  服务实现类,使用@Service(value = "articleService")让其作为IArticleService注入的默认类
 * </p>
 *
 * @author zhoushiya
 * @since 2020-04-30
 */
@Service(value = "articleService")
public class ArticleServiceImpl implements IArticleService {

    @Resource
    ArticleMapper articleMapper;

    @Resource
    private Mapper mapper;

    public ArticleVO insert(ArticleVO articleVO) {
        Article article= mapper.map(articleVO,Article.class);
        articleMapper.insert(article);
        return articleVO;
    }

    @Override
    public ArticleVO getById(long id) {
        Article article = articleMapper.getById(id);
        ArticleVO articleVO= mapper.map(article,ArticleVO.class);
        return articleVO;
    }

    @Override
    public List<ArticleVO> getAll() {
        List<Article> articles = articleMapper.getAll();
        List<ArticleVO> articleVOS= DozerUtils.mapList(articles,ArticleVO.class);
        return articleVOS;
    }
}

注:此处@Service(value = "articleService")不可省略,即当使用IArticleService自动注入的时候,默认使用这个实现类。如果此处省略了,那么需要在注入使用的地方需要指定实现类的名称,如:@Resource(name="articleServiceImpl")。下面会讲到。

3.2.10 加上config.Swagger2.java文件

package com.zhoushiya.springbootstudy.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;

@Configuration
@EnableSwagger2
public class Swagger2 {

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("springboot利用swagger构建api文档")
                .description("简单优雅的restfun风格")
                .termsOfServiceUrl("https://www.cnblogs.com/zhoushiya")
                .version("1.0")
                .build();
    }

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //扫描basePackage包下面的“/rest/”路径下的内容作为接口文档构建的目标
                .apis(RequestHandlerSelectors.basePackage("com.zhoushiya.springbootstudy"))
                .paths(PathSelectors.regex("/rest/.*"))
                .build();
    }


}

就是配置swagger2,让后面程序启动后可以通过swagger-ui来访问。
注:createRestApi方法中,swagger要扫描的包,paths中是要扫描的接口的路径。例如:com.zhoushiya.springbootstudy.ArticleController下有个方法getAll,其访问路径是localhost/rest/article,那么会被扫描出来并展示到swagger-ui网页中。反之,不是com.zhoushiya.springbootstudy包中,或者不符合 localhost/rest/ 路径的接口都不会出现。

3.2.11 加上controller.ArticleController.java文件

接口控制器,为前端提供访问

package com.zhoushiya.springbootstudy.testdb.controller;

import com.zhoushiya.springbootstudy.testdb.service.IArticleService;
import com.zhoushiya.springbootstudy.testdb.vo.ArticleVO;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/rest")
public class ArticleController {

    @Resource
    IArticleService articleService;

    /**
     * 增加一篇文章
     *
     * @param article
     * @return
     */
    @ApiOperation(value = "添加文章", notes = "添加新文章", httpMethod = "POST")
    @ApiResponses({
            @ApiResponse(code = 200, message = "成功", response = ArticleVO.class),
            @ApiResponse(code = 400, message = "用户输入错误", response = ArticleVO.class),
            @ApiResponse(code = 500, message = "系统内部错误", response = ArticleVO.class),
    })
    // @RequestMapping(value = "/article", method = RequestMethod.POST, produces = "application/json")
    @PostMapping("/article")
    public ArticleVO insertArticle(@RequestBody ArticleVO article) {
        articleService.insert(article);
        return article;
    }

    /**
     * 获取一篇Article,使用GET方法
     */
    // @RequestMapping(value = "/article/{id}", method = GET, produces = "application/json")
    @GetMapping("/article/{id}")
    public ArticleVO getById(@PathVariable Long id) {
        ArticleVO articleVO = articleService.getById(id);
        return articleVO;
    }

    @GetMapping("/article")
    public List<ArticleVO> getAll() {
        return articleService.getAll();
    }
}

注:

  • @RequestMapping注解,这里表示整个控制器接口路径都在/rest下面,方便swagger去查找
  • @Resource注解此处没有加name="",因为按照3.2.9此处默认会去找ArticleServiceImpl实现类。如果你在上面@Service省略了value="",那么这里就要加上name="articleServiceImpl"。如果两个地方都省略了,那么程序会报错,大意是注入的实现发现了两个,程序不知道用哪一个。
  • @Apixxx等注解都是Swagger提供的,目的在于前端界面展示详细的说明
  • @PostMapping,@GetMapping等都是@RequestMapping的简便写法,对应其中method=Post,Get等

3.2.12 加上单测:test/java/com/zhoushiya/springbootstudy/ArticleTest.java

package com.zhoushiya.springbootstudy;

import com.zhoushiya.springbootstudy.testdb.service.IArticleService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@SpringBootTest
@RunWith(SpringRunner.class)
public class ArticleTest {
    @Resource
    IArticleService articleService;

    @Test
    public void getAll(){
        articleService.getAll().forEach(System.out::println);
    }
}

3.2.13 配置连接字符串

spring:
  datasource:
    # mysql 6 以上为com.mysql.cj.jdbc.Driver
    # mysql 6 以下为com.mysql.jdbc.driver
    driver-class-name: com.mysql.cj.jdbc.Driver
    # mysql 8 要加上serverTimezone=Asia/Shanghai
    # 如果连接出现Public Key Retrieval is not allowed错误,还要加上allowPublicKeyRetrieval=true
    url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: welcome

3.2.14 去数据库创建article表

CREATE TABLE `article` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `author` varchar(32) NOT NULL,
  `content` varchar(512) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `title` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_571gx7oqo5xpmgocegaidlcu9` (`title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.2.15 随便在article表中添加一些数据

4.测试

  • 运行ArticleTest.getAll(),如果打印除了结果就表示配置成功了
  • 另外访问/swagger-ui.html页面也可以看到当前的接口。
    我的spring-boot-study之mybatis的应用
    然后可以执行getAll方法得到结果
    我的spring-boot-study之mybatis的应用
上一篇:最通俗易懂的Redis发布订阅及代码实战


下一篇:深入浅出ES6(八):Symbols