【尚硅谷】MybatisPlus 学习笔记(上)

目录

一、MybatisPlus简介

1.1、简介

1.2、支持数据库

二、入门案例

2.1、开发环境

2.2、创建数据库及表

创建表

添加数据

2.3、创建SpringBoot项目

初始化工程

导入依赖

idea中安装lombok插件

配置application.yml

启动类

实体类

添加mapper

测试

添加日志

项目结构

三、基本CRUD

3.1、BaseMapper

3.1.1、插入

3.1.2、删除

根据id删除记录

根据id批量删除记录

通过map条件删除记录

3.1.3、修改

3.1.4、查询

根据id查询用户信息

根据多个id查询多个用户信息

通过map条件查询用户信息

查询所有数据

3.1.5、自定义功能

3.2、通用Service

3.2.1、IService

3.2.2、创建Service接口和实现类

3.2.3、测试查询记录数

3.2.4、测试批量插入

四、常用注解

4.1、@TableName

4.1.1、问题

4.1.2、通过@TableName解决问题

4.1.3、通过全局配置解决问题

4.2、@TableId

4.2.1、问题

4.2.2、通过@TableId解决问题

4.2.3、@TableId的value属性

4.2.4、@TableId的type属性

4.2.5、配置全局主键策略

4.2.6、雪花算法

背景

数据库分表

垂直分表

水平分表

4.3、@TableField

4.3.1、情况1

4.3.2、情况2

4.4、@TableLogic

4.4.1、逻辑删除

4.4.2、实现逻辑删除

五、条件构造器和常用接口

5.1、wapper介绍

5.2、QueryWrapper

5.2.1、例1:组装查询条件

5.2.2、例2:组装排序条件

5.2.3、例3:组装删除条件

5.2.4、例4:条件的优先级

5.2.5、例5:组装select子句

5.2.6、例6:实现子查询

5.3、UpdateWrapper

5.4、condition

5.4.1、思路1

5.4.2、思路2

5.5、LambdaQueryWrapper

5.6、LambdaUpdateWrapper


一、MybatisPlus简介

1.1、简介

(1)润物无声

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

(2)效率至上

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

(3)丰富功能

代码生成、自动分页、逻辑删除、自动填充等功能一应俱全。

1.2、支持数据库

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

二、入门案例

2.1、开发环境

IDE:idea 2019.2
JDK:JDK8+
构建工具:maven 3.6.3
MySQL版本:MySQL 5.7
Spring Boot:2.6.3
MyBatis-Plus:3.5.1

2.2、创建数据库及表

创建表

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
    `id` bigint(20) NOT NULL COMMENT '主键ID',
    `name` varchar(30) DEFAULT NULL COMMENT '姓名',
    `age` int(11) DEFAULT NULL COMMENT '年龄',
    `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加数据

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');

2.3、创建SpringBoot项目

初始化工程

使用 Spring Initializr 快速初始化一个 Spring Boot 工程。

注意:如果你通过Spring Initializr 创建的SpringBoot版本太高,那就用maven来创建工程,再自己指定版本,操作帖子如下。

IDEA中,如何将maven项目变为SpringBoot项目?_idea 基于maven项目 转化成springboot项目-CSDN博客

导入依赖

<dependencies>
	<!--web依赖-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<!--test测试依赖-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.5.1</version>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
</dependencies>

idea中安装lombok插件

配置application.yml

server:
  port: 8888
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:13306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: root

启动类

在Spring Boot启动类中添加@MapperScan注解,扫描mapper包

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
@MapperScan("com.oracle.mapper")
public class MybatisPlusDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusDemoApplication.class,args);
    }
}

实体类

package com.oracle.pojo;

import lombok.Data;

@Data //lombok注解
public class User {

    private Long id;
    private String name;
    private Integer age;
    private String email;

}

添加mapper

BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型。

public interface UserMapper extends BaseMapper<User> {

}

测试

package com.oracle;

import com.oracle.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MybatisPlusTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList(){
        //selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
        userMapper.selectList(null).forEach(System.out::println);
    }

}

IDEA userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确 的执行。
为了避免报错,可以在mapper接口上添加 @Repository 注解

添加日志

在application.yml中配置日志输出

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

项目结构

三、基本CRUD

3.1、BaseMapper

MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:

package com.baomidou.mybatisplus.core.mapper;

public interface BaseMapper<T> extends Mapper<T> {
    /**
     * 插入一条记录
     * @param entity 实体对象
     */
    int insert(T entity);
    
    /**
     * 根据 ID 删除
     * @param id 主键ID
     */
    int deleteById(Serializable id);
    
    /**
     * 根据实体(ID)删除
     * @param entity 实体对象
     * @since 3.4.4
     */
    int deleteById(T entity);
    
    /**
     * 根据 columnMap 条件,删除记录
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
    /**
     * 根据 entity 条件,删除记录
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
     * 删除(根据ID 批量删除)
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    
    /**
     * 根据 ID 修改
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);
    
    /**
     * 根据 whereEntity 条件,更新记录
     * @param entity 实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
    
    /**
     * 根据 ID 查询
     * @param id 主键ID
     */
    T selectById(Serializable id);
    
    /**
     * 查询(根据ID 批量查询)
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    
    /**
     * 查询(根据 columnMap 条件)
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
     </p>
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        List<T> ts = this.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(ts)) {
            if (ts.size() != 1) {
                throw ExceptionUtils.mpe("One record is expected, but the query
                        result is multiple records");
            }
            return ts.get(0);
        }
        return null;
    }
    
    /**
     * 根据 Wrapper 条件,查询总记录数
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
     * 根据 entity 条件,查询全部记录
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
     * 根据 Wrapper 条件,查询全部记录
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
                                                 queryWrapper);
    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     * @param page 分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     * @param page 分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

}

3.1.1、插入

@Test
public void testInsert(){
	User user = new User(null, "张三", 23, "zhangsan@atguigu.com");
	//INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
	int result = userMapper.insert(user);
	System.out.println("受影响行数:"+result);
	//1761203470926962690
	System.out.println("id自动获取:"+user.getId());
}

最终执行的结果,所获取的id为1761203470926962690

这是因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id

3.1.2、删除

根据id删除记录
@Test
public void testDeleteById(){
	// 通过id删除用户信息
	// DELETE FROM user WHERE id=?
	int result = userMapper.deleteById(1761203470926962690L);
	System.out.println("受影响行数:"+result);
}

根据id批量删除记录
@Test
public void testDeleteBatchIds(){
	//通过多个id批量删除
	//DELETE FROM user WHERE id IN ( ? , ? , ? )
	List<Long> idList = Arrays.asList(1L, 2L, 3L);
	int result = userMapper.deleteBatchIds(idList);
	System.out.println("受影响行数:"+result);
}

通过map条件删除记录
@Test
public void testDeleteByMap(){
	// 根据map集合中所设置的条件删除记录
	// DELETE FROM user WHERE name = ? AND age = ?
	Map<String, Object> map = new HashMap<>();
	map.put("age", 23);
	map.put("name", "张三");
	int result = userMapper.deleteByMap(map);
	System.out.println("受影响行数:"+result);
}

3.1.3、修改

@Test
public void testUpdateById(){
	User user = new User(4L, "admin", 22, null);
	//UPDATE user SET name=?, age=? WHERE id=?
	int result = userMapper.updateById(user);
	System.out.println("受影响行数:"+result);
}

3.1.4、查询

根据id查询用户信息
@Test
public void testSelectById(){
	//根据id查询用户信息
	//SELECT id,name,age,email FROM user WHERE id=?
	User user = userMapper.selectById(4L);
	System.out.println(user);
}

根据多个id查询多个用户信息
@Test
public void testSelectBatchIds(){
	//根据多个id查询多个用户信息
	//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
	List<Long> idList = Arrays.asList(4L, 5L);
	List<User> list = userMapper.selectBatchIds(idList);
	list.forEach(System.out::println);
}

通过map条件查询用户信息
@Test
public void testSelectByMap(){
	//通过map条件查询用户信息
	//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
	Map<String, Object> map = new HashMap<>();
	map.put("age", 22);
	map.put("name", "admin");
	List<User> list = userMapper.selectByMap(map);
	list.forEach(System.out::println);
}

查询所有数据
@Test
public void testSelectList(){
	//查询所有用户信息
	//SELECT id,name,age,email FROM user
	List<User> list = userMapper.selectList(null);
	list.forEach(System.out::println);
}

通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所有数据。

3.1.5、自定义功能

创建UserMapper.xml文件

UserMapper定义自定义方法:

package com.oracle.mapper;

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

import java.util.Map;

@Repository
public interface UserMapper extends BaseMapper<User> {

    /**
     * 根据id查询用户信息为map集合
     * @param id
     * @return
     */
    Map<String, Object> selectMapById(Long id);

}

UserMapper.xml中写对应的方法实现:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.oracle.mapper.UserMapper">

    <select id="selectMapById" resultType="map">
        select id, name, age, email from user where id = #{id}
    </select>

</mapper>

测试:

@Test
public void testMap() {
	Map<String, Object> map = userMapper.selectMapById(1L);
	System.out.println(map);
}

3.2、通用Service

说明:

  • 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承Mybatis-Plus 提供的基类
  • 官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3

3.2.1、IService

MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑

详情查看源码IService和ServiceImpl

3.2.2、创建Service接口和实现类

/**
 * UserService继承IService模板提供的基础功能
 */
public interface UserService extends IService<User> {
    
}
package com.oracle.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.oracle.mapper.UserMapper;
import com.oracle.pojo.User;
import com.oracle.service.UserService;
import org.springframework.stereotype.Service;

/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
}

3.2.3、测试查询记录数

@Autowired
private UserService userService;


@Test
public void testGetCount(){
	long count = userService.count();
	System.out.println("总记录数:" + count);
}

3.2.4、测试批量插入

@Test
public void testSaveBatch(){
	// SQL长度有限制,海量数据插入单条SQL无法实行,
	// 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
	ArrayList<User> users = new ArrayList<>();
	for (int i = 0; i < 5; i++) {
		User user = new User();
		user.setName("ybc" + i);
		user.setAge(20 + i);
		users.add(user);
	}
	//SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
	userService.saveBatch(users);
}

四、常用注解

4.1、@TableName

经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表。

由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决
定,且默认操作的表名和实体类型的类名一致。

4.1.1、问题

若实体类类型的类名和要操作的表的表名不一致,会出现什么问题?

我们将表user更名为t_user,测试查询功能:
程序抛出异常,Table 'mybatis_plus.user' doesn't exist,因为现在的表名为t_user,而默认操作的表名和实体类型的类名一致,即user表。

4.1.2、通过@TableName解决问题

在实体类类型上添加@TableName("t_user"),标识实体类对应的表,即可成功执行SQL语句

@TableName("t_user")
public class User {

    private Long id;
    private String name;
    private Integer age;
    private String email;

}

4.1.3、通过全局配置解决问题

在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_。


此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就不需要在每个实体类上通过@TableName标识实体类对应的表。

# 配置MyBatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MybatisPlus操作表的默认前缀
      table-prefix: t_

4.2、@TableId

经过以上的测试,MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id

4.2.1、问题

若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus会自动识别uid为主键列吗?


我们实体类中的属性id改为uid,将表中的字段id也改为uid,测试添加功能程序抛出异常,Field 'uid' doesn't have a default value,说明MyBatis-Plus没有将uid作为主键赋值

4.2.2、通过@TableId解决问题

在实体类中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句

public class User {

    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;

}

4.2.3、@TableId的value属性

若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解@TableId,则抛出异常Unknown column 'id' in 'field list',即MyBatis-Plus仍然会将id作为表的主键操作,而表中表示主键的是字段uid


此时需要通过@TableId注解的value属性,指定表中的主键字段,@TableId("uid")或
@TableId(value="uid")

4.2.4、@TableId的type属性

type属性用来定义主键策略

常用的主键策略:

描述
IdType.ASSIGN_ID(默认)基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
IdType.AUTO使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效

4.2.5、配置全局主键策略

# 配置MyBatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MybatisPlus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-Plus的主键策略
      id-type: auto

4.2.6、雪花算法

背景

需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。

数据库分表

将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。

单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:

垂直分表

垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。

例如,前面示意图中的 nickname 和 description 字段,假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。

水平分表

水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。

但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理

主键自增

①以最常见的用户 ID 为例,可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中,1000000 ~ 1999999 放到表2中,以此类推。


②复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小。


③优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动。


④缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而另外一个分段实际存储的数据量有 1000 万条。

取模

①同样以用户 ID 为例,假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号为 6 的子表中。


②复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。


③优点:表分布比较均匀。


④缺点:扩充新的表很麻烦,所有数据都要重分布。

雪花算法

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

①核心思想:
长度共64bit(一个long型)。首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

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

4.3、@TableField

经过以上的测试,我们可以发现,MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段名一致。

如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?

4.3.1、情况1

若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格
例如实体类属性userName,表中字段user_name
此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格
相当于在MyBatis中配置

4.3.2、情况2

若实体类中的属性和表中的字段不满足情况1
例如实体类属性name,表中字段username
此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段名

4.4、@TableLogic

4.4.1、逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
  • 使用场景:可以进行数据恢复

4.4.2、实现逻辑删除

step1:数据库中创建逻辑删除状态列,设置默认值为0

step2:实体类中添加逻辑删除属性

public class User {

    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer isDeleted;

}

step3:测试
测试删除功能,真正执行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
测试查询功能,被逻辑删除的数据默认不会被查询
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0

五、条件构造器和常用接口

5.1、wapper介绍

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

  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
    • QueryWrapper : 查询条件封装
    • UpdateWrapper : Update 条件封装
    • AbstractLambdaWrapper : 使用Lambda 语法
      • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
      • LambdaUpdateWrapper : Lambda 更新封装Wrapper

5.2、QueryWrapper

5.2.1、例1:组装查询条件

@Test
public void test01(){
	//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
	//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.like("username", "a")
			.between("age", 20, 30)
			.isNotNull("email");
	List<User> list = userMapper.selectList(queryWrapper);
	list.forEach(System.out::println);
}

5.2.2、例2:组装排序条件

@Test
public void test02(){
	//按年龄降序查询用户,如果年龄相同则按id升序排列
	//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper
			.orderByDesc("age")
			.orderByAsc("id");
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

5.2.3、例3:组装删除条件

@Test
public void test03(){
	// 删除email为空的用户
	// DELETE FROM t_user WHERE (email IS NULL)
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.isNull("email");
	//条件构造器也可以构建删除语句的条件
	int result = userMapper.delete(queryWrapper);
	System.out.println("受影响的行数:" + result);
}

5.2.4、例4:条件的优先级

@Test
public void test04() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	// 将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
	// UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ? OR email IS NULL)
	queryWrapper
			.like("username", "a")
			.gt("age", 20)
			.or()
			.isNull("email");
	User user = new User();
	user.setAge(18);
	user.setEmail("user@atguigu.com");
	int result = userMapper.update(user, queryWrapper);
	System.out.println("受影响的行数:" + result);
}
@Test
public void test04() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	// 将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
	// UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
	// lambda表达式内的逻辑优先运算
	queryWrapper
			.like("username", "a")
			.and(i -> i.gt("age", 20).or().isNull("email"));
	User user = new User();
	user.setAge(18);
	user.setEmail("user@atguigu.com");
	int result = userMapper.update(user, queryWrapper);
	System.out.println("受影响的行数:" + result);
}

5.2.5、例5:组装select子句

@Test
public void test05() {
	// 查询用户信息的name和age字段
	// SELECT name,age FROM t_user
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.select("name", "age");
	// selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
	List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
	maps.forEach(System.out::println);
}

5.2.6、例6:实现子查询

@Test
public void test06() {
	//  查询id小于等于3的用户信息
	//  SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (id IN (select id from t_user where id <= 3))
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.inSql("id", "select id from t_user where id <= 3");
	List<User> list = userMapper.selectList(queryWrapper);
	list.forEach(System.out::println);
}

5.3、UpdateWrapper

@Test
public void test07() {
	// 将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
	// 组装set子句以及修改条件
	UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
	// lambda表达式内的逻辑优先运算
	updateWrapper
			.set("age", 18)
			.set("email", "user@atguigu.com")
			.like("username", "a")
			.and(i -> i.gt("age", 20).or().isNull("email"));
	// 这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
	// UPDATE t_user SET username=?, age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
	// User user = new User();
	// user.setName("张三");

	// int result = userMapper.update(user, updateWrapper);
	// UPDATE t_user SET age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
	int result = userMapper.update(null, updateWrapper);
	System.out.println(result);
}

5.4、condition

在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果。

5.4.1、思路1

@Test
public void test08() {
	//定义查询条件,有可能为null(用户未输入或未选择)
	String username = null;
	Integer ageBegin = 10;
	Integer ageEnd = 24;
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
	if(StringUtils.isNotBlank(username)){
		queryWrapper.like("username","a");
	}
	if(ageBegin != null){
		queryWrapper.ge("age", ageBegin);
	}
	if(ageEnd != null){
		queryWrapper.le("age", ageEnd);
	}
	//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

5.4.2、思路2

上面的实现方案没有问题,但是代码比较复杂,我们可以使用带condition参数的重载方法构建查询条件,简化代码的编写

@Test
public void test08UseCondition() {
	// 定义查询条件,有可能为null(用户未输入或未选择)
	String username = null;
	Integer ageBegin = 10;
	Integer ageEnd = 24;
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	// StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
	queryWrapper
			.like(StringUtils.isNotBlank(username), "username", "a")
			.ge(ageBegin != null, "age", ageBegin)
			.le(ageEnd != null, "age", ageEnd);
	// SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

5.5、LambdaQueryWrapper

其实就是为了防止我们把字段名写错。

@Test
public void test09() {
	// 定义查询条件,有可能为null(用户未输入)
	String username = "a";
	Integer ageBegin = 10;
	Integer ageEnd = 24;
	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);
}

5.6、LambdaUpdateWrapper

@Test
public void test10() {
	//组装set子句
	LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
	updateWrapper
			.set(User::getAge, 18)
			.set(User::getEmail, "user@atguigu.com")
			.like(User::getName, "a")
			.and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail)); // lambda表达式内的逻辑优先运算
	User user = new User();
	int result = userMapper.update(user, updateWrapper);
	System.out.println("受影响的行数:" + result);
}
  • 36
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我向往自由

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值