文章目录
使用MyBatis-Plus能够带来的好处是什么呢?MyBatis-Plus和MyBatis是一个怎样的关系?
使用MyBatis-Plus好处是可以节省非常多代码量,少写SQL、还可以代码生成、从而加快我们在开发中的效率(拒绝加班、拒绝996)
MyBatis-Plus是对MyBatis框架进行了更强的封装,核心当然还是MyBatis。相当于在MyBatis的基础上新增了许多在实际开发中经常被使用到的功能
并且完全的简化了这些功能的实现代码,比如:分页、代码生成、乐观锁、逻辑删除等等...更好的帮助像我这种CRUD工程师进行高效率的项目开发
以上描述完全是个人理解,为了小伙伴们更好的学习MyBati-Plus可参考官方文档:https://baomidou.com
继续往下将带大家从Mybati-Plus的入门到常用的核心功能
1.mybatis-Plus快速入门体验
开发环境:SpringBoot 2.3.4+Mybatis-Plus 3.4.1+MySQL 8.0
注:不同的MyBatis-Plus版本可能在某些功能的配置上有所不同
- 创建数据库表并插入测试数据
<!--创建表-->
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');
- 新建一个SpringBoot项目并在pom.xml文件中导入所需的Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
- 在application.yaml文件中配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=Asia/Shanghai
username: root
password: libo
- 创建User实体类
@Data
/*全部参数的构造方法*/
@AllArgsConstructor
/*无参构造*/
@NoArgsConstructor
/*指定数据库表名*/
@TableName(value = "user")
/*实体类*/
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 创建userMapper接口并继承BaseMapper接口
别忘了在启动类上加入@MapperScan(value = “com.mybatiPlus.mapper”) //扫描mapper文件
@Repository
/*Mapper接口*/
public interface userMapper extends BaseMapper<User> {
}
- 使用MyBatis-Plus操作数据(这里就测试下查询user表的所有数据)
@SpringBootTest
/*测试类*/
public class mybatisPlusTest {
/*注入持久层userMapper接口*/
@Autowired
private userMapper userMapper;
/*查询user表中所有数据*/
@Test
public void seleteByList() {
/*Wrapper是一个条件构造器,这里暂且先不适用*/
List<User> userList = userMapper.selectList(null);
/*打印List*/
userList.forEach(System.out::println);
}
控制台打印的结果:
的确是把user表中的所有数据查询出来了,然而我们并没有编写SQL语句对吧,这就是MyBatis-Plus的强大之处。
这里就有两个问题:1.userMapper下的方法哪里来的?2.SQL是谁写的?
方法来自BaseMapper,因为我们继承了他并且传入了泛型。SQL语句当然是MyBatis-Plus中封装的啦,这只是MyBatis-Plus强大功能中的其中之一
2.配置日志输出
- 在application.yaml中加入
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 再次测试查询所有方法,然后观察控制台与之前多了哪些东西?
3.主键生成策略
什么是主键生成策略呢?在数据库中一张表的主键一般就是我们的id字段,但是主键字段的值就有很多种了。
比如常见的id自增,或者说是insert into插入数据的时候手动填入,那在MyBatis-Plus中又给我们带来哪些不同的生成策略呢?
- 插入一条数据到user表
/*添加数据*/
@Test
public void insert() {
User user = new User();
/*名字*/
user.setName("周杰伦");
/*年龄*/
user.setAge(18);
/*email*/
user.setEmail("abc@163.com");
/*添加数据*/
userMapper.insert(user);
}
- 查看日志输出,看MyBabtis-Plus默认给我们生成的id是什么?
- 查看数据库
问题:为什么会生成这么长一串id呢?或者说这个id是根据什么生成的呢?下面就介绍MyBatis的主键生成策略
MyBatis-Plus中不同的主键生成策略:
1. IdType.AUTO:数据库自增id(数据库表也必须设置为自增id)
2. IdType.NONE:该类型为未设置主键类型(和下面这个差不多)
3. IdType.INPUT:用户输入ID(自定义id)
4. IdType.ASSIGN_UUID:生成全局唯一UUID 注:主键字段为字符串
5. IdType.ID_WORKER:默认的全局唯一id (雪花算法) MyBatis-Plus默认策略
6. IdType.ID_WORKER_STR:是ID_WORKER的字符串表示法 (雪花算法)
注:IdType是一个枚举类
所以我们刚刚插入的数据生成的id就是使用MyBatis-Plus默认的生成策略IdType.ID_WORKER,其中采用了雪花算法。
主键生成策略的详细介绍可参考:分布式系统主键id生成策略
如果想要使用不同的策略需要在实体类的主键字段上加上注解 @TableId(type = IdType.ASSIGN_UUID)
比如我们再测试一个自动生成的UUID,因为UUID包含字母那就要把主键id字段改为String类型,数据库主键字段类型为varchar
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private Integer age;
private String email;
再次执行我们之前的添加数据方法,查看结果
记得将实体类主键类型和数据库表主键字段类型改回来,上面只做一个测试。
4.单表删除操作
/*根据id删除数据*/
@Test
public void delete() {
/*根据id删除*/
userMapper.deleteById(1342413749983252482L);
}
另外再补充两个不同的删除方式:
根据id批量删除
/*根据id批量删除*/
@Test
public void deleteByList() {
/*批量删除id为1、2的数据*/
List<Long> longs = Arrays.asList(1L, 2L);
userMapper.deleteBatchIds(longs);
}
根据Map条件删除
/*根据Map条件删除*/
@Test
public void deleteByMap() {
Map<String, Object> map = new HashMap();
/*删除name为周杰伦的数据*/
map.put("name", "周杰伦");
userMapper.deleteByMap(map);
}
5.单表逻辑删除
我们之前删除数据是直接从数据库表中删除的,删除之后表里面这条数据就没有了,对吧
那么逻辑删除就是:要删除的数据并不是真正的从表中删除,只是在查询时不展示这条数据即可。
例如就相当于回收站:被删除的数据在回收站,但是回收站并没有清空,我们只是判断它没有作用了,放到一边而已
如何实现:
- 数据库表新增一个字段并且给一个默认值
假设我们设置逻辑删除字段的值默认为0
- 实体类增加逻辑删除字段配上注解
加上注解:@TableLogic
@Data
/*全部参数的构造方法*/
@AllArgsConstructor
/*无参构造*/
@NoArgsConstructor
/*指定数据库表名*/
@TableName(value = "user")
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;
/*逻辑删除*/
@TableLogic
private Integer delete;
}
- 在application.yaml文件中配置MyBatisPlus逻辑删除组件
mybatis-plus:
#逻辑删除
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 配置完成之后让我们来测试一个删除方法
/*根据id删除数据*/
@Test
public void delete() {
/*根据id删除*/
userMapper.deleteById(3L);
}
逻辑删除其实走的是update方法,把逻辑删除字段的值给修改了,我们设置的是未删除时字段默认为0,逻辑删除后字段为1
然后在查询时加上条件deleted = 0,所以在查询时deleted字段为1的值我们并没有查询出来,只查询了值为0的数据
6.单表更新操作
现在想把id为1的数据中name改为刘德华,看看应该如何操作?
/*更新数据*/
@Test
public void update() {
User user = new User();
/*id*/
user.setId(1L);
/*修改名字*/
user.setName("刘德华");
/*根据id修改*/
userMapper.updateById(user);
}
查看日志执行的SQL:
查看数据库表:
上面只是修改了一个name字段,如果修改多个字段呢?
/*更新数据*/
@Test
public void update() {
User user = new User();
/*id*/
user.setId(1L);
/*修改名字*/
user.setName("刘德华");
/*修改年龄*/
user.setAge(20);
/*修改邮箱*/
user.setEmail("123@qq.com");
/*根据id修改*/
userMapper.updateById(user);
}
注意看执行的SQL语句,和上面修改一个字段相比较,相当于在修改数据时MyBatis-Plus会做一个实体类字段的的非空验证
实体类中不为空的字段就视为要修改的属性。
7.单表查询操作
/*查询user表中所有数据*/
@Test
public void seleteByTest() {
/*Wrapper是一个条件构造器,这里暂且先不使用*/
List<User> userList = userMapper.selectList(null);
/*打印List*/
userList.forEach(System.out::println);
}
/*根据id查询一个*/
@Test
public void seleteByid() {
/*查询id为5的数据*/
User user = userMapper.selectById(5);
/*输出*/
System.out.println(user);
}
/*根据id批量查询*/
@Test
public void selectBatchIds() {
List<Integer> list = Arrays.asList(3, 4, 5);
/*查询多个id*/
List<User> users = userMapper.selectBatchIds(list);
/*输出所有*/
users.forEach(System.out::println);
}
/*条件查询*/
@Test
public void selectByMap() {
Map<String, Object> map = new HashMap();
/*查询name为周杰伦的数据*/
map.put("name", "周杰伦");
List<User> users = userMapper.selectByMap(map);
/*输出所有*/
users.forEach(System.out::println);
}
8.分页查询
一般分页的几种方式:
- 自己创建分页类,然后limit查询进行分页
- PageHelper插件分页
MyBatis-Plus这么强大当然也提供了分页插件,那我们就看看在MyBatis-Plus中如何使用分页?
首先在SpringBoot中创建一个配置类,然后添加方法注册一个Bean对象。这个配置类还会配置其他关于MyBatis-Plus的插件
/*配置类*/
@Component
public class myBatisPlusConfig {
/*MyBatis-Plus分页插件*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
在测试类中新建一个方法进行对分页的测试:
注:Page 对象中有很多方法,比如:数据总数,总页数等等。
/*分页查询*/
@Test
public void testPage() {
/*参数一:多少页(页码) 参数二:多少条数据(页面大小)*/
Page page = new Page(1, 1);
page = userMapper.selectPage(page, null);
/*输出数据*/
page.getRecords().forEach(System.out::println);
}
9.自动填充功能
在开发中一张表起码有这样的两个字段吧,一个是数据的插入时间,还有一个就是数据的修改时间。
所谓自动填充就是数据在插入、修改的时候给具体的字段设置值。例如设置当前时间
其实这种实现有两种方式:
注:不管采用什么样的方式实现都必须有这两个字段
- 数据库级别(不推荐使用)
数据的创建时间字段默认一栏写入CURRENT_TIMESTAMP,该字段就会在新增数据时自动填入当前时间(创建数据时填入该字段)
只需要勾选根据当前时间戳更新,该数据在修改时,updateTime字段就会自动填入当前时间(修改数据时更新该字段)
- 代码级别
如果使用第一种方法,我们在开发中不一定能有权限去修改数据库,非专业数据库工程师去操作也不太好
MyBatis-Plus给我们提供了代码级别的填充。
- 可以先看下这个枚举类:
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
- 在实体类种创建两个字段:
/*创建时间*/
@TableField(fill = FieldFill.INSERT)
private Date cteateTime;
/*修改时间*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
- 创建一个类来继承 MetaObjectHandler,该类作为MyBatis-Plus的自动填充处理类:
@Slf4j
@Component
/*自动填充处理类*/
public class MyMetaObjectHandler implements MetaObjectHandler {
/*在插入数据时填充时间,两个字段都填充cteateTime和updateTime*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("cteateTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
/*在插入数据时填充时间,只填充updateTime*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
- 至此在数据的插入时cteateTime和updateTime两个字段都会被填充,在数据修改时updateTime字段会被填充。
10.乐观锁
乐观锁的实现方式:这里需要多增加一个字段,这里取名为version(版本)
- 先查询出当前数据的version字段
- 修改时带上这个version字段
- 执行修改的SQL语句时:set version = 新version where version = 旧version
- 如果第一次取出的version和修改时的version不同,那么就修改失败。修改成功那么就在旧version的值上+1
其实这里就是线程并发的问题:
那我们假设现在有AB两个线程,两个线程都要去执行修改同一条数据的操作,并且该数据的version字段为0,A线程执行时去查询值为0,B线程执行时查询值也为0。但是A线程先执行,那么version字段+1,那么现在version已经为1了,线程B去执行修改时发现 两次的version不一样,查询时为0,执行时为1,那么B线程就修改失败
看看MyBatis-Plus如何实现乐观锁:
数据库表和实体类都要新增一个version字段,并且在实体类字段上加上@Version注解,数据库字段给一个默认值
@Version
private Integer version;
在SpringBoot的代码中注入一个Bean对象
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
11.wrapper 条件构造器
上面的SQL案例都是些比较简单的数据库操作,但是如何有很复杂的条件查询应该在MyBatis-Plus中如何使用?
这里就需要用到wrapper条件构造器,下面给大家举几个复杂条件的例子,wrapper 的功能远不止这些,参考官方文档进行编写
/*wrapper构造器*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
public class wrapperTest {
/*注入持久层userMapper接口*/
@Autowired
private userMapper userMapper;
/*查询name和email不为null并且age大于等于18的数据*/
@Test
public void test(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",18);
userMapper.selectList(wrapper).forEach(System.out::println);
}
/*查询name等于周杰伦的数据*/
@Test
public void test2(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.eq("name","周杰伦");
userMapper.selectList(wrapper).forEach(System.out::println);
}
/*查询age在20到30之间的数据*/
@Test
public void test3(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.between("age",20,30);//区间
Integer integer = userMapper.selectCount(wrapper);//查询结果数
System.out.println(integer);
}
/*模糊查询*/
@Test
public void test4(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.
notLike("name","李")//not like
.likeLeft("email","123");//%123
List<Map<String, Object>> map = userMapper.selectMaps(wrapper);
map.forEach(System.out::println);
}
/*子查询*/
@Test
public void test5(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.inSql("id","select id from user where id < 5");
List<Object> list = userMapper.selectObjs(wrapper);
list.forEach(System.out::println);
}
/*根据id倒叙*/
@Test
public void test6(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}