【轻松学】Springboot整合Mybatis-PLus进行快速开发
程序员文章站
2022-03-26 17:06:13
...
在正式开始介绍Mybatis-Plus之前,先介绍一款相当方便的实体类自动getter/setter的插件
Lombok
,注意Lombok
插件现在针对IDEA2020.1.1
版本的话还是不兼容的,并且Mybatis-Plus搭配Lombok开发进行CRUD操作的话效率会更高。
首先我们导入坐标依赖
<dependencies>
<!-- springboot启动类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok插件 实体类插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MybatisPuls的包,这个是自己开发的 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<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>
</dependencies>
整体项目结果如图所示
首先我们要配置一个实体类User
package com.ysw.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/*
自增策略
*/
/*
这个id对应我们数据库的主键,是随机生成的唯一id(主键策略 - 雪花算法[分布式系统唯一id生成])
自增id
*/
/*
默认全局唯一id
@TableId(type = IdType.ID_WORKER)
*/
/*
* 需要配置主键自增
在实体类上配置 @TableId(type = IdType.ID_WORKER)
数据库字段一定是自增的
type = IdType.AUTO 数据库id自增
type = IdType.NONE 未设置主键,意思就是这个表没有主键
type = IdType.INPUT 手动输入,意思就是要我们自己输入一个属性值(自己配置)
type = IdType.WORKER 默认全局id
type = IdType.UUID 全局唯一id
type = IdType.ID_WORKER_STR id的字符串表示法
*/
//使用这个注解可以直接主键自增(我们的用户id自增),所以我们都不需要设置我们下一个ID的注解了
// * 推荐使用
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
/*
这里通过代码级别让它自动更新
字段添加填充内容
*/
//这里是直接在插入的时候自动执行,可以理解为插入的时候就设置好创建时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//在插入/更新的时候都自动更新,可以理解为更新的时候就更新一次时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
//乐观锁注解,版本号。然后去注册主键
@Version
private Integer version;
//逻辑删除:未删除是0,已删除是1
@TableLogic
private Integer deleted;
}
如果使用了逻辑删除的话我们需要在yml文件中配置逻辑删除的对应配置,其实逻辑删除就是当我们删除这个字段的时候,我们数据库有一个字段被自动修改了,当删除之后该条信息在数据库中依旧是存在的。但是由于这个数据库中deleted
字段值改为了1,所以我们要使用这条数据的话会发现被MP屏蔽掉了,但是这条数据依旧存在。这就是逻辑删除,yml配置如下:
# 这里我们用的是 MySQL 5.X 版本的
spring:
# 设置当前为开发环境
profiles:
active: dev
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 如果用的MySQL驱动是8版本的话,就要增加时区的配置
# serverTimeZone=GMT%2B8 东八区
# 配置我们的日志在控制台打印输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置逻辑删除
global-config:
db-config:
# 已经删除的值是1
logic-delete-value: 1
# 没有删除的值是0
logic-not-delete-value: 0
当我们定义好我们的实力类之后,我们就可以在Mapper中定义一个接口了,这个接口是继承自BaseMapper<User>
的。其实我们MP中所有的CRUD都是来自于这个BaseMapper的接口的。
/**
* @Repository 表示是持久层的
* 用户接口,所有的CRUD都已经编写完成了,不需要任何配置
*/
public interface UserMapper extends BaseMapper<User> {
//在对应的mapper上面继承基本的类 BaseMapper
}
如果我们想进行分页操作的话,还需要一个配置类进行配置,以及乐观锁的配置。注意,我在配置类中配置了一个MapperScan的标签,用于扫描我们的Mapper接口。我们也可以在Springboot启动类里面加上这个注解来扫描包。(小编发现当我们Mapper类有@Repository
注解的时候我们可以直接通过Autowired
来进行识别,如果Mapper类没有@Repository
的话可以通过@Resource
注解来进行识别)
/**
* 配置类
*/
//使用注解扫描mapper文件,一般我们都在配置类中扫描,这样不用在启动类中进行扫描
@MapperScan("com.ysw.mapper")
//开启自动管理事务
@EnableTransactionManagement
//设置当前类为配置类
@Configuration
public class MybatisPlusConfig {
//注册乐观锁插件,配置乐观锁插件完毕(使用的是拦截器)
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
//分页配置插件,然后就可以直接使用Page对象了
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
如果我们想实现自动填充功能(比如我们插入一条数据和更改一条数据的时候都会记录时间)
,就可以配置一个handler处理器
/**
* 自定义处理器:自动填充
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 创建数据的时候就执行一次更新(主要是设置更新的时间) - 插入时的填充策略
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill");
//创建的时候设置create_time字段的值,第一个值指定实体类的属性,第二个值指定数据,第三个值指定metaObject
this.setFieldValByName("createTime", new Date(), metaObject);
//创建的时候设置update_time字段的值,三个值同上
this.setFieldValByName("updateTime", new Date(), metaObject);
}
/**
* 更新数据的时候就执行一次更新(主要是更新最新的操作时间) - 更新时的更新策略
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill");
//更新的时候只更新update_time字段的值
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
链接好数据库之后,我们就可以尽情的CRUD啦!
package com.ysw;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ysw.entity.User;
import com.ysw.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
class MybatisPlusApplicationTests {
/**
* UserMapper继承了BaseMapper,所有的方法都来自父类,我们也可以编写自己的扩展方法
*
* Resource - 直接不需要在Mapper文件中使用 @Repository 注解
* Autowired - 需要使用 @Repository 注解,否则无法直接扫描到
*/
@Resource
private UserMapper userMapper;
/**
* 查询所有
*/
@Test
void contextLoads() {
//参数是一个Wrapper,条件构造器
//查询所有用户
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
/**
* 测试插入
*/
@Test
void insert(){
User user = new User();
//听说可以自动生成id,但是自测的时候id并不会自动生成
//数据库插入的id默认值为:全局的唯一id(也就是随机生成的一个id)
//user.setId(111L);
//这里注意,我们是无法直接提取出我们的ID的,这个是会在我们的数据库中直接自增的
user.setName("tuofengTest");
user.setAge(21);
user.setEmail("aaa@qq.com");
int count = userMapper.insert(user);
System.out.println(count);
}
/**
* 测试更新
*/
@Test
void update(){
// User user = new User();
// //通过条件自动拼接动态sql
// user.setId(115L);
// user.setName("世文AAAAAAi");
//
// userMapper.updateById(user);
//更新同时使用乐观锁
User user = userMapper.selectById(115L);
user.setName("test2222222");
//测试乐观锁版本
int count = userMapper.updateById(user);
System.out.println(count);
}
/*
所有的时间操作都是自动完成的
我们不希望手动进行更新
因此数据库表中一定有两个参数:gmt_create(创建的时间)、gmt_modify(修改的时间)
这两个字段是几乎所有的表都要配置上的,自动化的进行
1、数据库级别
1.1、在表中新增字段:create_time、update_time
1.2、创建时间指定,但是更新时间随时更新
2、代码级别 handler.MyMetaObjectHandler
2.1、删除数据库的默认值
2.2、实体类字段上增加注解
*/
/*
乐观锁:
顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不去上锁。
如果出现了问题就在测试加锁处理,再次更新测试
version、new version(版本号机制 -> ABA问题)
悲观锁:
它认为总是会出问题,无论干什么都会上锁,再去操作
*/
//测试乐观锁成功,单线程情况下进行修改(并且将版本号 + 1)
@Test
public void testHappyLock(){
//查询用户信息
User user = userMapper.selectById(1L);
//修改用户信息
user.setName("世文");
user.setEmail("aaa@qq.com");
//3、执行更新操作
userMapper.updateById(user);
}
//测试乐观锁失败
@Test
public void testHappyLock2(){
//线程1
User user1 = userMapper.selectById(1L);
user1.setName("世文1111");
user1.setEmail("aaa@qq.com");
//模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(1L);
user2.setName("世文2222");
user2.setEmail("aaa@qq.com");
//多线程情况下一定要记得加锁
//user2先更新了
userMapper.updateById(user2);
//user1被插队了,如果没有乐观锁就会覆盖插队线程的值(先执行的操作,反而被插队的操作替代了)
//这里可以使用自旋锁来多次提交
userMapper.updateById(user1);
}
/**
* 测试查询一个用户
*/
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
}
/**
* 查询一群数据(批量查询)
*/
@Test
public void testSelectByIds(){
//自动将数字转成集合
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
/**
* 条件查询之一,使用map操作
*/
@Test
public void testSelectByBatchIds(){
//使用hashmap自定义查询条件
HashMap<String, Object> hashMap = new HashMap<>();
//自定义查询,用map来进行多条件查询,相当于 name = jane
//hashMap.put("name","Jane");
hashMap.put("age","21");
//进行查询
List<User> users = userMapper.selectByMap(hashMap);
users.forEach(System.out::println);
}
/**
* 测试分页查询 - MP其实是内置了分页插件的
*/
@Test
public void testPage(){
//参数一: 当前页
//参数二: 页面大小
//这里导入mp的page对象,然后找到第一页的5数据,每页5个数据
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
//获取当前页数
long current = page.getCurrent();
//获取每一页的页数
long size = page.getSize();
//获取总页数
long total = page.getTotal();
//是否有下一页
boolean hasNext = page.hasNext();
//是否有上一页
boolean hasPrevious = page.hasPrevious();
System.out.println(current + " - " + size + " - " + total);
}
/**
* 删除操作
*/
public void testDeleteById(){
//根据id进行删除
//userMapper.deleteById();
//根据ids进行批量删除
//userMapper.deleteBatchIds();
//根据map条件进行批量删除(map指定之后都是批量删除的)
//userMapper.deleteByMap();
}
/**
* 逻辑删除 - 走的是更新操作,而不是删除操作
*/
@Test
public void deleteById(){
//更改了数据库的deleted字段
//但是在查询的时候会自动过滤被删除的字段(deleted = 1的被过滤字段)
userMapper.deleteById(1L);
}
}
以下案例都是使用Wrapper进行条件构造的
package com.ysw;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ysw.entity.User;
import com.ysw.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* Wrapper构造器
*/
@SpringBootTest
public class WrapperTest {
@Resource
private UserMapper userMapper;
/**
* 查询所有,测试wrapper(isNotNull、gt - greater than、ge - greater equals)
*/
@Test
void contextLoads() {
//定义查询器,这个和map其实是差不多的
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询名字这个数据库字段不为空的(这里写数据库字段)
wrapper
.isNotNull("create_time")
.isNotNull("email")
.gt("age",10) //年龄大于10
.ge("age",21); //年龄大于或等于21
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于21岁的
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
/**
* 查询名字
*/
@Test
void test2(){
//创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq("name", "世文");
userMapper.selectOne(queryWrapper);
}
/**
* 查询年龄在20 - 23之间用户(数量即可)
*/
@Test
void test3(){
//创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//between的边界值是包含进去的
queryWrapper
.between("age", 20, 23);
//查询人数
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
/**
* 模糊查询(不包含)
*/
@Test
void test5(){
//创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//左和右 意思是%是在左边还是右边,如果是左边的话就是 %世文 左边通配,右边就是 世文% 右边通配
//名字里面不包含世文的
queryWrapper
.notLike("name", "世文")
.likeLeft("age", "1"); //这里意思是年龄是1结尾的 %1
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
/**
* id在子查询中查出来,这个是一个in查询
*/
@Test
void test6(){
QueryWrapper<User> wrapper = new QueryWrapper();
//IN (select id from user where id < 3))
wrapper
.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
/**
* 排序
*/
@Test
void test7(){
QueryWrapper<User> wrapper = new QueryWrapper();
//通过id进行降序排序
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}