mybatis-plus:3.5.3.1 注意:springboot版本需要在3.2.0以下才兼容
mybatis-plus官网:https://baomidou.com

快速了解(入门案例)

  1. 准备数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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, '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. 创建项目,导入依赖(pom.xml)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven. apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
    </parent>

    <groupId>cn.xnj</groupId>
    <artifactId>springboot-mybatisplus-04</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>
    <!-- spring-boot核心包 -->
    <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>

    <!-- mybatis-plus -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    </dependency>

    <!--数据库相关配置启动器-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!--druid数据库连接池-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.22</version>
    </dependency>

    <!-- mysql驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
    </dependency>

    <!--lombok-->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version>
    </dependency>

    </dependencies>

    <!--SpringBoot应用打包插件-->
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>
  3. 配置文件(application.yml)和启动类(MainApp)
    application.yml:src/main/resources

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
    url: jdbc:mysql://localhost:3306/studb
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

    mybatis-plus:
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台输出sql语句
    #map-underscore-to-camel-case: true #mybatisplus已经默认开启了驼峰命名
    #mapper-locations: classpath:/mapper/*.xml
    type-aliases-package: cn.xnj.pojo #别名包扫描路径

    MainApp:cn.xnj

    1
    2
    3
    4
    5
    6
    7
    @MapperScan("cn.xnj.mapper")
    @SpringBootApplication
    public class MainApp {
    public static void main(String[] args) {
    SpringApplication.run(MainApp.class,args);
    }
    }
  4. 实体类(User)和持久层(UserMapper)
    User: cn.xnj.pojo

    1
    2
    3
    4
    5
    6
    7
    @Data
    public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    }

    UserMapper:xn.xnj.mapper

    1
    2
    public interface UserMapper extends BaseMapper<User> {
    }
  5. 测试(MyBatisPlusTest)
    MyBatisPlusTest:src/main/test/java/cn.xnj.test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootTest
    public class MyBatisPlusTest {
    @Autowired
    private UserMapper userMapper;

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

基于Mapper接口CRUD

下面使用的案例即上面的入门案例

Insert方法

语法:

1
2
3
4
// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法
int insert(T entity);

参数说明:

类型 参数名 描述
T entity 实体对象

示例:

1
2
3
4
5
6
7
8
9
@Test
public void test_insert(){
User user = new User();
user.setName("李四");
user.setAge(20);
user.setEmail("EMAIL");
int rows = userMapper.insert(user);
System.out.println("rows:"+rows);
}

Delete方法

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 根据 ID 删除
int deleteById(Serializable id);

// 2. 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

// 3. 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

// 4. 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);


参数说明:

类型 参数名 描述
Serializable id 主键 ID
Collection<? extends Serializable> idList 主键 ID 列表(不能为 null 以及 empty)
Map columnMap 表字段 map 对象
Wrapper wrapper 实体对象封装操作类(可以为 null)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void test_delete(){
// 1. 根据id删除
//DELETE FROM user WHERE id=?
int rows1 = userMapper.deleteById(1276067841L);
System.out.println("rows1="+rows1);


// 2. 删除(根据ID 批量删除)
//DELETE FROM user WHERE id IN ( ? , ? , ? )
Collection<Long> ids = List.of(1L,2L,3L);
int rows2 = userMapper.deleteBatchIds(ids);
System.out.println("rows2="+rows2);


// 3. 根据 columnMap 条件,删除记录
//DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("age",24);
columnMap.put("name","Billie");
int rows4 = userMapper.deleteByMap(columnMap);
System.out.println("rows4="+rows4);


// 4. 根据 entity 条件,删除记录 wrapper 条件
//wrapper 条件封装对象,可以无限的封装条件
//userMapper.delete(wrapper);

}

Update方法

语法:

1
2
3
4
5
6
// 1. 根据 ID 修改  主键属性必须有值
int updateById(@Param(Constants.ENTITY) T entity);

// 2. 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity,
@Param(Constants.WRAPPER) Wrapper<T> whereWrapper);

参数说明:

类型 参数名 描述
T entity 实体对象 (set 条件值,可为 null)
Wrapper updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test_update(){
//当属性为null时,不修改,所以实体属性都用封装类型

// 1. 根据 ID 修改 ,主键属性必须有值 修改id为1的用户的年龄为30
// UPDATE user SET age=? WHERE id=?
User user = new User();
user.setId(1L);
user.setAge(30);
int rows1 = userMapper.updateById(user);
System.out.println("rows1="+rows1);


// 2. 根据 whereWrapper 条件,更新记录
//将所有人的年龄改为22
// UPDATE user SET age=?
User user2 = new User();
user2.setAge(22);
int rows2 = userMapper.update(user2,null);//null表示没有条件
System.out.println("rows2="+rows2);
}

Select方法

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 根据 ID 查询
T selectById(Serializable id);

// 2. 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

// 3. 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 4. 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 5. 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

// 6. 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 7. 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 8. 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 9. 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 10. 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

参数说明:

类型 参数名 描述
Serializable id 主键 ID
Wrapper queryWrapper 实体对象封装操作类(可以为 null)
Collection<? extends Serializable> idList 主键 ID 列表(不能为 null 以及 empty)
Map columnMap 表字段 map 对象
IPage page 分页查询条件(可以为 RowBounds.DEFAULT)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test_select(){
//1. 根据id查询
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(1L);
System.out.println("user="+user);

//2. 根据ID 批量查询
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
Collection<Long> ids = List.of(1L,2L,3L);
List<User> userList1 = userMapper.selectBatchIds(ids);
System.out.println("userList1 ="+userList1);

// 5. 查询(根据 columnMap 条件)
//SELECT id,name,age,email FROM user WHERE name =? AND age =?
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("age",24);
columnMap.put("name","Billie");
List<User> userList2 = userMapper.selectByMap(columnMap);
System.out.println("userList2="+userList2);

}

基于Service接口CRUD

下面使用的案例即上面的入门案例

通用 Service CRUD 封装IService (opens new window)接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,

对比Mapper接口CRUD区别:

  • service添加了批量方法
  • service层的方法自动添加事务

使用IService接口方式

  1. mapper还是照前面写继承BaseMapper<T>【cn.xnj.mapper】

    1
    public interface UserMapper extends BaseMapper<User> {}
  2. service接口继承Iservice<T>【cn.xnj.service】

    1
    public interface UserService extends IService<User> {}
  3. service的实现类继承ServiceImpl<M extends BaseMapper<T>, T>【cn.xnj.service.impl】

    1
    2
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

官网: https://baomidou.com/guides/data-interface/

save

语法:

1
2
3
4
5
6
// 1. 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 2. 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 3. 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

参数:

类型 参数名 描述
T entity 实体对象
Collection entityList 实体对象集合
int batchSize 插入批次数量(即一次insert语句插入的条数)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void test_save(){
//1.插入一条记录(选择字段,策略插入)
User user1 = new User(null,"李四",20,"EMAIL");
boolean flg1 = userService.save(user1);

//2. 插入(批量) 默认批次 这里不能再用上面的user对象加入集合中进行批量插入,出现主键重复
//假设默认的批次的大小为2
/**
* INSERT INTO user (name,age, email) VALUES
* ('王五1', 20,'EMAIL1'),
* ('王五2', 21,'EMAIL2'),
*/
List<User> userList = new ArrayList<>();
User user2 = new User(null,"王五1",20,"EMAIL");
User user3 = new User(null,"王五2",21,"EMAIL2");
userList.add(user2);
userList.add(user3);
boolean flg2 = userService.saveBatch(userList);

//3. 插入(批量) 分批次 设置批次大小为1
/**
* INSERT INTO user (name,age, email) VALUES('张三1', 20,'EMAIL'),
* INSERT INTO user (name,age, email) VALUES('张三2', 21,'EMAIL2'),
* INSERT INTO user (name,age, email) VALUES('张三3', 20,'EMAIL3'),
*/
List<User> userList2 = new ArrayList<>();
User user4 = new User(null,"张三1",20,"EMAIL");
User user5 = new User(null,"张三2",21,"EMAIL2");
User user6 = new User(null,"张三3",20,"EMAIL");
userList2.add(user4);
userList2.add(user5);
userList2.add(user6);
boolean flg3 = userService.saveBatch(userList2,1);
}

saveOrUpdate

语法:

1
2
3
4
5
6
7
8
// 1. TableId 注解属性值存在则更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 2. 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 3. 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 4. 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

参数:

类型 参数名 描述
T entity 实体对象
Wrapper updateWrapper 实体对象封装操作类 UpdateWrapper
Collection entityList 实体对象集合
int batchSize 插入批次数量

示例:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test_saveOrUpdate(){
// 1. TableId 注解属性值存在则更新记录,否插入一条记录
//如果id有值就更新,没有值就插入一条记录
User user = new User(null,"John Doe",30,"john.doe@example.com");
boolean flg1 = userService.saveOrUpdate(user);// 插入一条记录

user.setId(1843191296522436610L);
user.setAge(40);
boolean flg2 = userService.saveOrUpdate(user);// 更新一条记录
}

remove

语法:

1
2
3
4
5
6
7
8
// 1. 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 2. 根据 ID 删除
boolean removeById(Serializable id);
// 3. 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 4. 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

参数:

类型 参数名 描述
Wrapper queryWrapper 实体包装类 QueryWrapper
Serializable id 主键 ID
Map columnMap 表字段 map 对象
Collection<? extends Serializable> idList 主键 ID 列表

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test_remove(){
// 2. 根据ID 删除一条记录
boolean flag = userService.removeById(1843191296522436610L);

// 3. 根据 columnMap 条件,删除记录
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "张三1");
columnMap.put("age", 20);
boolean flag2 = userService.removeByMap(columnMap);

// 4. 删除(根据ID 批量删除)
//DELETE FROM user WHERE id IN (?,?,? )
Collection<Long> ids = List.of(1L,2L,3L);
boolean flag3 = userService.removeByIds(ids);
}

update

语法:

1
2
3
4
5
6
7
8
9
10
// 1. 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 2. 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 3. 根据 ID 选择修改
boolean updateById(T entity);
// 4. 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 5. 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

参数:

类型 参数名 描述
Wrapper updateWrapper 实体对象封装操作类 UpdateWrapper
T entity 实体对象
Collection entityList 实体对象集合
int batchSize 更新批次数量

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test_update() {
//为空的属性不更改

// 3. 根据 ID 选择修改
User user = new User(1843124048071974914L, "李四", 28, null);
boolean flag = userService.updateById(user);

// 4. 根据ID 批量更新 默认批次
List<User> userList = new ArrayList<>();
User user1 = new User(5L, null, 58, null);
User user2 = new User(4L, null, 68, null);
userList.add(user1);
userList.add(user2);
boolean flag2 = userService.updateBatchById(userList);

// 5. 根据ID 批量更新 分批次
}

get

语法:

1
2
3
4
5
6
7
8
9
10
// 1. 根据 ID 查询
T getById(Serializable id);
// 2. 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 3. 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 4. 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 5. 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

参数:

类型 参数名 描述
Serializable id 主键 ID
Wrapper queryWrapper 实体对象封装操作类 QueryWrapper
boolean throwEx 有多个 result 是否抛出异常
T entity 实体对象
Function<? super Object, V> mapper 转换函数

示例:

1
2
3
4
5
6
7
@Test
public void test_get(){

User user = userService.getById(3L);//返回的是单个对象
System.out.println(user);
}

list

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 查询所有
List<T> list();
// 2. 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 3. 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 4. 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 5. 查询所有列表
List<Map<String, Object>> listMaps();
// 6. 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 7. 查询全部记录
List<Object> listObjs();
// 8. 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 9. 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 10. 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

参数:
|类型|参数名|描述|
|:—-:|:—-:|:—-:|
|Wrapper|queryWrapper|实体对象封装操作类 QueryWrapper|
|Collection<? extends Serializable>|idList|主键 ID 列表|
|Map|columnMap|表字段 map 对象|
|Function<? super Object, V>|mapper|转换函数|

示例:

1
2
3
4
5
6
@Test
public void test_list(){

List<User> userList = userService.list(null);//查询全部,返回的是集合
System.out.println(userList);
}

page分页查询

  1. 前置条件说明【依然是上面入门案例】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package cn.xnj.mapper;
    public interface UserMapper extends BaseMapper<User> {}

    package cn.xnj.service;
    public interface UserService extends IService<User> {}

    package cn.xnj.service.impl;
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
  2. 在配置类中将mybatis-plus插件加入ioc容器中,并加入分页插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @MapperScan("cn.xnj.mapper")
    @SpringBootApplication
    public class MainApp {
    public static void main(String[] args) {
    SpringApplication.run(MainApp.class,args);
    }

    //mybatis-plus插件加入到ioc容器中
    @Bean
    public MybatisPlusInterceptor plusInterceptor(){
    //mybatis-plus的插件集合【加入到这个插件集合中即可:分页插件,乐观锁插件..】
    MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
    //加入分页插件
    mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.MYSQL));
    return mybatisPlusInterceptor;
    }
    }
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @SpringBootTest
    public class MybatisPlusPageTest {

    @Autowired
    private UserMapper userMapper;
    @Test
    public void test01(){
    //IPage -> Page(页码,页容量)
    Page<User> page = new Page<>(1,2);
    userMapper.selectPage(page,null);//第二个参数为条件

    //结果page最后也会被封装结果
    long current = page.getCurrent();//当前页页码
    long size = page.getSize();//页容量
    List<User> records = page.getRecords();//当前页数据
    long total = page.getTotal();//总记录数
    long pages = page.getPages();//总页数
    boolean hasNext = page.hasNext();//是否有下一页
    boolean hasPrevious = page.hasPrevious();//是否有上一页

    }
    }

自定义page分页查询

  1. 同上方,需要在配置类中添加mybatis-plus分页的插件
  2. 在mapper中定义你自己的page方法
    注意方法返回值:IPage<查询返回类型> 如查询部分字段还重命名了就可以用Map

    1
    2
    3
    4
    5
    package cn.xnj.mapper;
    public interface UserMapper extends BaseMapper<User> {
    //返回的是IPage
    IPage<User> selectPageByAge(IPage<User> page, @Param("age") Integer age);
    }

    mybatisplus对mapperxml文件的默认目录是:resources/mapper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace=接口类的全限定名,这样实现对应 -->
    <mapper namespace="cn.xnj.mapper.UserMapper">

    <!--resultType的value是page的泛型-->
    <select id="selectPageByAge" resultType="cn.xnj.pojo.User">
    select * from user where age>#{age}
    </select>
    </mapper>
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testMyPage(){
    //IPage -> Page(页码,页容量)
    IPage<User> page = new Page<>(1,2);
    userMapper.selectPageByAge(page,18);
    //结果page最后也会被封装结果
    long current = page.getCurrent();//当前页页码
    long size = page.getSize();//页容量
    List<User> records = page.getRecords();//当前页数据
    long total = page.getTotal();//总记录数
    long pages = page.getPages();//总页数
    boolean hasNext = page.hasNext();//是否有下一页
    boolean hasPrevious = page.hasPrevious();//是否有上一页

    System.out.println(page);
    }

条件构造器

MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。

修改的时候:UpdateWrapper、LambdaUpdateWrapper
删除,查询,修改:QueryWrapper、LambdaQueryWrapper

函数:

函数名 说明 函数名 说明
eq 等于= ne 不等于<>
gt 大于> ge 大于等于>=
lt 小于< le 小于等于<=
between between a and b notBetween not between a and b
like like ‘%值%’ notLike not like ‘%值%’
likeLeft like ‘%值’ likeRight like ‘值%’
isNull 字段 is null isNotNull 字段 is not null
in 字段 in (v0,v1,..) notIn 字段 not in (v0,v1,..)
inSql 字段 in (sql语句) notInSql 字段 not in (sql语句)
groupBy 分组 orderBy 排序
orderByAsc 升序排序 orderByDesc 降序排序
having having(sql语句)
or 拼接or and and嵌套
or()函数只有后面紧跟的条件为or拼接,其他条件全默认为and连接

基于QueryWrapper组装条件

示例:
创建一个QueryWrapper对象来组装条件
普通使用或链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test_01(){
//查询用户名包含'张',年龄在20,30之间,并且邮箱不为空
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
/*queryWrapper.like("name","张");
queryWrapper.between("age",20,30);
queryWrapper.isNotNull("email");*/

//链式调用
queryWrapper.like("name","张")
.between("age",20,30)
.isNotNull("email");

List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}


排序
1
2
3
4
5
6
7
// 按年龄降序查询用户,如果年龄相同,则按id升序排列
QueryWrapper<User> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.orderByDesc("age")
.orderByAsc("id");

List<User> userList1 = userMapper.selectList(queryWrapper1);
System.out.println(userList1);

查询指定列
1
2
3
4
5
6
7
// 查询用户名和年龄字段,并且用户年龄大于25的用户
QueryWrapper<User> queryWrapper2 = new QueryWrapper<>();
queryWrapper2.gt("age",25);
queryWrapper2.select("name","age");

List<User> userList2 = userMapper.selectList(queryWrapper2);
System.out.println(userList2);

删除
1
2
3
4
5
6
7
8
9
@Test
public void test_02(){
// 1. 删除邮箱为null的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");

int rows = userMapper.delete(queryWrapper);
System.out.println(rows);
}

修改操作
使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test_03(){
//将年龄大于20并且用户名中包含有'张'或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("name","张")//条件直接俄调用方法默认使用and拼接
.or().isNull("email");

User user = new User();
user.setAge(25);
user.setEmail("EMAIL666");

int rows = userMapper.update(user,queryWrapper);
System.out.println(rows);
}

条件判断(if test=”判断”)
每个方法都会有一个boolean condition,运行第一位放一个比较表达式 true则整个条件生效 false不生效
1
2
3
4
5
6
7
8
9
10
@Test
public void test_04(){
//前端传两个参数:name不为空 条件=,age>18 条件=
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
String name = "张xx";
Integer age = 23;
queryWrapper.eq(StringUtils.isNotBlank(name),"name",name);
queryWrapper.eq(age!=null && age>18,"age",age);
List<User> userList = userMapper.selectList(queryWrapper);
}

基于UpdateWrapper组装条件

updateWrapper能直接修改数据,也能将数据修改为任意值如null【QueryWrapper不能修改为null】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@SpringBootTest
public class MyBatisPlusUpdateWrapperTest {

@Autowired
private UserMapper userMapper;
@Test
public void test_01(){
//updatewrappper修改【条件,修改】
// 可以直接修改数据 .set(“列名”,值)
//允许修改为任意值 .set("列名",null)
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();

updateWrapper.gt("age",20)
.like("name","张")
.or().isNull("email")
.set("age",99);

int rows = userMapper.update(null, updateWrapper);
}
}

基于LambdaQueryWrapper和LambdaUpdateWrapper组装条件

在写条件字段时可以直接:(对象::get方法,值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@SpringBootTest
public class MyBatisPlusLambdaQueryWrapperTest {

@Autowired
private UserMapper userMapper;

//LambdaQueryWrapper
@Test
public void test_select(){
// 查询用户名包含'张',年龄在20,30之间,并且邮箱不为空

//queryWrapper,链式调用
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","张")
.between("age",20,30)
.isNotNull("email");
List<User> userList1 = userMapper.selectList(queryWrapper);

//LambdaQueryWrapper,lambda
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(User::getName,"张")
.between(User::getAge,20,30)
.isNotNull(User::getEmail);
List<User> userList2 = userMapper.selectList(lambdaQueryWrapper);
}


//LambdaUpdateWrapper
@Test
public void test_update(){
//将年龄大于20并且用户名中包含有'张'的用户年龄改为18岁

//UpdateQueryWrapper,修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("age",20)
.like("name","张")//条件直接俄调用方法默认使用and拼接
.set("age",18);
int rows1 = userMapper.update(null,updateWrapper);

//LambdaQueryWrapper
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.gt(User::getAge,20)
.like(User::getName,"张")
.set(User::getAge,18);
int rows2 = userMapper.update(null,lambdaUpdateWrapper);

}
}

MyBatisPlus核心注解使用

  1. @TableName(value=”表名”)
    如果仅使用该注解,而不指定值,就使用类名为表名,忽略大小写,可以省略该注解!
    也可以在全局配置文件中指定类名与表名的映射关系
    1
    2
    3
    4
    mybatis-plus: # mybatis-plus的配置
    global-config:
    db-config:
    table-prefix: t_ # 表名前缀字符串 User -> t_user
    • value:当实体类名与数据库表名不一致的时候,可以用value来指定数据库表名
  1. @TableId(value=”主键列名”,type=主键策略)
    用在属性上指定主键id字段

    • value:当主键名与属性名完全不同时在里面填主键字段名
    • type:主键策略如:auto、ASSIGN_ID(雪花算法生成不重复的long型id,默认)
      ASSIGN_ID雪花算法: 1.数据库主键为 bigint / varchar(64)类型 2.实体属性为long 3.随机生成不重复
      auto自增:1.数据库主键类型为数字 设置了auto_increment 2.插入数据自增长
      也可以通过全局配置文件配置type
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      mybatis-plus:
      configuration:
      # 配置MyBatis日志
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
      db-config:
      # 配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-Plus的主键策略
      id-type: auto
  2. @TableField(value="非主键字段名",exist = 是否为表字段)

    • @TableField用于实体类非主键字段的其他属性上
    • value:当表字段名和属性名不一致就可以使用value来指定
    • exist:当为false时表示该字段不属于数据库表字段,插入或查询时不参与

逻辑删除的实现

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

  1. 表添加逻辑删除字段(可以是一个布尔类型、整数类型或枚举类型。)

    1
    alter table user add deleted int default 0 ;  # int 类型 1 逻辑删除 0 未逻辑删除
  2. 实体类添加逻辑删除属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Data
    public class User {

    // @TableId
    private Integer id;
    private String name;
    private Integer age;
    private String email;

    //逻辑删除,默认值为0,1为删除
    //当删除数据的时候,会自动将deleted字段的值改为1
    //当查询数据的时候,会自动将deleted字段的值为1的数据过滤掉,只查询deleted=0的数据
    @TableLogic
    //逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0
    private Integer deleted;
    }

    如果觉得每个类都添加@TableLogic注解麻烦,可以在全局配置中指定

    1
    2
    3
    4
    5
    6
    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)

乐观锁的实现

乐观锁和悲观锁:
用上厕所蹲坑来比喻,悲观锁就是有厕所门的厕所,一个人进去了锁了门,另一个人就进不去了。乐观锁就是没有厕所门的厕所,一个人进去了,另一个人进去发现已经有人了就失败,再进去发现还是有人还是失败,直到厕所里没人了再进去成功。

注意:悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!

乐观锁具体方案:
版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。

  • 每条数据添加一个版本号字段version
  • 取出记录时,获取当前 version
  • 更新时,检查获取版本号是不是数据库当前最新版本号
  • 如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1
  • 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败

实现步骤:

  1. 实体类添加乐观锁字段并添加@Version注解

    1
    2
    @Version
    private Integer version;
  2. 数据库也添加version字段

    1
    alter table user add version int default 1 ;  # int 类型 乐观锁字段
    • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,
    • 仅支持 updateById(id) 与 update(entity, wrapper) 方法
  3. 添加版本号更新插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //mybatis-plus插件加入到ioc容器中
    @Bean
    public MybatisPlusInterceptor plusInterceptor(){
    //mybatis-plus的插件集合【加入到这个插件集合中即可:分页插件,乐观锁插件..】
    MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
    //加入分页插件
    //mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

    //加入乐观锁插件 mybatis-plus会在更新的时候,每次帮我们对比版本号字段和增加版本号+1
    mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return mybatisPlusInterceptor;
    }
  4. 之后正常使用更新即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //演示乐观锁生效场景
    @Test
    public void testQuick7(){
    //步骤1: 先查询,在更新 获取version数据
    //同时查询两条,但是version唯一,最后更新的失败
    User user = userMapper.selectById(5); //version=1
    User user1 = userMapper.selectById(5);//version=1

    user.setAge(20); //age=20
    user1.setAge(30); //age=30

    userMapper.updateById(user);//修改成功 version=2
    //乐观锁生效,失败!
    userMapper.updateById(user1);//2!=1 修改失败
    //age=20
    }

防全表更新和删除实现

  1. 添加防止全表更新和删除拦截器

    1
    2
    3
    4
    5
    6
    7
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //防止全表更新和删除插件【防止恶意操作】
    mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
    return interceptor;
    }
  2. 测试全部更新或全部删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void testQuick8(){
    User user = new User();
    user.setName("custom_name");
    user.setEmail("xxx@mail.com");
    //Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
    //全局更新,报错
    userService.saveOrUpdate(user,null);
    }

MyBatis-Plus代码生成器(MyBatisX插件)

  1. 确保你的idea安装了MyBatisX的插件

  2. 点击idea右侧数据库,连接数据库

  3. 右键表,点击MyBatisX-Generator

    • module path:选择项目如 D:/code/springboot-project
    • base package:项目根包如 cn.xnj
    • relative package:存实体类的包如 pojo
    • ignore file prefix/sufix:忽略字段的前/后缀
    • ignore table prefix/sufix:忽略表的前前/后缀如 t_
  4. 点击next

    • annotation注解: MyBatis-Plus3
    • options: Lombok Model
    • template: mybatis-plus3
  5. 点击finish即可生成
    注意实体类属性中version,deleted等字段还需要自己添加相应需求注解

MyBatisX代码生成器:在mapper里写方法名时会发现mybatisx的提示,按其提示写,然后alter+enter将会帮我们自动生成代码