有勇气的牛排博客
☰

Mybatis Plus 为简化开发、提高效率而生

有勇气的牛排 2437 Java 2023-03-20 21:50:16

知止自律打卡-微信小程序APP
一款微信自律打卡小程序,支持自定义打卡项目、习惯记录、项目日记、专注计时与数据统计。自律打卡、学习打卡、读书打卡、健身打卡、运动打卡、减肥打卡、戒色打卡、养生打卡、戒烟、戒酒、戒熬夜等多种场景。万能打卡模板自由创建。可自定义每日打卡目标,记录习惯连续天数,可视化追踪成长进度。
了解产品详情 →
微信搜索「知止自律打卡」
知止自律打卡微信小程序

前言

MyBatis-Plus是一个个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis-Plus提供了通用的mapper和service,可以在不编写任何SQL语句的情况下,快速的实现对单表的CRUD、批量、逻辑删除、分页等操作。

官网:https://baomidou.com/

<font face=" 楷体,华文行楷,隶书,黑体" color=red size=4>哈喽,大家好,/我是有勇气的牛排/(全网同名)🐮🐮🐮</font>

<font face=" 楷体,华文行楷,隶书,黑体" color=blue size=4>有问题的小伙伴欢迎在文末/评论,点赞、收藏/是对我最大的支持!!!。</font>

官网:https://www.couragesteak.com/

特性

支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

2 开发环境

JDK: JDK8+

构建工具:mavem

MySQL:5.7

Spring Boot

MyBatis-Plus:3.5.1

3 数据库准备

创建表

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, "有勇气的牛排1", 18, "test1@couragesteak.com"),
(2, "有勇气的牛排2", 19, "test2@couragesteak.com"),
(3, "有勇气的牛排3", 20, "test3@couragesteak.com"),
(4, "有勇气的牛排4", 21, "test4@couragesteak.com"),
(5, "有勇气的牛排5", 22, "test5@couragesteak.com");

image.png

4 BaseMapper 接口

4.1 案例 selectList

pojo/entity:UserEntity

@Data
public class UserEntity {
    private Long id;		// 默认为雪花算法
    private String name;
    private Integer age;
    private String email;
}

mapper:UserMapper.java(接口)

// 继承 MyBatis-Plus的 BaseMapper<泛型>
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<UserEntity> {
    
}

Controller:TestController.java

@RestController
public class TestController {

    @Autowired
    private UserMapper userMapper;

    // http://127.0.0.1:8080/test
    @RequestMapping("/test")
    public String testSelectList(){
        
        // 通过条件构造器,查询一个list集合,若没有条件,则可以设置null为参数
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);

        return "666";
    }

}

测试方法==Controller

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: MyBatisPlusTest.java
 * desc     :
 * */

package com.couragesteak;

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

import java.util.List;

@SpringBootTest
public class MyBatisPlusTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList(){
        System.out.println("666");
        // 通过条件构造器,查询一个list集合,若没有条件,则可以设置null为参数
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);
        
    }
}

image.png

4.2 插入方法 insert

@Test
public void testInsert() {
    User user = new User();
    // user.setId(6);
    user.setName("有勇气的牛排6");
    user.setAge(18);
    user.setEmail("test6@couragesteak.com");

    int result = userMapper.insert(user);
    System.out.println(result);

    System.out.println("result: " + result);
    System.out.println("result: " + user.getId());

}

4.3 删除方法 delete

4.3.1 deleteById

删除id=1637055722611748866的用户

DELETE FROM user WHERE id=?
// 通过id删除用户信息
int result = userMapper.deleteById(1637055722611748866L);
System.out.println("result: " + result);

4.3.2 deleteBatchIds 批量删除

DELETE FROM user WHERE id IN ( ? , ? , ? )
// 通过多个id实现批量删除
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result: " + result);

4.3.3 deleteByMap 按照条件删除

DELETE FROM user WHERE name = ? AND age = ?
// 根据map集合中所设置的条件删除用户信息
Map<String, Object> map = new HashMap<>();
map.put("name", "有勇气的牛排6");
map.put("age", 22);

int result = userMapper.deleteByMap(map);
System.out.println("result: " + result);

4.4 update 修改信息

UPDATE user SET name=?, email=? WHERE id=?
// 修改用户信息
User user = new User();
user.setId(8L);
user.setName("牛排哥");
user.setEmail("test1@couragesteak.com");

int result = userMapper.updateById(user);
System.out.println("result: " + result);

image.png

4.5 查询功能

4.5.1 selectById

通过id查询用户信息

SELECT id,name,age,email FROM user WHERE id=?
// 通过id查询用户信息
User user = userMapper.selectById(1L);
System.out.println(user);

image.png

4.5.2 selectBatchIds 批量查询

SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
// 根据多个id查询多个用户信息
List<Long> list = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(list);
users.forEach(System.out::println);

image.png

4.5.3 selectByMap 条件查询

SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
// 根据map集合中的条件查询用户信息
Map<String, Object> map = new HashMap<>();
map.put("name", "牛排哥");
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);

image.png

4.5.4 selectList

SELECT id,name,age,email FROM user
// 查询所有数据
// SELECT id,name,age,email FROM user
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);

4.6 MyBatis 自定义Mapper查询方法

时间久了还真会忘,下面一起回顾下

创建映射

classpath: mapper/UserMapper.xml

<?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">

<mapper namespace="com.couragesteak.mapper.UserMapper">

    <!-- Map<String, Object> selectMapById(Long id); -->
    <select id="selectByMapId" resultType="map">
        select id,name,age,email from user where id = #{id}
    </select>

</mapper>

UserMapper.java 接口

// 继承 MyBatis-Plus的 BaseMapper<泛型>
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<User> {

    /**
     * 自定义查询: 根据id查询用户信息为map集合
     * 映射文件: classpath: mapper/UserMapper.xml
     * @param id
     * @return
     */
    Map<String, Object> selectByMapId(Long id);

}

调用

@SpringBootTest
public class MyBatisPlusTest {

    @Autowired
    private UserMapper userMapper;
 
    @Test
    public void testSelect() {
        // 自定义查询方法
        Map<String, Object> map = userMapper.selectByMapId(1L);
        System.out.println(map);
    }
}

image.png

5 Service CRUD接口

5.1 创建自己的Service

这里我们通过继承 MyBatisPlus的IService,来实现我们自己的Serice

com.couragesteak.service: sercice接口

package com.couragesteak.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.couragesteak.pojo.User;

/**
 * UserService 继承 IService 模板提供的基础功能
 * IServcice<泛型为-实体类对象>
 */
public interface UserService extends IService<User> {

}

com.couragesteak.service.Impl: sercice接口实现类

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: UserServiceImpl.java
 * desc     : Service实现类
 * */

package com.couragesteak.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.couragesteak.mapper.UserMapper;
import com.couragesteak.pojo.User;
import com.couragesteak.service.UserService;

/**
 * 1. 继承 ServiceImpl<M:当前接口类型, T:当前实体类型>
 * 2. 实现自己的接口 UserSercice
 */

@Service    // 将Sercice标记为组件
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

image.png

5.2 save添加

5.2.1 saveBatch 批量添加

// 批量添加:单个sql语句循环添加
// INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
ArrayList<User> list = new ArrayList<>();
for (int i = 0; i <= 10; i++) {
    User user = new User();
    user.setName("有勇气的牛排" + i);
    user.setAge(18 + i);
    list.add(user);
}

boolean b = userService.saveBatch(list);
System.out.println(b);

6 常用注解

6.1 @TableName 指定表

在上文的实际测试中,我们通过MyBatis-Plus实现了基本的CRUD,并没有指定要操作的表,仅仅在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表。

由此,我们可以得出,MyBatis-Plus在确定操作的表时,通过BaseMapper的泛型决定,即实体类型决定,并且默认操作表名和实体类名一致。

@TableName("c_user")    // 设置实体类对应的表名
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

6.2 全局配置指定表前缀

如果表前缀统一,通过配置此操作,可以省去6.1节中,为每个表添加@TableName注解。

mybatis-plus:
  # 设置MyBatis-Plus的全局配置
  global-config:
    db-config:
      # 设置实体类所对应的表的统一前缀
      table-prefix: "c_"
  

6.3 @Tableld

6.3.1 主键配置

MyBatis-Plus在实现CRUD时,会默认将id作为主键,并且在插入数据时,默认基于雪花算法的策略生成id。

public class User {
    // 将属性对应的字段uid指定为主键
    @TableId
    private Long uid;
    private String name;
}

@TableId注解的value属性,用于指定主键的字段

public class User {
    // 将属性id, 对应到表uid
    @TableId(value = "uid")
    private Long id;
    private String name;
}

type类型有:

AUTO(0): 数据库ID自增(确保数据库设置自增)
NONE(1): 用户输入ID
INPUT(2): 分配ID
ASSIGN_ID(3)(默认): 分配ID(主键类型为number/string): 雪花算法(与数据库是否自增无关)
ASSIGN_UUID(4): 分配ID(主键类型为string): 分配UUID

public class User {
    // @TableId的value属性,用于设置主键的字段
    // @TableId的type属性,用于设置主键生成策略
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
}

6.3.2 雪花算法

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

1. 核心思想

(1)长度攻64bit(一个long型)

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

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

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

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

image.png

2. 优点

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

6.4 @TableFild 指定属性对应字段名

(1)MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段包吃包一致。

(2)情景一:如果不一致,MyBatis-Plus会将 驼峰命名与下划线命名自动转换。

(3)情景二,如果情景一不成立,则需要使用 @TableFild("表字段名")来设置对应字段。

public class User {
    // @TableId的value属性,用于设置主键的字段
    // @TableId的type属性,用于设置主键生成策略
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
	
    // 指定属性所对应的字段名
    @TableField("u_name")
    private String name;
}

6.5 @TableLogic 逻辑删除

物理删除:真实删除,将对应数据从数据中删除。

逻辑删除:假删除,将对应数据中代表是否删除状态的字段,标记为删除状态。

应用场景:可以进行数据恢复。

实体类

@TableName("c_user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @TableField("u_name")
    private String name;
    private Integer age;
    private String email;
	
    // 开启逻辑删除
    @TableLogic
    private Integer isDeleted;
}

开启逻辑删除后,delete将会变为update

// 通过多个id实现批量删除
// 未开启逻辑删除:DELETE FROM user WHERE id IN ( ? , ? , ? )
// 开启逻辑删除:UPDATE c_user SET is_deleted=1 WHERE id IN ( ? , ? , ? ) AND is_deleted=0
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result: " + result);

查询语句也会发生变化

// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);

7 条件构造器

7.1 QueryWrapper

7.1.1 组装查询条件

场景:查询用户名包含9,年龄在18~20,邮箱部位null的用户信息

@Test
public void test01() {
    // SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (u_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("u_name", "9")    // (字段名, 值)
            .between("age", 18, 22)
            .isNotNull("email");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

image.png

7.1.2 组装排序条件

场景:查询用户信息,按照年龄降序,若年龄相同,则按照id升序排序

// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
        .orderByAsc("id");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

image.png

7.1.3 组装删除

删除邮箱地址为null的用户信息

// UPDATE c_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result:" + result);

7.1.4 and/or 条件优先级

将 (age>20 且 u_name 包含9) 或 email=null 的用户信息修改

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 18)
        .like("u_name", "9")
        .or()
        .isNull("email");

User user = new User();
user.setName("牛排哥");

int result = userMapper.update(user, queryWrapper);
System.out.println("result: " + result);

将 u_name包含9,且(age>20 或 email=null)的用户信息修改

// 将 u_name包含9,且(age>18 或 email=null)的用户信息修改
// // UPDATE c_user SET u_name=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
// lambda中的条件优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("u_name","9")
        .and(i->i.gt("age",18).or().isNull("email"));

User user = new User();
user.setName("有勇气的牛排博客");
int result = userMapper.update(user, queryWrapper);
System.out.println("result: " + result);

7.1.5 组装select 设置要查询的字段

查询 用户名、年龄、邮箱

// SELECT u_name,age,email FROM c_user WHERE is_deleted=0
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("u_name", "age", "email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);

image.png

7.1.6 实现子查询

查询 id<=3 的用户信息

# 子查询案例
select * from user
where id in(
	select uid from user where id <=3
)
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (id IN (select id from c_user where id <= 3))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id","select id from c_user where id <= 3");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

7.2 UpdateWrapper 设置条件、字段

UpdateWrapper 不仅可以设置条件,还可以设置字段

场景:将 u_name包含9,且(age>18 或 email=null)的用户信息修改

// UPDATE c_user SET u_name=?,email=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 条件
updateWrapper.like("u_name", "9")
        .and(i -> i.gt("age", 18).or().isNull("email"));
// 修改(相对于QueryWrapper,这里不在创建实体类对象)
updateWrapper.set("u_name", "牛排哥").set("email", "my@couragesteak.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result: " + result);

7.3 condition

在开发中,组装条件是最常见的功能,而这些条件数据来源于用户,是可选的,因此我们需要判断这些条件,若没有选择则不需要组装,以免影响SQL结果。

动态SQL,(这种组并不好)

String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;

// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// com.baomidou.mybatisplus.core.toolkit.StringUtils;
if (StringUtils.isNotBlank(username)) {
    // 判断某个字符串是否不为空、不为null、不为空白符
    queryWrapper.like("u_name", username);
}

if (ageBegin != null) {
    queryWrapper.ge("age", 18);  // ge 表示 >=
}

if (ageEnd != null) {
    queryWrapper.le("age", 18);  // le 表示 <=
}

List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

通过condition判断,将会很大程度上简化代码

String username = "9";
Integer ageBegin = 18;
Integer ageEnd = 30;

// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (u_name LIKE ? AND age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "u_name", username)
        .ge(ageBegin != null, "age", ageBegin)
        .le(ageEnd != null, "age", ageEnd);

List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

image.png

但是,为了避免字段写错,因此可以使用 LambdaQueryWrapper

7.4 LambdaQueryWrapper 可访问实体字段, 避免写错

String username = "9";
Integer ageBegin = 18;
Integer ageEnd = 30;

// LambdaQueryWrapper<泛型为:实体类类型>
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> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

7.5 LambdaUpdateWrapper

// UPDATE c_user SET u_name=?,email=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
// 条件
updateWrapper.like(User::getName, "9")
        .and(i -> i.gt(User::getAge, 18).or().isNull(User::getEmail));
// 修改(相对于QueryWrapper,这里不在创建实体类对象)
updateWrapper.set(User::getName, "牛排哥").set(User::getEmail, "my@couragesteak.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result: " + result);

8 插件

8.1 分页插件

配置类: MyBatisPlusConfig.java

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: MyBatisPlusConfig.java
 * desc     :
 * */

package com.couragesteak.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.couragesteak.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 添加具体插件: 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;

    }
}

测试

// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 LIMIT ?,?
        
Page<User> page = new Page<>(2, 3);
userMapper.selectPage(page, null);

System.out.println(page);
System.out.println("======");
System.out.println(page.getRecords());
System.out.println("当前页面: " + page.getCurrent());        // 当前页面
System.out.println("当前 查询 记录数: " + page.getSize());           // 查询 记录数
System.out.println("总页数: " + page.getPages());            // 总页数
System.out.println("获取总记录数: " + page.getTotal());       // 获取总记录数
System.out.println("是否有下一页: " + page.hasNext());        // 是否有下一页
System.out.println("是否有上一页: " + page.hasPrevious());    // 是否有上一页

image.png

8.2 xml 自定义分页

场景:查询 age > 20的用户信息,并且分页

yml

mybatis-plus:
  # 配置类型别名所对应的包
  type-aliases-package: com.couragesteak.pojo

UserMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.couragesteak.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.Map;
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<User> {

    Map<String, Object> selectByMapId(Long id);

    // 自定义分页 返回值 必须为Page对象
    Page<User> selectPageVo(@Param("page") Page<User> page, @Param("page") Integer age);

}

UserMapper.xml

<!-- Page<User> selectPageVo(@Param("page") Page<User> page, @Param("page") Integer age); -->
<select id="selectPageVo" resultType="User">
    select id,u_name,age,email from c_user where age > #{age}
</select>

测试

// select id,u_name,age,email from c_user where age > ? LIMIT ?

Page<User> page = new Page<>(1, 3);
// 查 age> 20 的,并且分页
userMapper.selectPageVo(page, 20);

System.out.println(page);
System.out.println("======");
System.out.println(page.getRecords());
System.out.println("当前页面: " + page.getCurrent());        // 当前页面
System.out.println("当前 查询 记录数: " + page.getSize());           // 查询 记录数
System.out.println("总页数: " + page.getPages());            // 总页数
System.out.println("获取总记录数: " + page.getTotal());       // 获取总记录数
System.out.println("是否有下一页: " + page.hasNext());        // 是否有下一页
System.out.println("是否有上一页: " + page.hasPrevious());    // 是否有上一页

image.png

8.3 乐观锁插件

8.3.1 模拟数据修改冲突

创建表

use mybatis_plus;

CREATE TABLE `c_product` (
	`id` bigint(20) NOT NULL COMMENT '主键ID',
    `name` varchar(30) DEFAULT NULL COMMENT '商品名称',
	`price` int(11) DEFAULT 0 COMMENT '价格',
	`version` int(50) DEFAULT 0 COMMENT '乐观锁版本号',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据

INSERT INTO c_product (id, NAME, price) VALUES (1, '网站开发', 2000);

创建实体

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

案例: 某商品售价100,先加50,再减30,但是小王、小李同时读取数据库,进行修改,导致小李修改的结果被小王的覆盖成为70。(应为120)

// 小李 查询 商品价格
Product product_li = productMapper.selectById(1);
// 小王 查询 商品价格
Product product_w = productMapper.selectById(1);


System.out.println("小李查询的商品价格:" + product_li.getPrice());
// 小李查询的商品价格: 2000
System.out.println("小李查询的商品价格:" + product_w.getPrice());
// 小王查询的商品价格: 2000


// 小李将商品价格 +50
product_li.setPrice(product_li.getPrice() + 50);
productMapper.updateById(product_li);
// 小王将商品价格 -30
product_w.setPrice(product_w.getPrice() - 30);
productMapper.updateById(product_w);

// 老板查询商品价格
Product product_boss = productMapper.selectById(1);
System.out.println("老板查询的商品价格:" + product_boss.getPrice());
// 老板查询的商品价格:70

8.3.2 乐观锁实现流程

在表中添加 version 字段,取出记录时获取当前version。

SELECT id,`name`,price,`version` FROM c_product WHERE id=1

更新时,version+1,如果where语句中的version版本不对,则更新失败。

UPDATE c_product SET price=price+50, `version`=`version`+1 WHERE id=1 AND 
`version`=1

8.3.3 MyBatis-Plus实现乐观锁

1、修改实体类

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: Product.java
 * desc     : 乐观锁版本
 * */

package com.couragesteak.pojo;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
@TableName("c_product")
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version    // 标识乐观锁版本号字段
    private Integer version;
}

2、添加乐观锁插件配置

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: MyBatisPlusConfig.java
 * desc     : 插件配置
 * */

package com.couragesteak.config;

...

@Configuration
@MapperScan("com.couragesteak.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 添加具体插件: 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
}

3、下面可以执行8.3.1中的案例,进行测试

4、优化流程 :失败重试

// 小李 查询 商品价格
Product product_li = productMapper.selectById(1);
// 小王 查询 商品价格
Product product_w = productMapper.selectById(1);


System.out.println("小李查询的商品价格:" + product_li.getPrice());
// 小李查询的商品价格: 2000
System.out.println("小李查询的商品价格:" + product_w.getPrice());
// 小王查询的商品价格: 2000

// 小李将商品价格 +50
product_li.setPrice(product_li.getPrice() + 50);
productMapper.updateById(product_li);

// 小王将商品价格 -30
product_w.setPrice(product_w.getPrice() - 30);
int result = productMapper.updateById(product_w);
if (result==0){
    // 操作失败,重试
    Product productNew = productMapper.selectById(1);
    productNew.setPrice(productNew.getPrice()-30);
    productMapper.updateById(productNew);
}


// 老板查询商品价格
Product product_boss = productMapper.selectById(1);
System.out.println("老板查询的商品价格:" + product_boss.getPrice());
// 老板查询的商品价格:120

9 通用枚举

表中有些字段值是固定的,例如性别(男或女),此时我们可以使用MaBatis-Plus的通用枚举来实现。

image.png

yaml

mybatis-plus:
  # 扫描通用枚举的包
  type-enums-package: com.couragesteak.enums

枚举类: SexEnum.java

@Getter
public enum SexEnum {

    MALE(1,"男"),
    FEMALE(2,"女");

    private Integer sex;
    private String sexName;

    // 创建构造器
    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}

实体

@Data
@TableName("c_user")
public class User {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableField("u_name")
    private String name;

    private Integer age;
    private String email;

    @EnumValue  // 将注解所标识属性的值存储到数据库中
    private SexEnum sex;

    @TableLogic
    private Integer isDeleted;
}

解决报错:

报错: Cause: java.sql.SQLException: Incorrect integer value: 'MALE' for column 'sex' at row 1

show variables like '%char%';

set character_set_server=utf8mb4;

ALTER TABLE `c_user` MODIFY COLUMN `sex`  text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

10 代码生成器

依赖

<!-- MyBatis-Plus 代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>
<!-- freemarker模板 -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

快速生成代码: FastAutoGeneratorTest.java

/*
 * @Author  : 有勇气的牛排(全网同名)
 * @FileName: FastAutoGeneratorTest.java
 * desc     :
 * */

package com.couragesteak;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

public class FastAutoGeneratorTest {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false", "root", "root123456")
                .globalConfig(builder -> {
                    builder.author("有勇气的牛排") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("E://java//MyBatisPlus//tmp"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.couragesteak") // 设置父包名
                            .moduleName("mybatisplus") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "E://java//MyBatisPlus//tmp")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("c_user") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

12 多数据源

适用于多种场景:纯粹多库、读写分离、一主多从、混合模式等。

创建两个库:mybatis_plus、mybatis_plu_1(新建),将mybatis_plus库的product表移动到mybatis_plu_1库,通过一个测试用例,分别获取用户数据商品数据,如果获取到说明,多库模拟成功。

引入依赖

<!-- 多数据源 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

yml配置

spring:
  # 配置数据源信息
  datasource:
    dynamic:
      # 设置默认的数据源 或者 数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root123456
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root123456

指定数据源,可以是实体,也可以是Service

UserServiceImpl实现类

@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
}

ProductServiceImpl 实现类

@Service
@DS("slave_1")  //指定所操作的数据源
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
    
}

13 MyBatisX插件

idea安装插件:MyBatisX

官方文档:https://baomidou.com/pages/ba5b24/#功能

image.png

13.1 生成实体、mapper、service、映射文件

需要连接数据源

image.png

image.png

image.png

13.2 生成 insert语句

生成MyBatisSQL

image.png

image20230320213123587.png

image.png

13.3 生成delete语句

插入

image20230320213223147.png

删除

# 根据ID和用户名删除
deleteByIdAndName

int deleteByIdAndName(@Param("id") Long id, @Param("name") String name);

<delete id="deleteByIdAndName">
    delete
    from c_product
    where id = #{id,jdbcType=NUMERIC}
    AND name = #{name,jdbcType=VARCHAR}
</delete>

这也太厉害这

查询

// 按照年龄降序
selectAllOrderByAgeDesc

[1] 感谢尚硅谷

标签:

×
×