Mybatis-plus复习


Mybatis-plus简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

润物无声

只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。

效率至上

只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。

丰富功能

热加载、代码生成、分页、性能分析等功能一应俱全。

依赖

注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。

同时我们还需要安装Lombok插件。

<dependencies>
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

配置

在 application.properties /yml配置文件中添加 MySQL 数据库的相关配置:

我的MySQL数据库版本是8 ,所以需要配置jdbc8驱动

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

yml格式

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC
      username: root
      password: 123456

启动类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

@SpringBootApplication
 @MapperScan("com.zenghao.demomptest.mapper")
 public class DemomptestApplication {

   public static void main(String[] args) {
     SpringApplication.run(DemomptestApplication.class, args);
   }

 }

添加实体

创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)

@Data
 public class User {
   private Long id
   private String name
   private Integer age
   private String email
 }

添加mapper

创建包 mapper 编写Mapper 接口: UserMapper.java

@Repository
 public interface UserMapper extends BaseMapper<User> {
 }

测试

添加测试类,进行功能测试:

@SpringBootTest
 class DemomptestApplicationTests {

   @Autowired
   private UserMapper userMapper;

   @Test
   public void findAll() {
     List<User> users = userMapper.selectList(null);
     System.out.println(users);
   }
 }

注意:

IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。

为了避免报错,可以在 dao 层 的接口上添加 @Repository 注

通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!

查看控制台输出:

image-20220622151614995

查看sql输出日志

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

主键策略

插入操作

*//添加
\ @Test
 public void testAdd() {
   User user = new User();
   user.setName("lucy");
   user.setAge(20);
   user.setEmail("1243@qq.com");
   int insert = userMapper.insert(user);
   System.out.println(insert);
 }

注意:数据库插入id值默认为:全局唯一id

image-20220622152030198

MP的主键策略

ASSIGN_ID

MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)

@TableId(type = IdType.ASSIGN_ID)
private String id;

雪花算法:分布式ID生成器

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。

核心思想:

长度共64bit(一个long型)。

首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。

41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。

10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。

12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

image-20220622152203787

优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。

AUTO 自增策略

需要在创建数据表的时候设置主键自增

实体字段中配置 @TableId(type = IdType.AUTO)

@TableId(type = IdType.AUTO)
private Long id;

要想影响所有实体的配置,可以设置全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

自动填充和乐观锁

更新操作

注意:update时生成的sql自动是动态sql:UPDATE user SET age=? WHERE id=?

*//修改
 @Test
 public void testUpdate() {
   User user = new User();
   user.setId(1340868235401764865L);
   user.setName("lucymary");
   int count = userMapper.updateById(user);
   System.out.println(count);
 }

自动填充

需求描述:

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作

数据库修改

在User表中添加datetime类型的新的字段 create_time、update_time

实体类修改

实体上增加字段并添加自动填充注解

@TableField(fill = FieldFill.INSERT\)
 private Date createTime; //create_time

@TableField(fill = FieldFill.INSERT_UPDATE\)
 private Date updateTime; *//update_time*

实现元对象处理器接口

注意:不要忘记添加 @Component 注解

@Component
 public class MyMetaObjectHandler implements MetaObjectHandler {

   //mp执行添加操作,这个方法执行
   @Override
   public void insertFill(MetaObject metaObject) {
     this.setFieldValByName("createTime",new Date(),metaObject);
     this.setFieldValByName("updateTime",new Date(),metaObject);
   }

   //mp执行修改操作,这个方法执行
   @Override
   public void updateFill(MetaObject metaObject) {
     this.setFieldValByName("updateTime",new Date(),metaObject);
   }
 }

乐观锁

场景

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

乐观锁实现方式:

取出记录时,获取当前version

更新时,带上这个version

执行更新时, set version = newVersion where version = oldVersion

如果version不对,就更新失败

接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:

乐观锁实现流程
修改实体类

添加 @Version 注解

@Version
private Integer version;
创建配置文件

创建包config,创建文件MybatisPlusConfig.java

此时可以删除主类中的 @MapperScan 扫描注解

@Configuration
 @MapperScan("com.zenghao.demomptest.mapper")
 public class MpConfig {
   */**
   \* 乐观锁插件
   \*/
   @Bean
   public OptimisticLockerInterceptor optimisticLockerInterceptor() {
     return new OptimisticLockerInterceptor();
   }
 }
注册乐观锁插件

在 MybatisPlusConfig 中注册 Bean

/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

查询

通过多个id批量查询

完成了动态sql的foreach的功能

*//多个id批量查询
\@Test
 public void testSelect1() {
   List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
   System.out.println(users);
 }

简单的条件查询

通过map封装查询条件

注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id

*//简单条件查询
 @Test
 public void testSelect2() {
   Map<String, Object> columnMap = new HashMap<>();
   columnMap.put("name","Jack");
   columnMap.put("age",20);
   List<User> users = userMapper.selectByMap(columnMap);
   System.out.println(users);
 }

分页

分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

添加分页插件

配置类中添加@Bean配置

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

测试selectPage分页

测试:最终通过page对象获取相关数据

*//分页查询
 @Test
 public void testSelectPage() {
   Page<User> page = new Page(1,3);
   Page<User> userPage = userMapper.selectPage(page, null);
   //返回对象得到分页所有数据
   long pages = userPage.getPages(); *//总页数
   long current = userPage.getCurrent(); *//当前页
   List<User> records = userPage.getRecords(); *//查询数据集合
   long total = userPage.getTotal(); *//总记录数
   boolean hasNext = userPage.hasNext(); *//下一页
   boolean hasPrevious = userPage.hasPrevious(); *//上一页

   System.out.println(pages);
   System.out.println(current);
   System.out.println(records);
   System.out.println(total);
   System.out.println(hasNext);
   System.out.println(hasPrevious);
 }

测试selectMapsPage分页

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值

测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {
//Page不需要泛型
Page<Map<String, Object>> page = newPage<>(1, 5);
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());

}

删除与逻辑删除

删除

根据id删除记录
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(5L);
system.out.println(result);
}
批量删除
@Test
public void testDeleteBatchIds() {
    int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
system.out.println(result);
}
简单条件删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Helen");
map.put("age", 18);
    int result = userMapper.deleteByMap(map);
system.out.println(result);}

逻辑删除

物理删除和逻辑删除

物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据

逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

逻辑删除的使用场景:

可以进行数据恢复

有关联数据,不便删除

逻辑删除实现流程
数据库修改

添加 deleted字段

ALTERTABLE `user` ADD COLUMN `deleted` boolean DEFAULT false
实体类修改

添加deleted 字段,并加上 @TableLogic 注解

@TableLogic private Integer deleted;
配置(可选)

application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无

mybatis-plus.global-config.db-config.logic-delete-value=1 
mybatis-plus.global-config.db-config.logic-not-delete-value=0

条件构造器和常用接口

wapper介绍

image-20220622154557754

Wrapper : 条件构造抽象类,最顶端父类

AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

​ QueryWrapper : 查询条件封装

​ UpdateWrapper : Update 条件封装

AbstractLambdaWrapper : 使用Lambda 语法

LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper

LambdaUpdateWrapper : Lambda 更新封装Wrapper

查询方式:

查询方式 说明
setSqlSelect 设置 SELECT 查询字段
where WHERE 语句,拼接 + WHERE 条件
and AND 语句,拼接 + AND 字段=值
andNew AND 语句,拼接 + AND (字段=值)
or OR 语句,拼接 + OR 字段=值
orNew OR 语句,拼接 + OR (字段=值)
eq 等于=
allEq 基于 map 内容等于=
ne 不等于<>
gt 大于>
ge 大于等于>=
lt 小于<
le 小于等于<=
like 模糊查询 LIKE
notLike 模糊查询 NOT LIKE
in IN 查询
notIn NOT IN 查询
isNull NULL 值查询
isNotNull IS NOT NULL
groupBy 分组 GROUP BY
having HAVING 关键词
orderBy 排序 ORDER BY
orderAsc ASC 排序 ORDER BY
orderDesc DESC 排序 ORDER BY
exists EXISTS 条件语句
notExists NOT EXISTS 条件语句
between BETWEEN 条件语句
notBetween NOT BETWEEN 条件语句
addFilter 自由拼接 SQL
last 拼接在最后,例如:last(“LIMIT 1”)

测试用例

@SpringBootTest
public class MybatisPlusWrapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test01(){
        //查询用户名包含a,年龄在20-30之间,邮箱信息不为null的用户信息
        QueryWrapper<User> queryWrapper =  new QueryWrapper<>();
        queryWrapper.like("user_name","a")
                .between("age",20,30)
                .isNotNull("email");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);

    }

    @Test
    public void test02(){
        //查询用户信息,按照年龄的降序,若年龄相同,则按照id的升序排序
        QueryWrapper<User> queryWrapper =  new QueryWrapper<>();
        queryWrapper.orderByDesc("age")
                .orderByAsc("uid");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    @Test
    public void test03(){
        //删除邮箱地址为null的用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
        int i = userMapper.delete(queryWrapper);
        System.out.println("result:"+i);
    }

    @Test
    public void test04(){
        //将(年龄大于20并且用户名中包含a)或邮箱为null的用户信息修改
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age",20)
                .like("user_name","a")
                .or()
                .isNull("email");
        User user = new User();
        user.setName("小明");
        user.setEmail("2283134870@qq.com");
        int update = userMapper.update(user, queryWrapper);
        System.out.println("result:"+update);
    }

    @Test
    public void test05(){
        //将用户名中包含有a并且(年龄大于20或邮箱为null) 的用户信息修改
        //lambda中的表达式优先执行
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("user_name","a")
                   .and(i->i.gt("age",20).or().isNull("email"));
        User user = new User();
        user.setName("小红");
        user.setEmail("2283134870@qq.com");
        int update = userMapper.update(user, queryWrapper);
        System.out.println("result:"+update);
    }

    @Test
    public void test06(){
        //查询用户的用户名,年龄,邮箱信息
        //SELECT user_name,age,email FROM t_user WHERE is_deleted=0
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("user_name","age","email");
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }

    /**
     * 子查询
     */
    @Test
    public void test07(){
        //查询id小于等于100的用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("uid","select uid from t_user where uid <= 10");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    @Test
    public void test08(){
        //将用户名中包含有a并且(年龄大于20或邮箱为null) 的用户信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("user_name","a")
                .and(i -> i.gt("age",20).or().isNull("email"));
        updateWrapper.set("user_name","小黑").set("email","787878.com");
        int i = userMapper.update(null, updateWrapper);
        System.out.println("rs:" + i);
    }

    @Test
    public void test09(){
        String username = "a";
        Integer ageBegin = 20;
        Integer ageEnd = 30;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if(StringUtils.isNotBlank(username)){
            //isNotBlank判断某个字符串是否不为空字符串,不为null,不为空白符
            queryWrapper.like("user_name",username);
        }
        if (ageBegin != null){
            queryWrapper.ge("age",ageBegin);
        }
        if (ageEnd != null){
            queryWrapper.le("age",ageEnd);
        }
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    @Test
    public void test10(){
        String username = "a";
        Integer ageBegin = 20;
        Integer ageEnd = 30;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username),"user_name",username)
                .ge(ageBegin != null,"age",ageBegin)
                .le(ageEnd != null,"age",ageEnd);
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    @Test
    public void test11() {
        String username = "a";
        Integer ageBegin = 20;
        Integer ageEnd = 30;
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
                .ge(ageBegin != null,User::getAge,ageBegin)
                .le(ageEnd != null,User::getAge,ageEnd);
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }
}

文章作者: zenghao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zenghao !
评论
  目录