我的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 先按照下图结构将目录创建好
说明:
- 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);
// @Param("id") 表示后面参数带入到Mapper.xml的detById方法中取代#{id}这个占位符
ArticleVO getById(@Param("id") 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页面也可以看到当前的接口。
然后可以执行getAll方法得到结果