有勇气的牛排博客

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

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

前言

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

官网:https://baomidou.com/

哈喽,大家好,/我是有勇气的牛排/(全网同名)🐮🐮🐮

有问题的小伙伴欢迎在文末/评论,点赞、收藏/是对我最大的支持!!!。

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

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

支持数据库

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

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

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>

这也太厉害这

  • 多个条件用And连接即可

查询

// 按照年龄降序 selectAllOrderByAgeDesc

[1] 感谢尚硅谷


留言

专栏
文章
加入群聊