MyBatis-Plus使用技巧 – 记忆角落

MyBatis-Plus使用技巧

/ 1评 / 0

最近在用 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/

一条回应:“MyBatis-Plus使用技巧”

  1. 速卖通刷单说道:

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

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注