文章目录
MyBatisPlus学习笔记前言1、MyBatisPlus概述2、快速体验3、CRUD接口3.1 Mapper层CRUD接口3.1.1 Insert3.1.2 Delete3.1.3 Update3.1.4 Select
3.2 Service层CRUD接口3.2.1 Save3.2.2 Remove3.2.3 Update3.2.4 Get
3.3 自定义SQL接口4、常用注解和配置4.1 @TableId4.2 @TableField4.3 @TableLogic
5、条件构造器和常用接口5.1 QueryWrapper5.2 UpdateWrapper5.3 使用Condition动态组装条件5.4 LamdaQueryWrapper5.5 LamdaUpdateWrapper
6、插件6.1 MyBatisPlus分页插件6.2 悲观锁与乐观锁6.3 MyBatisX插件
7、通用枚举8、代码生成器8.1 快速生成8.2 交互式生成
9、多数据源
MyBatisPlus学习笔记
前言
本文是参考MyBatisPlus官网对MyBatisPlus的一个学习笔记,主要是对MyBatisPlus的一个简单的入门学习,大致对MyBatisPlus有一个整体认知,熟悉使用MyBatisPlus提供的各种API(比如MyBatisPlus提供的增删改查接口),以及各种便利的特性和插件(比如自动生成代码、MyBatisPlus分页插件)。当然主要目的是想利用费曼学习方法将学到的知识进行一个输出,这样能够加深我对知识有一个更加深刻的认知,因为这些知识在官方是都能够查看到的,我用自己的认知、自己的排版对这些知识进行重复输出。
1、MyBatisPlus概述
MyBatisPlus是什么?
MyBatisPlus(简称MP)是MyBatis的增强版,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。和MyBatis一样是一款数据访问层的框架,可以大大简化对SQL的操作
官方文档地址:MyBatis-Plus Gitee开源地址:mybatis-plus: mybatis GitHub开源地址:baomidou/mybatis-plus 创始人:青苗
MyBatisPlus的t特点:
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作 支持的数据库
国外:MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift国内:达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库 框架结构
2、快速体验
#mermaid-svg-wEwO7RlaQNDn36ZG {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .error-icon{fill:#552222;}#mermaid-svg-wEwO7RlaQNDn36ZG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wEwO7RlaQNDn36ZG .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-wEwO7RlaQNDn36ZG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wEwO7RlaQNDn36ZG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wEwO7RlaQNDn36ZG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wEwO7RlaQNDn36ZG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wEwO7RlaQNDn36ZG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wEwO7RlaQNDn36ZG .marker.cross{stroke:#333333;}#mermaid-svg-wEwO7RlaQNDn36ZG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wEwO7RlaQNDn36ZG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .cluster-label text{fill:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .cluster-label span{color:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .label text,#mermaid-svg-wEwO7RlaQNDn36ZG span{fill:#333;color:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .node rect,#mermaid-svg-wEwO7RlaQNDn36ZG .node circle,#mermaid-svg-wEwO7RlaQNDn36ZG .node ellipse,#mermaid-svg-wEwO7RlaQNDn36ZG .node polygon,#mermaid-svg-wEwO7RlaQNDn36ZG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wEwO7RlaQNDn36ZG .node .label{text-align:center;}#mermaid-svg-wEwO7RlaQNDn36ZG .node.clickable{cursor:pointer;}#mermaid-svg-wEwO7RlaQNDn36ZG .arrowheadPath{fill:#333333;}#mermaid-svg-wEwO7RlaQNDn36ZG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wEwO7RlaQNDn36ZG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wEwO7RlaQNDn36ZG .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-wEwO7RlaQNDn36ZG .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-wEwO7RlaQNDn36ZG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wEwO7RlaQNDn36ZG .cluster text{fill:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG .cluster span{color:#333;}#mermaid-svg-wEwO7RlaQNDn36ZG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wEwO7RlaQNDn36ZG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
搭建环境
编写配置文件
编写实体类
编写Mapper
编写测试类
扫描Mapper
Step1:搭建环境 1)建表(使用的是MySQL) 建立一张 user 表 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)
);
往表中添加数据: DELETE FROM user;
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)编写配置文件 application.yml server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC
username: root
password: 32345678
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志
Step2:编写实体类 @Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
Step3:编写Mapper package com.hhxy.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hhxy.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper
/*
说明: BaseMapper
BaseMapper是MP封装了通用CRUD的接口,Mapper只需要集成BaseMapper
T为任意实体类,只要让Mapper集成BaseMapper并且关联对应的实体类,就能使用Mapper对象操作实体类对应的表了
*/
}
Step4:扫描Mapper 在SpringBoot的启动类上添加**@MapperScan("com.hhxy.mapper")**注解 PS:如果我们在Mapper接口上添加了@Mapper注解,则这个注解可以省略 Step5:编写测试类 @SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List
Assertions.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
}
知识拓展:关于MP中的SQL是如何确定要操作的表是谁
我们在使用MP时,我们自己编写的Mapper必须继承BaseMapper,通过这一步,我们MP底层会自动将T映射为SQL操作表,举个例子吧:当我们的T是User时,那么SQL操作的表就是user
那如果数据库中的表是tb_user,如何确保SQL操作的表是tb_user呢?
方案一:最直接的方法肯定是修改entity的格式,比如可以将entity修改成tb_user、TbUser、tbUser、Tb_User、Tb_User。 方案二:通过@TableName注解映射表名,比如@TableName("tb_user") 方案二:通过编写配置文件,给user添加一个前缀 mybatis-plus:
global-config:
db-config:
table-prefix: tb_
需要注意的是,通过配置文件是全局有效的,也就是所有的实体类都会增加一个前缀tb_
3、CRUD接口
MP在MyBatis的基础上做了增强,底层封装了大量通用的SQL,主要有BaseMapper
insert、delete、update的返回值都是int类型,返回值为0说明无数据更新,此时SQL执行失败save、remove的返回值都是boolean类型,返回值为false说明SQL执行失败
3.1 Mapper层CRUD接口
参数说明:
类型参数名描述Tentity实体对象Wrapperwrapper实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)Collection extends Serializable>idList主键 ID 列表(不能为 null 以及 empty)Serializableid主键 ID(可以是任何实现了序列化接口的数据类型)Map
备注:基本数据类型的包装类都实现了序列化接口,String也实现了序列化接口
3.1.1 Insert
int insert(T entity):插入一条记录,返回值为更新数据的条数
@Test
public void testInsert(){
User user = new User("张三", 23, "123@qq.com");
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
int result = userMapper.insert(user);
System.out.println("insert = " + result); // result = 1
// MP很贴心,它会将Insert后自动生成的id,重新赋值给user对象(不用向MyBatis还要进行配置才能获取生成id)
System.out.println("MP自动生成的id为: "+user.getId());
}
备注:不止是BaseMapper的Insert方法能够通过getId直接获取自动生成的Id,IService的save方法也能够通过getId直接获取自动生成的Id。总的来讲MP真的简化了MyBatis,让开发变得更简单
3.1.2 Delete
int deleteById(Serializable id):根据id删除一条记录,返回值为更新数据的条数 int deleteById(T entity):根据entity的id删除一条记录,返回值为更新数据的条数 注意事项:entity必须具有getId()这个方法,否则会报错 PS:其实可以通过@TableId配置自定义的主键,详情键第四节【常用注解和配置】 int deleteByMap(Map
int result = 0;
// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(1613825708092678146L);
User user = new User(1L,"张三", 23, "123@qq.com");
// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(user);
Map
map.put("name", "张三");
map.put("age", 23);
// DELETE FROM user WHERE name=? AND age=?;
result = userMapper.deleteByMap(map);
List
// DELETE FROM user WHERE id IN (?, ?, ?);
result = userMapper.deleteBatchIds(idList);
3.1.3 Update
int updateById(T entity):根据id进行修改 entity必须具有getId()这个方法,否则会报错 int update(T entity, Wrapper
int result = 0;
User user = new User(1L,"张三", 23, "123@qq.com");
// UPDATE user SET name=?, age=?, email=? WHERE id=?;
result = userMapper.updateById(user);
3.1.4 Select
T selectById(Serializable id):根据id进行查询 List
// SELECT id,name,age,email FROM user WHERE id=?;
User user = userMapper.selectById(1L);
List
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? );
List
Map
map.put("name", "张三");
map.put("age", 23);
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?;
List
// SELECT id,name,age,email FROM user;
List
3.2 Service层CRUD接口
类型参数名描述intbatchSize插入批次数量Tentity实体对象WrapperupdateWrapper实体对象封装操作类 UpdateWrapperCollectionentityList实体对象集合Collection extends Serializable>idListidListFunction super Object, V>mapper转换函数
3.2.1 Save
boolean save(T entity):新增一条记录 boolean saveBatch(Collection
使用saveBatch,最好在数据库连接的url中添加一个rewriteBatchedStatements=true参数,实现高性能的批量插入 使用saveBatch,底层使用了事务,执行多条新增只会提交一次事务;但是如果在for循环中使用,会提交多次事务(不建议在循环中使用saveBatch方法) saveBatch(Collection entityList)底层就是调用saveBatch(Colection entityList, int batchSize),只是前一个他设置了默认值batchSize=1000,也就是一个批次会插入1000条,超过一千条需要等待下一批次执行 备注:saveBatch底层是通过JDBC的executeBatch实现的。批次只针对增删改操作,它是指将执行SQL的语句的请求先存起来,等到达到一定数量,就当作一批发送给MySQL服务器,让MySQL服务器一次将这些SQL执行完,这样做可以避免频繁和MySQL数据库交互,造成效率低下
参考文章:
MyBatis-plus批量写入数据方法saveBatch速度很慢原因排查 Mybatis-Plus批量插入的简单自测Mybatis Plus saveBatch批量插入如何高效Java-Mysql之批处理_veejaLiu的博客-CSDN博客什么是批处理?
boolean saveBatch(Colection
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
User user = new User("张三", 22, "123@qq.com");
boolean result = userService.save(user);
List
for (int i = 0; i < 3; i++) {
User user = new User("张三", 18, "123@qq.com");
users.add(user);
}
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? ),( ?, ?, ?, ? ),( ?, ?, ?, ? );
boolean result = userService.saveBatch(users);
// 执行三次: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
boolean result = userService.saveBatch(users, 1);
3.2.2 Remove
boolean removeById(Serializable id):根据id删除数据boolean removeById(T entity):根据id删除数据boolean removeById(Serializable id, boolean useFill):boolean removeBatchByIds(Collection> list):更具id进行批量删除boolean removeBatchByIds(Collection> list, int batchSize):boolean removeBatchByIds(Collection> list, boolean useFill):boolean removeBatchByIds(Collection> list, int batchSize, boolean useFill):
3.2.3 Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection
// 根据ID 批量更新
boolean updateBatchById(Collection
3.2.4 Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper
// 根据 Wrapper,查询一条记录
T getOne(Wrapper
// 根据 Wrapper,查询一条记录
Map
// 根据 Wrapper,查询一条记录
// 查询所有
List
// 查询列表
List
// 查询(根据ID 批量查询)
Collection
// 查询(根据 columnMap 条件)
Collection
// 查询所有列表
List
// 查询列表
List
// 查询全部记录
List
// 查询全部记录
// 根据 Wrapper 条件,查询全部记录
List
// 根据 Wrapper 条件,查询全部记录
// 无条件分页查询
IPage
// 条件分页查询
IPage
// 无条件分页查询
IPage
// 条件分页查询
IPage
3.3 自定义SQL接口
这个和MyBatis中是高度重合的,我就不在这里继续赘述了,如果想了解可以参考这篇文章:初识MyBatisPlus
MP充分践行了它的理念:只做增强,不做修改
4、常用注解和配置
此外还有@TableName注解比较常用,这个已经在前面学习过了
4.1 @TableId
@TableId:用于映射主键 MP默认将id作为注解,如果数据库中主键非id,会报错。比如我们数据库中的注解为uid,实体类的字段也为uid,此时需要在实体类的uid上添加一个@TableId注解,告诉MP uid是主键
value属性 前面是数据库的主键为uid,实体类中的属性也是uid。但如果数据库中主键是uid,实体类中的主键是id呢?此时就需要使用@TableId注解的value属性了。具体使用方式: 给实体类的uid属性上添加@TableId(value = "uid"),只有value一个属性,还可以省略为@TableId(“uid”) type属性
IdType说明AUTO使用id自增策略(前提是数据库也要开启id自增)NONE不手动设置主键值,MP将默认给出一个 Long 类型的字符串作为主键INPUT手动设置主键值(不设置就没有)ASSIGN_ID使用雪花算法生成主键值ASSIGN_UUID使用uuid生成主键值MP的id生成策略默认是雪花算法,如果我们不想使用雪花算法,需要使用@TableId注解的type属性进行配置 给实体类的id属性上添加@TableId(type = idType.AUTO) 注意:手动设置id的优先级要高于MP的id生成策略
知识拓展:
拓展一:通过配置文件配置主键生成策略 mybatis-plus:
global-config:
db-config:
id-type: auto # assign_id、assign_uuid、input、none
拓展二:雪花算法 简介:雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的 主键的有序性 特点:全局唯一性(生成的id与当前时间相关)、递增性、高可用性(能够确保任何时候都能生成正确的id)、高性能(特别适合高并发场景) 核心思想: 长度共64bit(一个long型)。 首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负 数是1,所以id一般是正数,最高位是0。 41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。 10bit作为机器的ID(5个bit是数据中心,5bit的机器ID,可以部署在1024个节点)。 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID) 为了应对数据规模增长导致的压力,我们需要对数据库进行扩展。常见的扩展方式有:业务分库、主从复制、数据库分表 优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高 1)数据库分表 将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务 继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据, 如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进 行拆分。 单表数据拆分有两种方式:垂直分表和水平分表。示意图如下: 垂直分表:拆分字段。垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。 例如,前面示意图中的 nickname 和 description 字段,假设我们是一个婚恋网站,用户在筛选其他用 户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展 示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外 一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。 水平分表:拆分记录。水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以 作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000 万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。 但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性 能瓶颈或者隐患。 水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理 体验分表:以主键id为例 1)方式一:通过分段的方式分表。 选取适当的分段范围,比如可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中, 1000000 ~ 1999999 放到表2中,以此类推。 复杂点:分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会 导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适 的分段大小。 优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万, 只需要增加新的表就可以了,原有的数据不需要动 缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而 另外一个分段实际存储的数据量有 1000 万条 2)方式二:通过取模的方式分表。假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来 表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号 为 6 的子表中。 复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题 优点:表分布比较均匀 缺点:扩充新的表很麻烦,所有数据都要重分布
4.2 @TableField
前面我们遇到主键不一致,我们可以使用@TableId注解进行映射,如果我们的其它属性和表的字段不一致,我们则需要使用@TableField注解进行映射
情况一:MP自动映射驼峰和下划线
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格。例如实体类属性userName,表中字段user_name ,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格(这个在MyBatis中需要手动配置,而MP中则默认配置了)
情况二:使用@TableField注解映射字段
若实体类中的属性和表中的字段不满足情况一,例如实体类属性name,表中字段username,此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段
情况三:使用@TableField注解自动填充
在项目开发时,对于一些字段(比如:create_time、update_time……),每次执行添加、修改操作时,都需要去填充,如果使用注入的方式会显得比较麻烦,所以我们需要设置字段自动填充,当进行该操作时,就自动为这些字段进行赋值
字段自动填充主要使用到了@TableField注解的fill属性,该属性有以下取值:
Step1:环境搭建 …… Step2:创建实体类,并在实体类需要填充的字段添加上@TableField @TableField(fill = FieldFill.INSERT_UPDATE) // 当进行更新(插入、删除、修改)操作时,就会进行自动填充
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT) // 当进行插入操作时,就会进行自动填充
private LocalDateTime createTime;
Step3:编写元数据处理器,用于设置填充的数据 @Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时填充字段
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
if (metaObject.hasSetter("createTime")){
metaObject.setValue("createTime", LocalDateTime.now());
/* this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); */ //也可以使用这个,这是官方给出的自动填充方式
}
if (metaObject.hasSetter("updateTime")){
metaObject.setValue("updateTime", LocalDateTime.now());
}
}
/**
* 更新时填充字段(包括修改、删除)
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
if(metaObject.hasSetter("updateTime")){
metaObject.setValue("updateTime", LocalDateTime.now());
}
}
}
备注:如果实体类中只存在updateTime一个属性时,会报错,必须要同时具有updateTime和createTime两个属性(在实体类中添加@TableField注解进行自动填充,SpringBoot会自动调用MyMetaObjectHandler中所有的方法)。为了解决这个问题,我们可以添加一个if判断,判断该类是否具有该属性,然后再进行赋值操作。 注意事项
填充的数据类型要和实体类的数据类型保持一致,否则填充结果为null 注意区别官方给出的三个填充的方法 // 这个是通用的,插入和更新都可以使用 但是当字段存在值 的时候不进行填充
this.fillStrategy(metaObject, "createTime", LocalDateTime.now());
// 这个是insert的时候用的,插入的时候时候强制进行填充
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// update的时候使用,更新的时候强制进行填充
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
备注:以上方法都需要MyBatisPlus3.3.0版本才能进行使用,值得一提的是fillStrategy再3.3.0版本中有bug,需要使用更高的版本(PS:反正我现在使用的是MP3.5问题不大)
4.3 @TableLogic
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
如果我们想要被删除的数据能够恢复,就需要使用到逻辑删除
示例:
Step1:创建一个逻辑删除字段 is_deleted,同时设置默认值为0(0表示未删除,1表示删除) Step2:实体类中也添加一个逻辑删除的属性isDeleted Step3:给实体类的逻辑删除属性上添加一个@TableLogic注解 Step4:测试 int result = userMapper.deleteById(1613825708092678146L);
可以看到此时,删除的SQL并不是以前的DELETE FROM user WHERE id=?;了,而是换成了UPDATE tb_user SET is_deleted=1 WHERE id=? AND is_deleted=0; 要恢复逻辑删除的数据也能简单,直接在数据库中将is_deleted的值改成0就好了,需要注意的是逻辑删除后的数据,查询语句是无法查到的,同时update语句也是无法进行修改的
5、条件构造器和常用接口
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装UpdateWrapper : Update 条件封装AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询WrapperLambdaUpdateWrapper : Lambda 更新封装Wrapper
5.1 QueryWrapper
QueryWrapper 示例一:使用QueryMapper进行查询 @Test
public void test1() {
// 查询姓张的,年龄在20~30之间,并且email字段不为空的用户信息
QueryWrapper
queryWrapper.like("name", "张")
.between("age", 20, 30)
.isNotNull("email");
/*
SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND
(name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
*/
List
users.forEach(System.out::println);
}
进行指定字段查询: @Test
public void test6(){
// 查询指定字段(用户名、年龄、邮箱)
QueryWrapper
queryWrapper.select("name", "age", "email");
// SELECT name,age,email FROM user
List
maps.forEach(System.out::println);
}
使用QueryMapper实现子查询: @Test
public void test7(){
// 查询id小于100的用户信息
QueryWrapper
queryWrapper.inSql("id","select id from user where id < 100");
// SELECT id,name,age,email,is_deleted FROM user WHERE (id IN (select id from user where id < 100))
List
maps.forEach(System.out::println);
}
示例二:使用QueryMapper进行条件删除 @Test
public void test2(){
// 查询用户信息,按照年龄降序排序,如果年龄相同则按照id升序排序
QueryWrapper
queryWrapper.orderByDesc("age")
.orderByAsc("id");
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 ORDER BY age DESC,id ASC
List
users.forEach(System.out::println);
}
示例三:使用QueryMapper进行条件修改 @Test
public void test4(){
// 修改(年龄大于20并且姓张)或邮箱为null的用户信息
QueryWrapper
queryWrapper.gt("age", 20)
.like("name", "张")
.or() // QueryMapper的连接条件默认是 AND
.isNull("email");
User user = new User("张三", 22, "123@qq.com");
// UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)
userMapper.update(user, queryWrapper);
}
@Test
public void test5(){
// 修改年龄大于20并且(姓张或邮箱为null)的用户信息
QueryWrapper
queryWrapper.gt("age", 20)
.and(qw->qw.like("name", "张")
.or()
.isNull("email"));
User user = new User("张三", 22, "123@qq.com");
// UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND (name LIKE ? OR email IS NULL))
userMapper.update(user, queryWrapper);
}
5.2 UpdateWrapper
示例一:
@Test
public void test8(){
// 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息
UpdateWrapper
updateWrapper.like("name", "张")
.and(uw -> uw.gt("age", 20)
.or()
.isNull("email"));
updateWrapper.set("name", "李四").set("age", 22);
// UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
}
某个字段自增或自减,MyBatiPlusm目前只有两种方式实现
使用UpdateWrapper拼接 // 使用UpdateWrapper
UpdateWrapper wrapper = new UpdateWrapper();
wrapper.eq("id",【实体类对象】.getBid());
wrapper.setSql("'自增字段' = '自增字段' + 1"); // 注意SQL中没有 += 这个符号
【service对象】.update(wrapper);
// 直接调用update方法
【service对象】.update().
setSql("stock = stock -1")
.eq("voucher_id", voucherId)
.update();
直接写原生sql到xml中 略……
方式三:自定义一个自增自减字段的方法,参考文章:mybatis-plus 自定义UpdateWrapper(一)实现列自增
// 库存充足,秒杀券库存减一
// 方式一:使用Service对象的update方法
/*boolean flag = seckillVoucherService.update().
setSql("stock = stock -1")
.eq("voucher_id", voucherId)
.update();*/
// 方式二:使用LambdaUpdateWrapper的setSql方法
UpdateWrapper updateWrapper = new UpdateWrapper();
updateWrapper.eq("voucher_id", voucherId);
updateWrapper.setSql("stock = stock -1");
boolean flag = seckillVoucherService.update(updateWrapper);
// 方式三:使用自定义的LambdaUpdateWrapper
/*CustomLambdaUpdateWrapper
updateWrapper.descField(SeckillVoucher::getStock, 1)
.eq(SeckillVoucher::getVoucherId, voucherId);
boolean flag = seckillVoucherService.update(updateWrapper);*/
// 方式四:在xml中编写SQL语句
备注:相关完整代码请参考博主Gitee仓库中的hmdp项目
5.3 使用Condition动态组装条件
前面我们学习了QueryWrapper的常用方法,在这些方法的基础上,我们可以都可以通过添加一个condition条件,进行判断 是否需要添加改条件,这个类似于MyBatis中的动态SQL的标签,只有满足condition条件,才能够将改条件组装到SQL上
@Test
public void test9(){
// 模拟从前端接收参数
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
// 模糊查询姓某某的人,同时20 QueryWrapper queryWrapper.like(StringUtils.isNotBlank(username), "name", username) .ge(ageBegin != null, "age", ageBegin) .le(ageEnd != null, "age", ageEnd); // SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?) List users.forEach(System.out::println); } 5.4 LamdaQueryWrapper 前面我们使用QueryWrapper和UpdateWrapper时,字段都是自己来编写的,如果你尝试过就一定知道,自己通过字符串的形式编写是没有一点提示的,这样很容易出现字段名写错,导致SQL执行失败的情况。而MP的开发者也是知道这一点的,于是就提供了LamdaQueryWrapper,这样我们可以通过lamda表达式的形式确定字段名,这在IDEA中是有提示的,基本上可以杜绝字段写错的问题。这个完全可以替代QueryWrapper,前提是你的JDK必须大于1.8,因为Lamda表达式是JDK1.8引入的 #mermaid-svg-7Y106tXSNVAHn4hK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .error-icon{fill:#552222;}#mermaid-svg-7Y106tXSNVAHn4hK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7Y106tXSNVAHn4hK .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-7Y106tXSNVAHn4hK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7Y106tXSNVAHn4hK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7Y106tXSNVAHn4hK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7Y106tXSNVAHn4hK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7Y106tXSNVAHn4hK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7Y106tXSNVAHn4hK .marker.cross{stroke:#333333;}#mermaid-svg-7Y106tXSNVAHn4hK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7Y106tXSNVAHn4hK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .cluster-label text{fill:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .cluster-label span{color:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .label text,#mermaid-svg-7Y106tXSNVAHn4hK span{fill:#333;color:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .node rect,#mermaid-svg-7Y106tXSNVAHn4hK .node circle,#mermaid-svg-7Y106tXSNVAHn4hK .node ellipse,#mermaid-svg-7Y106tXSNVAHn4hK .node polygon,#mermaid-svg-7Y106tXSNVAHn4hK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7Y106tXSNVAHn4hK .node .label{text-align:center;}#mermaid-svg-7Y106tXSNVAHn4hK .node.clickable{cursor:pointer;}#mermaid-svg-7Y106tXSNVAHn4hK .arrowheadPath{fill:#333333;}#mermaid-svg-7Y106tXSNVAHn4hK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7Y106tXSNVAHn4hK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7Y106tXSNVAHn4hK .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-7Y106tXSNVAHn4hK .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-7Y106tXSNVAHn4hK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7Y106tXSNVAHn4hK .cluster text{fill:#333;}#mermaid-svg-7Y106tXSNVAHn4hK .cluster span{color:#333;}#mermaid-svg-7Y106tXSNVAHn4hK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7Y106tXSNVAHn4hK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建条件构造器 添加条件 执行SQL @Test public void test10(){ // 模拟从前端接收参数 String username = ""; Integer ageBegin = 20; Integer ageEnd = 30; // 模糊查询姓某某的人,同时20 LambdaQueryWrapper lambdaQueryWrapper.like(StringUtils.isNotBlank(username), User::getName, username) .ge(ageBegin != null, User::getAge, ageBegin) .le(ageEnd != null, User::getAge, ageEnd); // SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?) List users.forEach(System.out::println); } 5.5 LamdaUpdateWrapper 同样的这个也可以完全替代UpdateWrapper,推荐使用这个 #mermaid-svg-mrPqQrpGQo7yCVIc {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .error-icon{fill:#552222;}#mermaid-svg-mrPqQrpGQo7yCVIc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mrPqQrpGQo7yCVIc .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mrPqQrpGQo7yCVIc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mrPqQrpGQo7yCVIc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mrPqQrpGQo7yCVIc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mrPqQrpGQo7yCVIc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mrPqQrpGQo7yCVIc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mrPqQrpGQo7yCVIc .marker.cross{stroke:#333333;}#mermaid-svg-mrPqQrpGQo7yCVIc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mrPqQrpGQo7yCVIc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .cluster-label text{fill:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .cluster-label span{color:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .label text,#mermaid-svg-mrPqQrpGQo7yCVIc span{fill:#333;color:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .node rect,#mermaid-svg-mrPqQrpGQo7yCVIc .node circle,#mermaid-svg-mrPqQrpGQo7yCVIc .node ellipse,#mermaid-svg-mrPqQrpGQo7yCVIc .node polygon,#mermaid-svg-mrPqQrpGQo7yCVIc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mrPqQrpGQo7yCVIc .node .label{text-align:center;}#mermaid-svg-mrPqQrpGQo7yCVIc .node.clickable{cursor:pointer;}#mermaid-svg-mrPqQrpGQo7yCVIc .arrowheadPath{fill:#333333;}#mermaid-svg-mrPqQrpGQo7yCVIc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mrPqQrpGQo7yCVIc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mrPqQrpGQo7yCVIc .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-mrPqQrpGQo7yCVIc .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-mrPqQrpGQo7yCVIc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mrPqQrpGQo7yCVIc .cluster text{fill:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc .cluster span{color:#333;}#mermaid-svg-mrPqQrpGQo7yCVIc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mrPqQrpGQo7yCVIc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建条件构造器 添加条件 执行SQL @Test public void test11(){ // 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息 LambdaUpdateWrapper lambdaUpdateWrapper.like(User::getName, "张") .and(uw -> uw.gt(User::getAge, 20) .or() .isNull(User::getEmail)); lambdaUpdateWrapper.set(User::getName, "李四").set(User::getAge, 22); // UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL)) int result = userMapper.update(null, lambdaUpdateWrapper); System.out.println("result = " + result); } 6、插件 分页插件和乐观锁插件都是内置再MP中的,只需要配置以下就能使用了,而MyBatisX插件需要到插件商店中进行下载 6.1 MyBatisPlus分页插件 Step1:搭建环境 请参考第二节【快速体验】 Step2:创建MP配置类,配置拦截器 @Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ // 1、创建MP拦截器对象 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 2、将MP的分页插件添加到拦截器对象中 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } 备注:如果没有加@Configuration注解,则需要在启动类上添加@MapperScan注解扫描该类 Step3:测试 @Test public void testPage(){ // 创建分页构造器 Page // 执行SQL:分页查询所有条件(还可以使用QueryWrapper对象进行条件过滤) userMapper.selectPage(page, null); // 这里和前面获取生成id是一样的,MP将查询的数据重新赋值给了Page对象 System.out.println("当前页码 = " + page.getCurrent()); System.out.println("当前页展示的记录条数 = " + page.getSize()); System.out.println("当前页展示的记录: "); page.getRecords().forEach(System.out::println); System.out.println("分页查询的总页数 = " + page.getPages()); System.out.println("分页查询的总记录数 = " + page.getTotal()); System.out.println("是否存在上一页: " + page.hasPrevious()); System.out.println("是否存在下一页: " + page.hasNext()); } 知识拓展:XML自定义分页 如果在我们像自定义一个分页查询的SQL,并且需要使用MP内置的分页插件 UserMapper: /** * 通过年龄查询用户信息并进行分页 * @param page 如果想使用MP内置的分页插件,则分页对象必须放在第一个 * @param age * @return */ Page select id, name, age, email from user where age > #{age} 备注:这里配置了实体类的别名 测试结果: 6.2 悲观锁与乐观锁 在学习悲观锁和乐观锁之前,我们先模拟一个场景: 一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。 像上面那样,不加锁,就会导致操作并发时出现严重故障,所以我们对于这种事务性操作是一定要进行枷锁校验的,这样才能保证数据的一致性,不然系统的使用者蒙受巨大损失 乐观锁:对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。悲观锁:对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select … for update来实现悲观锁。需要注意的是使用悲观锁时,需要关闭数据库自动提交功能,即:set autocommit = 0; 如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。 示例一: 模拟数据冲突 数据冲突包括:丢失修改、不可重复读、脏读 Step1:环境搭建 1)建表 2)插入数据 3)导入依赖 4)编写配置文件 Step2:编码 1)编写实体类 2)编写Mapper Step3:测试 @Test public void testDataConflict(){ // 小李查看价格 Product p1 = productMapper.selectById(1L); System.out.println("小李取出的价格:" + p1.getPrice()); // 小王查看价格 Product p2 = productMapper.selectById(1L); System.out.println("小王取出的价格:" + p2.getPrice()); // 小李将价格加了50元,存入了数据库 p1.setPrice(p1.getPrice() + 50); int result1 = productMapper.updateById(p1); System.out.println("小李修改结果:" + result1); // 小王将商品减了30元,存入了数据库 p2.setPrice(p2.getPrice() - 30); int result2 = productMapper.updateById(p2); System.out.println("小王修改结果:" + result2); // 获取商品最终的结果 Product p3 = productMapper.selectById(1L); System.out.println("最后的结果:" + p3.getPrice()); } 示例二: 使用悲观锁解决数据冲突 Step1:搭建环境 略…… Step2:给表添加一个version字段。 Step3:在编写实体类时,使用@Version注解标记version属性。取出记录时,获取当前version;更新时,version + 1,如果where语句中的version版本不对,则更新失败 Step4:编写配置类 @Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } } Step5:测试 测试代码和示例一相同,略…… 小李进行操作时,此时小王还没进行更新操作,version字段不变;当小王操作时,因为前面小李进行了更新操作,所以version+1了,和小王获取的version不同,更新操作失败! 示例三: 使用悲观锁解决数据冲突 Step1:搭建环境 略…… Step2: 6.3 MyBatisX插件 MyBatisX插件主要具有以下功能 通过Mapper接口快速在Mapper配置文件中生成statement快速生成代码 7、通用枚举 表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举 来实现 Step1:搭建环境 1)建表(将sex的类型设置为int) 2)插入数据 3)导入依赖 4)编写配置文件 Step2:编码 1)编写枚举类 @Getter // 需要提供get方法,让外界能够获取到枚举对象 public enum SexEnum { MALE(1, "男"), FEMALE(2, "女"); @EnumValue // 将注解所标识的值存储到数据库中(不添加这个注解,则添加的值是MALE或FEMALE) private Integer sex; private String sexName; SexEnum(Integer sex, String sexName) { this.sex = sex; this.sexName = sexName; } } 备注:MP3.5.2版本以前(3.5.2开始就已经废弃了,不需要配置就能直接使用通用没觉了),还需要再配置文件中进行配置,才能成功使用通用枚举 2)编写实体类 @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; private SexEnum sex; } 3)编写Mapper Step3:测试 @Test public void testEnum(){ User user = new User("张三", 23, "123@qq.com", SexEnum.MALE); int result = userMapper.insert(user); System.out.println("result = " + result); } 8、代码生成器 在MyBatis中我们可以通过逆向工程快速生成代码,从而节省大量的时间,但是MyBatis的逆向工程配置起来较为麻烦,所以MP简化了配置,让代码生成变得更加简单,堪称码农神器。让我们不必过多地重复哪些CRUD操作,只专注于核心业务。 代码生成器模板结构: FastAutoGenerator.create("url", "username", "password") //2、全局配置 .globalConfig(...) //3、包配置 .packageConfig(...) //4、策略配置 .strategyConfig(...) //5、模板引擎配置 .templateEngine(...) //6、执行 .execute(); 8.1 快速生成 示例: Step1:搭建环境 1)建库、建表 2)创建SpringBoot项目 3)引入依赖 4)编写配置文件 # 配置端口 server: port: 8080 # Spring相关配置 spring: # 数据源相关配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC username: root password: 32345678 # MyBatisPlus相关配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志 type-aliases-package: com.hhxy.entity # 配置类型别名 Step2:编写生成器 注意:一定要使用绝对路径 public class FastAutoGeneratorTest { public static void main(String[] args) { // 1、配置数据源 FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC", "root", "32345678") // 2、全局配置 .globalConfig(builder -> { builder.author("ghp") // 设置作者 .enableSwagger() // 开启 swagger 模式 .fileOverride() // 覆盖已生成文件 .outputDir("/src/main/java"); // 指定输出目录 }) // 3、包配置 .packageConfig(builder -> { builder.parent("com.hhxy") // 设置父包名 .moduleName("") // 设置父包模块名 .pathInfo(Collections.singletonMap(OutputFile.xml, "/src/main/resources/mapper")); // 设置mapperXml生成路径 }) // 4、策略配置 .strategyConfig(builder -> { builder.addInclude("user") // 设置需要生成的表名(数据库中必须存在该表) .addTablePrefix("tb_", "t_", "tbl_"); // 设置过滤表前缀 }) // 5、模板引擎配置 .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 // 6、执行 .execute(); } } 备注:MyBatisPlus5.2开始,fileOverride方法被弃用了 Step3:运行方法 使用MyBatisX插件生成自定的SQL 我们需要通过insert、delete、update、select等关键次开头,IDEA会有提示 8.2 交互式生成 public class InteractiveAutoGeneratorTest { public static void main(String[] args) { Scanner scan = new Scanner(System.in); System.out.println("=====================数据库配置======================="); System.out.println("请输入 URL"); String url = scan.next(); System.out.println("请输入 username"); String username = scan.next(); System.out.println("请输入 password"); String password = scan.next(); FastAutoGenerator.create(url, username, password) // 全局配置 .globalConfig((scanner, builder) -> builder.author(scanner.apply("=====================全局配置=======================\n请输入作者名称")) .outputDir(System.getProperty("user.dir") + "/src/main/java") .commentDate("yyyy-MM-dd hh:mm:ss") .dateType(DateType.TIME_PACK) .enableSwagger() .fileOverride() .enableSwagger() .disableOpenDir() ) // 包配置 .packageConfig((scanner, builder) -> builder.parent(scanner.apply("=====================包配置=======================\n请输入包名?")) .moduleName(scanner.apply("请输入父包模块名?")) .entity("entity") .service("service") .serviceImpl("serviceImpl") .mapper("mapper") .xml("mapper") .other("utils") .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir")+"/src/main/resources/mapper")) ) // 策略配置 .strategyConfig((scanner, builder) -> { builder.addInclude(getTables(scanner.apply("=====================策略配置=======================\n请输入表名,多个参数使用逗号分隔"))) .serviceBuilder() .formatServiceFileName("%sService") .formatServiceImplFileName("%sServiceImpl") .entityBuilder() //实体类策略配置 .enableLombok() //开启 Lombok .disableSerialVersionUID() .logicDeleteColumnName("deleted") //逻辑删除字段 .naming(NamingStrategy.underline_to_camel) .columnNaming(NamingStrategy.underline_to_camel) .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE)) .enableTableFieldAnnotation() // 开启生成实体时生成字段注解 .controllerBuilder() .formatFileName("%sController") .enableRestStyle() .mapperBuilder() .superClass(BaseMapper.class) .formatMapperFileName("%sMapper") .enableMapperAnnotation() //@mapper .formatXmlFileName("%sMapper"); }) .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 .execute(); } // 处理 all 情况 protected static List return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); } } 9、多数据源 一个项目中经常会使用到多个数据库,比如主从复制,主库来进行更新操作,从库用来进行查询操作,从而有效降低数据库库的负担,一定程度提高系统的效率。 关于主从复制相关配置可以参考:MySQL学习笔记 多数据源主要存在以下三种配置方式: # 多主多从 纯粹多库(记得设置primary) 混合配置 spring: spring: spring: datasource: datasource: datasource: dynamic: dynamic: dynamic: datasource: datasource: datasource: master_1: mysql: master: master_2: oracle: slave_1: slave_1: sqlserver: slave_2: slave_2: postgresql: oracle_1: slave_3: h2: oracle_2: 主从复制相关配置: spring: # 配置数据源信息 datasource: dynamic: # 设置默认的数据源或者数据源组, 默认值即为master # primary: master # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源 # strict: false datasource: names: master,slave # 设置别名,可以随便取,但要与后面的前缀对应(可以配置多个从库) master: url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 slave: url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 @Service @DS("master") // 使用主库 public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; public List selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("slave") // 使用从库 public List selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } } 好文推荐
发表评论