我的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 先按照下图结构将目录创建好
说明:
- 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页面也可以看到当前的接口。
然后可以执行getAll方法得到结果