MyBatis-Plus使用技巧
本文最后更新于 1961 天前,其中的信息可能已经有所发展或是发生改变。

最近在用 Mybatis-Plus,嗯,是真他妈香!!!今天就来说说 Mybatis-Plus 的那些使用技巧

创建user表

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, 'glj1', 18, 'test1@199604.com'),
(2, 'glj1', 20, 'test2@199604.com'),
(3, 'glj1', 28, 'test3@199604.com'),
(4, 'glj1', 21, 'test4@199604.com'),
(5, 'glj1', 24, 'test5@199604.com');

注意:– 真实开发中往往都会有这四个字段,version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)

导入依赖

<!-- 数据库驱动 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发,并非官方的!-->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.4.2</version>
</dependency>

创建application.yml

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/txds?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
#    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

业务代码

实体类
package com.glj.securitydemo.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: GuoLiangJun
 * @Date: 2021/3/26
 * @Time: 17:26
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

mapper接口
package com.glj.securitydemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.glj.securitydemo.pojo.User;
import org.springframework.stereotype.Repository;

/**
 * @author jiomer
 */
@Repository
public interface UserMapper extends BaseMapper<User> {

}

注意点,我们需要在主启动类上去扫描我们的mapper包下的所有接口 @MapperScan(value = “com.glj.securitydemo.mapper”)

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SecuritydemoApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        List<User> userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
    }
}    

配置日志

application.yml文件添加日志配置:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Mybatis-plus的CRUD

插入操作
	@Test
    public void testInsertUser(){
        User user = new User();
        user.setName("glj1");
        user.setAge(24);
        user.setEmail("admin@199604.com");
        int result = userMapper.insert(user);
        System.out.println("result:"+result);
        System.out.println("user:"+user);
    }

看到id会自动填充。数据库插入的id的默认值为:全局的唯一id

主键生成策略

1)主键自增 1、实体类字段上 @TableId(type = IdType.AUTO)

@Tableid

  • 描述:主键注解
属性 类型 必须指定 默认值 描述
value String “” 主键字段名
type Enum IdType.NONE 主键类型

IdType

描述
AUTO 数据库ID自增
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert前自行set主键值
ASSIGN_ID 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)
ID_WORKER 分布式全局唯一ID 长整型类型(please use ASSIGN_ID)
UUID 32位UUID字符串(please use ASSIGN_UUID)
ID_WORKER_STR 分布式全局唯一ID 字符串类型(please use ASSIGN_ID)

具体注解到:https://mp.baomidou.com/guide/annotation.html#tablename

2、数据库id字段设置为自增!

3、再次测试(可以看到id值比上次插入的大1)id的生成策略源码解释

更新操作
    @Test
    public void testUpdateUser(){
        User user = new User();
        user.setName("111mybatis");
        user.setAge(24);
        user.setId(1L);

        int i  = userMapper.updateById(user);
        System.out.println("i:"+i);
    }
自动填充

创建时间、修改时间!这两个字段操作都是自动化完成的,我们不希望手动更新!阿里巴巴开发手册:所有的数据库表都要配置上gmt_create、gmt_modified!而且需要自动化!

1、在表中新增字段 gmt_create, gmt_modified

2、实体类字段属性上需要增加注解

    /**
     * 字段添加填充内容
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime gmtCreate;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime gmtModified;

3.编写处理器来处理这个注解

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // MP3.3.0版本之前的配置类
        //  this.setFieldValByName("gmtCreate",new Date(),metaObject);
        // this.setFieldValByName("gmtModified",new Date(),metaObject);
        // MP3.3之后的配置类
        this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // MP3.3.0版本之前的配置类
        // this.setFieldValByName("gmtModified", new Date(),metaObject);
        // MP3.3之后的配置类
        this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
    }
}
乐观锁

乐观锁 : 顾名思义,十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题, 再次更新值测试 悲观锁:顾名思义,十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!

乐观锁实现方式:

取出记录时,获取当前version 更新时,带上这个version 执行更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败

乐观锁测试:

1.给数据库中增加version字段

2.实体类加对应的字段

    @Version
    private Integer version;

3.配置类

@MapperScan("com.glj.securitydemo.mapper")
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {
    
    /**
     * 注册乐观锁插件
     * @return
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

4.测试

    @Test
    public void testOptimisticLocker(){
        // 1、查询用户信息
        User user = userMapper.selectById(1L);
        // 2、修改用户信息
        user.setName("test");
        user.setEmail("123456@qq.com");
        // 3、执行更新操作
        userMapper.updateById(user);
    }
查询操作
普通查询
    @Test
    public void testSelectById(){
        User user = userMapper.selectById(1);
        System.out.println("user:"+user);
    }
    // 测试批量查询!
    @Test
    public void testSelectByBatchId(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        users.forEach(System.out::println);
    }

    // 按条件查询之一使用map操作
    @Test
    public void testSelectByBatchIds(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","glj");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }
条件查询(QueryWrapper)

如果说,我们需要查询的 SQL 语句如下:

SELECT * FROM user WHERE age = 24;

那么对应的代码可以为:

    @Test
    public void testQueryWrapper1(){
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq("age",24);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }
条件查询(QueryWrapper lambda)
    @Test
    public void testQueryWrapper2(){
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.lambda().eq(User::getAge,24);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }
普通分页查询

1、配置拦截器组件

/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
	return new PaginationInterceptor();
}

2、直接使用Page对象

    @Test
    public void testSelectByPage(){
        // 分页对象
        Page<User> queryPage = new Page<>(0, 1);
        // 分页查询
        IPage<User> iPage = userMapper.selectPage(queryPage , null);
        // 数据总数
        long total = iPage.getTotal();
        // 集合数据
        List<User> list = iPage.getRecords();
        System.out.println("total:"+total);
        list.forEach(System.out::println);
    }
分页查询(联表)

当我们需要关联表格分页查询时,普通分页查询已经满足不了我们的需求了,那么我们需要进行联表分页查询

假设我们需要的 SQL 语句如下:

select a.*,b.name as sex_text from user a
left join user_sex b
on a.id = b.id
where a.age > 20

那么我们需要进行如下操作

1、新建 UserVO.java

@Data
public class UserVO extends User{
    private String sexText;
}

2.UserMapper.java 中新增:

IPage<UserVO> list(Page<UserVO> page, @Param(Constants.WRAPPER) Wrapper<UserVO> queryWrapper);

3.编写代码

    @Test
    public void testQueryWrapper3(){
        QueryWrapper<UserVO> queryWrapper = new QueryWrapper();
        queryWrapper.eq("age",24);
        // 分页对象
        Page<UserVO> queryPage = new Page<>(0, 10);
        // 分页查询
        IPage<UserVO> iPage = userMapper.list(queryPage , queryWrapper);
        // 数据总数
        Long total = iPage.getTotal();
        // 集合数据
        List<UserVO> list = iPage.getRecords();
        list.forEach(System.out::println);
    }

以上就是分页查询(联表)时的操作,这个应该是用的比较多的

AND 和 OR深入

queryWrapper 默认是按照 and 来连接的,但是在我们业务需求中,肯定会用到 or 来写 SQL

1.刚使用

假设我们需要的 SQL 语句如下:

SELECT
	a.* 
FROM
	user a 
WHERE
	1 = 1 
	AND a.id <> 1 
	AND ( a.name = 'glj_test' OR a.email = 'admin@199604.com' )

那么我们可以这样写

    @Test
    public void testQueryWrapper4(){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // AND a.id <> 1 
        queryWrapper.ne(User::getId,1);
        // AND ( a.`name` = 'glj_test' OR a.email = 'admin@199604.com' )
        queryWrapper.and(i -> i.eq(User::getName,"glj_test").or().eq(User::getEmail,"admin@199604.com"));
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

以上就完成了初级的 and 和 or 联用的情况

2.咱们开始进阶了~

假设我们需要的 SQL 语句如下:

SELECT
	a.* 
FROM
	user a 
WHERE
	1 = 1 
	AND a.id <> 1 
	AND ( (a.name = 'glj' AND a.age = 24) OR (a.email = 'admin@199604.com' or  a.age = 24))

那么我们可以这样写:

    @Test
    public void testQueryWrapper5(){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // AND a.id <> 1
        queryWrapper.ne(User::getId,1);

        queryWrapper.and(i -> (i.and(j -> j.eq(User::getName,"glj").eq(User::getAge,24)))
                .or(j -> j.eq(User::getEmail,"admin@199604.com").or().eq(User::getAge,24)));
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

以上就完成了复杂的 and 和 or 联用的情况

那么以后遇见更复杂的,可以按此规则继续进行拼接 SQL

具体查看官网API:https://mp.baomidou.com/guide/wrapper.html#abstractwrapper

 

指定查询字段(select)

当我们只需要查询表格中的某几个字段,如果表格数据很大的话,我们不应该查询表格的所有字段,假如,我们需要的 SQL 如下:

SELECT
	id,
	`name`,
	age 
FROM
	user 
WHERE
	1 = 1 
	AND age = 24

我们只需要查询年龄等于24的用户的 id、name、age,所以,可以这样写:

    @Test
    public void testQueryWrapper6(){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 只查询 id,name,age
        queryWrapper.select(User::getId, User::getName, User::getAge);
        // 查询条件为:age = 24
        queryWrapper.eq(User::getAge, 24);
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

 

删除操作
物理删除
	//测试删除
	@Test
    public void testDeleteById(){
        userMapper.deleteById(5L);
    }
    // 通过id批量删除
    @Test
    public void testDeleteBatchId(){
        userMapper.deleteBatchIds(Arrays.asList(1L,3L));
    }
    // 通过map删除
    @Test
    public void testDeleteMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","glj");
        userMapper.deleteByMap(map);
    }
逻辑删除

物理删除 :从数据库中直接移除

逻辑删除 :在数据库中没有被移除,而是通过一个变量来让它失效!

deleted = 0 => deleted = 1 管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

1、在数据表中增加一个 deleted 字段

2、实体类中增加属性

    @TableLogic
    private Integer deleted;

3、配置文件:

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)

4、测试删除

    @Test
    public void testDeleteById(){
        userMapper.deleteById(2L);
    }
    
------------------------日志
JDBC Connection [HikariProxyConnection@1551760389 wrapping com.mysql.cj.jdbc.ConnectionImpl@345d053b] will not be managed by Spring
==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 2(Long)
<==    Updates: 1

注意:自己在 xml 文件中写的 SQL 不会自动加上逻辑删除条件

最后更多技巧请参考官网:https://mp.baomidou.com/

评论

  1. Image
    Windows Firefox 56.0
    5 年前
    2021-6-18 14:34:08

    总结得太好了!大长见识!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Image
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇