先来看看官网对mybatis-puls的介绍
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
看到这里我们大概明白了mp就是一个强化版的mybatis框架,而且是国人苞米团队开发,官方文档是用中文写的,看起来可以说是超级舒服了。官网上也有很多例子,推荐大家多多参看官网学习。
mp的特性里面说到内置了通用的mapper、提供了分页插件及对lambada表达式的支持。今天的目标就是体验一下这些特性。
项目准备
先创建好一个springboot项目,然后我们导入一下会使用到的jar依赖
<!--连接数据库需要的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mp需要的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--swagger依赖,个人比较喜欢拿这个来做测试 如果用postman做测试则此依赖和后续swagger相关设置都可不设置-->
<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>
<optional>true</optional>
</dependency>
准备一下数据库
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
mp也提供了强大的代码生成工具,具体配置方式可参考我写的mp代码自动生成
因为现在是初学,所以我又自己手动创建了一遍想看看mp和mybatis的不同在哪里,先贴出最后的目录结构(util包中的是mp的自动代码生成工具,不用管)
新建config包,在此包下新建MybatisPlusConfig类,此类用来对mp进行配置
//Spring boot方式
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
在config包下新建SwaggerConfig类,这个类主要用来配置swagger
最需要注意的地方是下面这段代码中的包需要设置成自己的controller包路径(如果用postman测试不用swagger的话就不用设置了)
.apis(RequestHandlerSelectors.basePackage("com.thz.mybatis_plus_demo.controller"))
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.pathMapping("/")
.select()
.apis(RequestHandlerSelectors.basePackage("com.thz.mybatis_plus_demo.controller"))
.paths(PathSelectors.any())
.build().apiInfo(new ApiInfoBuilder()
.title("SpringBoot整合Swagger")
.description("SpringBoot整合Swagger,详细信息......")
.version("9.0")
.contact(new Contact("我的CSDN","https://blog.csdn.net/m0_49558851","qwer@gmail.com"))
.license("The Apache License")
.licenseUrl("http://www.baidu.com")
.build());
}
}
配置文件application.yml设置一下数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 151415
新建entity包,在此包下新建实体类User
@Data @EqualsAndHashCode @Accessors三个注解是lombok的
@Data注解设置构造器,get,set方法由lombok帮我们生成。
@EqualsAndHashCode注解设置equals方法和hashCode方法,可参考Lombok 的 @EqualsAndHashCode(callSuper = false) 的使用
@Accessors注解设置get、set方法的生成方式,可参考lombok @Accessors用法
@ApiModel(“user实体”)为swagger注解
@ApiModelProperty为swagger注解
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel("user实体")
public class User implements Serializable {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@ApiModelProperty(value = "id")
private Long id;
/**
* 姓名
*/
@ApiModelProperty(value = "姓名")
private String name;
/**
* 年龄
*/
@ApiModelProperty(value = "年龄")
private Integer age;
/**
* 邮箱
*/
@ApiModelProperty(value = "邮箱")
private String email;
}
新建mapper包,在此包下新建接口UserMapper继承BaseMapper
public interface UserMapper extends BaseMapper<User> {
}
这里继承了BaseMapper时我们将泛型设置为了我们的实体User,这就是mp特性中说到的通用mapper,mp在BaseMapper中写好了通用的CRUD,我们只需要继承并传递相关泛型就可以使用这些mp的通用CRUD,连xml都不用写。
不过我们还是需要将xml文件创建出来的,所以在mapper包下新建xml包,在此包下新建UserMapper.xml 然后填写一下命名空间namespace就行了
<?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.thz.mybatis_plus_demo.mapper.UserMapper">
</mapper>
在启动类中添加注解扫描的mapper位置(这里扫描的是UserMapper这个类,不是xml文件)
@SpringBootApplication
@MapperScan("com.thz.mybatis_plus_demo.mapper")
public class MybatisPlusDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusDemoApplication.class, args);
}
}
新建service包,在此包下新建UserService接口继承IService接口
public interface UserService extends IService<User> {
}
既然mapper都有通用的,那么service肯定是同理了,我们继承一下mp的IService后面再controller中就能使用mp写好的通用CRUD方法了
在service包下新建impl包,在此包下编写UserService的实现类UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
}
同理需要继承mp的ServiceImpl,不然只继承接口中的方法,不继承实现类那还不是得自己动手编写接口方法的实现。
至于下面的自动注入userMapper其实没有也可以,但是做项目的时候肯定会碰到一些复杂的逻辑需要自己编写一些联表查询的sql,这时候在UserMapper 接口中新增新的方法,然后在xml中写对应的SQL语句,这个时候在service中用自动注入的userMapper就能调用自己写的sql了。(毕竟是对mybatis的增强,这个地方还是和mybatis一样的,该怎么来就怎么来)
基本CRUD
到这里准备工作就差不多了,接下来让我们来见证奇迹,嘿嘿
新建controller包,在此包下新建类UserController
@RestController
@RequestMapping("/user")
@Api(tags = "用户管理相关接口")
public class UserController {
@Resource
private UserService userService;
@PostMapping
@ApiOperation("新增")
@Transactional(rollbackFor = Exception.class)
public void save(@RequestBody User user){
userService.save(user);
}
@DeleteMapping("{id}")
@ApiOperation("删除")
public void delete(@PathVariable("id") String id){
userService.removeById(id);
}
@PutMapping()
@ApiOperation("修改")
public void update(@RequestBody User user){
System.out.println("123");
userService.updateById(user);
}
}
啥都不说了,先项目跑起来用swagger测试一波看看,访问 http://localhost:8080/swagger-ui.html#/
先试试新增
填写好传入的json数据,点击执行
{
"age": 21,
"email": "123456789@163.com",
"name": "南城"
}
可以看到返回200,说明成功了
我们再去数据库看一下,有数据,说明成功了。
删改的测试这里就不详述了,需要注意的是删改的时候id是必传值。改的时候在json中没写的的属性默认为null传入数据库。
查询和分页放在一起说
分页
普通分页查询
在controller中新增方法
@GetMapping("/Page")
@ApiOperation("分页查询")
@ApiImplicitParams({@ApiImplicitParam(name = "pageNo",value = "当前页",dataType = "Integer",defaultValue = "1"),
@ApiImplicitParam(name = "pageSize",value = "每页条数",dataType = "Integer",defaultValue = "2")})
public IPage<User> findByPage(Integer pageNo , Integer pageSize) {
Page<User> page = new Page<>(pageNo,pageSize);
return userService.PageList(page);
}
使用分页需要进行特别的配置,前端传递过来请求页数及每页条数后,我们用这两个参数 new一个Page< User >对象(这个参数后面会被传递到service中当做selectPage方法的一个参数执行查询,这个selectPage也是继承的BaseMapper的方法)
这里说明一下返回值IPage是mp分页插件提供的一种数据类型,包含了分页信息以及当前页的具体数据json数组,具体数据类型可以通过泛型指定。
这里就指定了IPage< User >,其返回类型如下(records中就是当前页的用户信息数组)
{
"current": 0,
"pages": 0,
"records": [
{
"age": 0,
"email": "string",
"id": 0,
"name": "string"
}
],
"searchCount": true,
"size": 0,
"total": 0
}
UserService修改如下
public interface UserService extends IService<User> {
public IPage<User> PageList(Page<User> page);
}
实现类UserServiceImpl修改如下
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 分页查询
* @param page
* @return
*/
public IPage<User> PageList(Page<User> page){
return userMapper.selectPage(page, null);
}
}
selectPage方法是UserMapper继承自BaseMapper的方法,第一个参数是分页相关参数,第二个参数是查询条件。查询条件后面会说到,这里不需要使用查询条件,所以先写null。
重启项目使用swagger测试,可以看到分页成功了。
条件分页查询
接下来说一说条件分页查询,上面已经说到了QueryWrapper方法的第二个参数传的就是查询条件,对于这个查询条件mp也封装了一个强大的条件构造器叫QueryWrapper,这个QueryWrapper可以帮助我们在java代码中通过简单的方法调用设置一些查询条件,最后将其变成sql对象中where后面的语句。
呢么我们现在需要做的就是new一个QueryWrapper对象,然后调用这个对象的一些方法设置一下查询条件,然后将这个对象设置成QueryWrapper的第二个参数就实现了 条件分页查询。
接下来看看具体的代码如何编写再说说QueryWrapper的一些设置查询条件的方法是啥,怎么调用。
在controller中新增如下方法
@PostMapping("/PageByAll")
@ApiOperation("条件分页查询")
@ApiImplicitParams({@ApiImplicitParam(name = "pageNo",value = "当前页",dataType = "Integer",defaultValue = "1"),
@ApiImplicitParam(name = "pageSize",value = "每页条数",dataType = "Integer",defaultValue = "2")})
public IPage<User> findByPage(@RequestBody User user, Integer pageNo , Integer pageSize) {
Page<User> page = new Page<>(pageNo,pageSize);
return userService.PageByAll(user,page);
}
UserService接口修改如下
public interface UserService extends IService<User> {
public IPage<User> PageList(Page<User> page);
public IPage<User> PageByAll(User user, Page<User> page);
}
在实现类中实现新增的方法PageByAll
@Override
public IPage<User> PageByAll(User user, Page<User> page) {
QueryWrapper queryWrapper = new QueryWrapper();
if (user.getName() != null && !"".equals(user.getName())){
queryWrapper.eq("name",user.getName());
}else if (user.getEmail() != null && !"".equals(user.getEmail())){
queryWrapper.eq("email",user.getEmail());
}else if (user.getAge() != null && user.getAge() != 0){
queryWrapper.eq("age",user.getAge());
}
return userMapper.selectPage(page,queryWrapper);
}
可以看到,在判断用户传过来的条件中如果不为空。我们调用了eq方法,第一个参数是数据库字段名,第二个参数就是比较值。
所以queryWrapper.eq(“name”,user.getName());这段代码最后变成的sql语句就是where name = ‘user对象中的name属性值’ ,而且构造器queryWrapper会只能的帮助我们添加多个条件间的and。此外除了等于,带有大于、小于、不等于等等方法如下表所示
(该图来自博客(五)springboot + mybatis plus强大的条件构造器queryWrapper、updateWrapper)
Lambda支持
通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
就是通过lambda的形式给条件构造器的第一个参数赋值,不用我们自己写字段名字,避免了一些不必要的错误,直接看代码,一看就懂。
注意使用Lambda时,条件构造器变了个名字,叫LambdaQueryWrapper<>
@Override
public IPage<User> PageByAll(User user, Page<User> page) {
// QueryWrapper queryWrapper = new QueryWrapper();
// if (user.getName() != null && !"".equals(user.getName())){
// queryWrapper.eq("name",user.getName());
// }else if (user.getEmail() != null && !"".equals(user.getEmail())){
// queryWrapper.eq("email",user.getEmail());
// }else if (user.getAge() != null && user.getAge() != 0){
// queryWrapper.eq("age",user.getAge());
// }
// return userMapper.selectPage(page,queryWrapper);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>();
if (user.getName() != null && !"".equals(user.getName())){
queryWrapper.eq(User::getName,user.getName());
}else if (user.getEmail() != null && !"".equals(user.getEmail())){
queryWrapper.eq(User::getEmail,user.getEmail());
}else if (user.getAge() != null && user.getAge() != 0){
queryWrapper.eq(User::getAge,user.getAge());
}
return userMapper.selectPage(page,queryWrapper);
}