自己学习于B站的 三更草堂

新的一年努力学习,明年2-3 月,高薪工作我来了~

MyBatis-Plus 轻松把握:

官网图标是一个 魂斗罗 示意:Mybatis 和 Plus就像兄弟一样,相辅相成

介绍:

MyBatis-Plus 简称 MP, 是一个MyBatis 的加强工具 官方网站

在 MyBatis 的根底上只做加强不做扭转,为简化开发、提高效率而生

  • MyBatis 大家都理解吧,对于Java开发者曾经是 妇孺皆知,ORM 对象关系映射的,半自动化,长久层的框架
  • MyBatis-plus 是国人研发,简化了 MyBatis 的开发代码...

个性:

  • 无侵入:只做加强不做扭转,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会主动注入根本 CURD,性能根本无损耗,间接面向对象操作
  • 反对自定义全局通用操作:反对全局通用办法注入( Write once, use anywhere )
  • 反对 Lambda 模式调用:通过 Lambda 表达式,不便的编写各类查问条件,无需再放心字段写错
  • 反对 ActiveRecord 模式:反对 ActiveRecord 模式调用,实体类只需继承 Model 类即可进行弱小的 CRUD 操作
  • 反对主键主动生成:反对多达 4 种主键策略(内含分布式惟一 ID 生成器 - Sequence),可自在配置,完满解决主键问题
  • 弱小的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过大量配置即可实现单表大部分 CRUD 操作,更有弱小的条件结构器,满足各类应用需要
  • 内置代码生成器,分页插件,反对多种数据库:MySQL Oracle DB2 HSQL
  • 内置性能剖析插件 可输入 Sql 语句以及其执行工夫,倡议开发测试时启用该性能,能疾速揪出慢查问
  • 内置全局拦挡插件:提供全表 delete 、 update 操作智能剖析阻断,也可自定义拦挡规定,预防误操作

SpringBoot 疾速入门:

筹备工作:

自己应用的数据库,.sql文件

CREATE TABLE `user` (  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',  `user_name` varchar(20) NOT NULL COMMENT '用户名',  `password` varchar(20) NOT NULL COMMENT '明码',  `name` varchar(30) DEFAULT NULL COMMENT '姓名',  `age` int(11) DEFAULT NULL COMMENT '年龄',  `address` varchar(100) DEFAULT NULL COMMENT '地址',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;insert  into `user`(`id`,`user_name`,`password`,`name`,`age`,`address`) values (1,'ruiwen','123','瑞文',12,'诺克萨斯'),(2,'gailun','1332','盖伦',13,'德玛西亚'),(3,'timu','123','提姆',22,'约德尔'),(4,'daji','1222','亚索',221,'艾欧尼亚');

创立一个SpringBoot 的 Maven 工程:

<img src="https://wsm540.oss-cn-beijing.aliyuncs.com/Java框架/MybatisPlusimage-20211230005147061.png" alt="image-20211230005147061" style="zoom:50%;" />

① 增加依赖

pom.xml

<!-- SpringBoot 的父依赖 --><parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.5.0</version></parent><!-- Maven 依赖 --><dependencies>    <!-- SpringBoot starter依赖 -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!-- SpringBoot test测试程序依赖; -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency>    <!-- Lombok 依赖,为了不便疾速加载实体类~ -->    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <optional>true</optional>    </dependency>    <!-- 应用Mybatis-plus 依赖;                加载了Mybatis-plus 依赖,就能够不须要Mybatis 依赖了, plus依赖中默认集成了 Mybatis依赖;        -->    <dependency>        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-boot-starter</artifactId>        <version>3.4.3</version>    </dependency>    <!-- mysql的驱动 -->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>    </dependency></dependencies>

② SpringBoot 启动类:

这里不过多的解释SpringBoot 了, 须要学习的小伙伴能够借鉴:Java_慈爱学习笔记

SpringBootRun.Java

//SpringBoot 启动类注解~@SpringBootApplicationpublic class SpringBootRun {    public static void main(String[] args) {        SpringApplication.run(SpringBootRun.class);    }}

③ entity 实体类:

com.wsm.entity 包下的实体类:User.Java

  • 次要是,提供JavaBean 与要查问的数据库,属性/列进行关联...
@Data@NoArgsConstructor@AllArgsConstructorpublic class User {    private Long id;    private String userName;    private String password;    private String name;    private Integer age;    private String address;}

④ mapper 接口文件:

com.wsm.mapper 包下的 xxxMapper 接口

  • MyBatis 提倡 面向接口编程 提供每一个实体类对应的接口,与对应的Mapper.xml 映射文件进行 sql 的实现;
  • 而,MyBatis-plus 对其进行了封装

    Mapper接口,extends继承 BaseMapper<T> 并通过 <T> 泛型指定对应的实体类... 使 xxxMapper接口, 领有BaseMapper的所有办法();

    MyBatis-plus 对其中的办法都有其对应的实现映射,所以,只须要 继承BaseMapper<T> 就实现了大量的罕用办法, 这就是MP的简略弱小之处,省去开发者的大量反复工作

    MP 提供了大量的办法, 各种的 CRUD

UserMapper.Java

@Mapper//@Mapper 注解,使以后的Mapper 接口,被Spring进行治理,不然须要在,启动类上申明 @MapperScan("com.wsm.mapper") 类扫描指定包下,mapper接口文件;public interface UserMapper  extends BaseMapper<User> {    //Mapper 接口 extends集成 BaseMapper<T> 泛型对应的实体类;    //  Ctrl+右击, 进入BaseMapper 中能够看到, MP 默认给对应实体类提供好的实现办法();    //  增删改查... 即各种的, 重载 CRUD 的操作;        //如果,BaseMapper<T> 中,没有提供的,前面还能够在,该 xxxMapper 文件中, 自定义本人须要的办法();}

⑤ SpringBoot 配置文件⚙:

application.yml 的语法结构,比拟清晰明了

spring:  # 配置SpringBoot 连贯的数据源,留神 这里的Mysql连贯 用户名 明码 要依据本人的理论状况来~  datasource:    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&serverTimezone=UTC    username: root    password: ok    driver-class-name: com.mysql.cj.jdbc.Driver

⑥ 运行测试:

,所有准备就绪运行SpringBoot 启动类:

  .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::                (v2.5.0)

启动胜利, 筹备Maven 测试类,进行测试程序:

Maven test模块下测试程序:

test模块,就Maven包下的专门用来测试程序的模块

com.wsm.MPTest.Java 测试程序,第一个MP程序:查问所有的User表

/* MP的测试类; **/@SpringBootTest//@SpringBootTest JUnit等其余测试框架联合起来,提供了便捷高效的测试伎俩.//应用@SpringBootTest后,Spring将加载所有被治理的bean,根本等同于启动了整个服务,此时便能够开始功能测试. 须要引入: spring-boot-starter-test 依赖;public class MPTest {    @Autowired    private UserMapper userMapper;    @Test    public void testSelectList(){        System.out.println("测试 selectList(null); 查问全副!");            // selectList(queryWrapper); 参数须要是一个 查问增加Wrapper; 不设置,则无条件查问全副!        List<User> users = userMapper.selectList(null);        users.forEach(System.out::println);    }}

运行♂️:

总结:

MyBatis-Plus是如此的简略,BaseMapper<T>接口类中,曾经默认集成了很多单表的 CRUD的操作,咱们只须要去调用即可 轻松的实现:增删改查

MP 罕用配置⚙:

MP 具备弱小的注解 / yml的全局配置 使框架具备更加弱小的性能!

表/实体 映射:@TableName

默认状况下MP操作的表名就是实体类的类名,然而如果表名和类名不统一就须要咱们本人设置映射规定

部分设置~独自,针对某些表~:

  • MP 能够通过 @TableName注解 进行,==Java实体 与 数据库之间的互相映射==

    注解在类上,指定类和数据库表的映射关系

    如果,实体类 类名——>转换 小写后——> 和数据库映射表雷同,能够不指定该注解; MP 默认就是这样映射的;

  • 这里文章中就不贴代码了, 能够私聊或自行下载... 这里介绍一下,验证思路:

    同样的库中,从新创立一个 除表名外,表构造雷同的,数据不同的表 通过 @TableName 来进行切换执行查看数据;

全局设置~表前缀~:

  • 下面的 @TableName 能够设置,数据库与实体的 表名进行映射

    个别一个项目表名的前缀都是对立格调的,这个时候如果一个个设置就太麻烦了。咱们能够通过配置来设置全局的表名前缀

  • 例如:

    如果一个我的项目中所有的表名相比于类名都是多了个前缀: tb_ 这能够应用如下形式配置:

    # MP 参数设置:mybatis-plus:  global-config:    db-config:      # 设置数据库映射 实体时候增加的 前缀;      table-prefix: tb_

    这样,MP 在通过实体映射 数据库表的时候,会在后面主动增加 tb_,能够大量节俭开发者的工作...(只限于,存在法则的表;

  • 验证思路:

    在数据库中,创立一个 表名前缀 tb_ ,表构造雷同的,数据不同的表 执行查看运行数据!

设置主键生成策略:@TableId

注解在实体类的某一字段上,示意这个字段对应数据库表的主键

  • 当数据库表字段 和 实体类属性名都是 id 时候,无需应用改注解进行指定, MP会自定进行关联; 且默认应用的是 雪花算法
  • 如果: 数据库表字段 和 实体类属性名 不匹配/不是 id 时候须要应用,@TableId注解的 value属性进行关联~ type属性:主键策略

type 主键生成策略:IdType

主键生成策略的值,是一个枚举类型,全都定义在 idType枚举类中,取值如下:

AUTO 自增

  • 数据库ID自增,依赖于数据库。该类型请确保数据库设置了 ID自增 否则有效默认采纳 雪花算法

NONE 默认

  • 未设置主键类型,默认采纳雪花算法
  • 留神:

    要留神数据库主键列的 长度要11 位,不然数据库新增列不够长报错!长度太小!

    Java 的字段要是 Long 长整型

INPUT 手动输出

  • 须要手动设置主键,若不设置,插入操作生成SQL语句时,主键这一列的值会是null

ASSIGN_ID 手动+默认

  • 当没有手动设置主键,即实体类中的主键属性为空时,才会主动填充,应用雪花算法

ASSIGN_UUID 手动+uuid

  • 当实体类的主键属性为空时,才会主动填充,应用UUID
  • 留神:

    uuid 是一个带有字母的字符串,数据库的字段须要是 varchar 字符类型,长度40 Java字段要是String字符类型;

uuid 和 雪花算法:❄

首先,雪花算法 和 uuid 都是为了保障,在分布式环境下,保障数据库表中,主键惟一!

雪花算法:

  • 通常长度 11 个数字组成,分布式环境下,有序且惟一的全局id
  • 生成形式:工夫戳 + 机器id + 毫秒序列号, 个别罕用的有了分布式保证数据惟一且自增,有的公司第一个数据应用 雪花算法 ,前面的数据采纳auto 自增
  • 1478677587608748035 1478677587608748036 ...

uuid

  • JDK1.5 Java又了其包装类,来生成uuid String uuid = UUID.randomUUID().toString();
  • uuid 有很多版本,不同版本有不同的生成策略,但都是保障惟一 这里不粗疏介绍:

    UUID总长度 36,由 32 个 16 进制字符和 4 个连字符组成,例如: 5c6aeee6-00f1-45b1-aafa-d615a18217aa

全局设置 主键生成策略:

mybatis-plus:  global-config:    db-config:      # 设置全局 主键生成策略;      id-type: auto

表字段/实体属性 映射:@TableFieid

与@TableName 类型 注解在某一字段上,指定Java实体类的字段和数据库表的列的映射关系

  • MP 默认开启 表列/实体字段的 驼峰映射

    即:数据库中的 user_name 字段,会主动与 Java实体的 userName 进行映射匹配

  • 而,对于某些齐全不一样的数据库列/实体字段 能够通过 @TableFieid进行关联,注解申明在要匹配的字段名上

    value 属性指定表的列名

    fill 属性指定,字段为空时会进行主动填充的值

    exist 属性,设置之后示意该,实体属性,不和任何数据库列匹配 CRUD的Sql 会疏忽这个字段~

    exist 也能够通过其它形式来实现,如应用 static transient 关键字的属性,不过不是很正当;

MP 打印日志:

如果须要打印MP操作对应的SQL语句等,能够配置日志输入:

mybatis-plus:  configuration:    # 设置MP 打印SQL 语句日志;    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Mapper CRUD接口:

MP 封装了一些最根底的CRUD办法,Mapper接口只须要继承 BaseMapper<T>MP 在程序运行时候,会主动给 Mapper接口,实现对应的实现~

  • MP 为了不便操作前面又提供了 Service CRUD接口 还有 Wrapper条件结构器 不便组装SQL where条件

BaseMapper 接口办法:

  • insert(T entity) 插入一条记录
  • deleteById(Serializable id) 依据主键id删除一条记录
  • delete(Wrapper<T> wrapper) 依据条件结构器wrapper进行删除
  • selectById(Serializable id) 依据主键id进行查找
  • selectBatchIds(Collection idList) 依据主键id进行批量查找
  • selectList(Wrapper<T> wrapper) 依据条件结构器wrapper进行查问
  • selectMaps(Wrapper<T> wrapper) 依据 wrapper 条件,查问记录,将查问后果封装为一个Map,Map的key为后果的列,value为值
  • update(T entity, Wrapper<T> wrapper) 依据条件结构器wrapper进行更新
  • updateById(T entity) 传入对象类型,必须给主键列赋值,批改非主键列的字段...
  • ..... 等:

插入数据: Insert

通过 BaseMapper的 insert(); 办法, 传入一个对象, 对其进行新增入库;

test模块:com.wsm.MPTest.Java

// insert(); 新增用户@Testpublic void testInsert(){    User user = new User();    user.setName("wsm");    user.setAge(540);    user.setPassword("qwer");    user.setUserName("wsm");    user.setAddress("皮尔及沃特");    // 讲创立的对象,新增入库,并返回影响行数;    int insert = userMapper.insert(user);    // 判断影响行数,是否新增胜利!    if(insert >0)        System.out.println("新增胜利");    else        System.out.println("新增失败");}

删除数据: Deletexxx

test模块:com.wsm.MPTest.Java

// deleteById(); 依据id 删除数据;@Testpublic void testDelByid(){    // deleteById(Serializable id);    // Java 数值类型 继承了 Number抽象类 实现了 Serializable序列化接口,所以传入一个 Serializable对象;    int del = userMapper.deleteById(1);    // 判断影响行数,是否删除胜利!    if(del >0)        System.out.println("删除胜利");    else        System.out.println("删除失败");}// deleteBatchIds(Collection); 依据 id 汇合,批量删除数据!@Testpublic void testDelBat(){    List<Integer> ids = new ArrayList<>();    ids.add(2);    ids.add(3);    ids.add(4);    // 批量删除 2 3 4 主键列的数据;    int del = userMapper.deleteBatchIds(ids);    // 判断影响行数,是否删除胜利!    if(del >0)        System.out.println("批量删除胜利");    else        System.out.println("批量删除失败");}// deleteByMap(Map); 依据传入Map 指定的K作为列名和V作为列值进行等值匹配查找;@Testpublic void testDelByMap(){    HashMap<String, Object> map = new HashMap<>();    map.put("name","wsm");    map.put("age",540);    // 删除 name = wsm age = 540 的数据;    int del = userMapper.deleteByMap(map);    // 判断影响行数,是否删除胜利!    if(del >0)        System.out.println("删除匹配的数据");    else        System.out.println("未删除数据");}

批改数据: Updatexxx

test模块:com.wsm.MPTest.Java

// updateByid(T) 依据传入对象,id属性来批改对应数据的对应字段值...@Testpublic void updByid(){    User user = new User();    user.setId(2L);    user.setUserName("wsm");    // 批改数据库 id=2 的数据,user_name 值为wsm 其它属性未赋值,数据库不会改变~    int upd = userMapper.updateById(user);    // 判断影响行数,是否批改胜利!    if(upd >0)        System.out.println("数据批改胜利!");    else        System.out.println("数据批改失败!");}

Wrapper 调节结构器:

咱们在实际操作数据库的时候会波及到很多的条件,MP为咱们提供了一个功能强大的条件结构器 Wrapper

Wrapper 是一个 抽象类,

其子类 AbstractWrapper抽象类 中提供了很多用于结构Where条件的办法

AbstractWrapper的子类QueryWrapper则额定提供了用于针对Select语法的select办法。能够用来设置查问哪些列;

AbstractWrapper的子类UpdateWrapper则额定提供了用于针对SET语法的set办法。能够用来设置对哪些列进行更新;

Condition

所有条件结构器的办法中();

  • 都能够指定一个 boolean类型的参数,condition 能够,用来决定该条件是否退出最初生成的WHERE语句中

举例:

// 假如name变量是一个内部传入的参数String name = ""; QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.like(StringUtils.hasText(name), "name", name);    // 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中// 其实就是对上面代码的简化if (StringUtils.hasText(name)) {    wrapper.like("name", name);}// StringUitils.hasText("");     外面的值为 null 、"" 、 " ",那么返回值为false... // 即:下面的示意,name 不存在则不加该条件, 当然判断是否成立的形式有很多: Lambda函数式接口...

罕用AbstractWrapper办法

eq    等于 =    eq("name", "老王") ---> name = '老王'ne  不等于 <>    ne("name", "老王")---> name <> '老王'gt    大于 >    gt("age", 18) ---> age > 18ge    大于等于≥    ge("age", 18) ---> age >= 18lt    小于<    lt("age", 18) ---> age < 18le    小于等于≤    le("age", 18) ---> age <= 18between        相当于SQL中的BETWEEN AND                 between("age", 18, 30) ---> age between 18 and 30  (18 ≤ age ≥ 30)notBetween     相当于between 取反            notBetween("age", 18, 30) ---> age not between 18 and 30like        含糊匹配            like("name", "王") ---> name like '%王%'notLike     含糊匹配取反            notLike("name", "王") ---> name not like '%王%'likeRight    含糊匹配右半边            likeRight("name", "王") ---> name like '王%'likeLeft    含糊匹配左半边            likeLeft("name", "王") ---> name like '%王'isNull         判断字段为空的匹配            isNull("name") ---> name is nullisNotNull    字段不为空的匹配            isNotNull("name") ---> name is not nulland            嵌套            and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')or            拼接            eq("id",1).or().eq("name","老王") ---> id = 1 or name = '老王'in                                in("age",{1,2,3})--->age in (1,2,3)groupBy        分组            groupBy("id", "name")--->group by id,nameorderByAsc     正排序            orderByAsc("id", "name")--->order by id ASC,name ASCorderByDesc 倒排            orderByDesc("id", "name")--->order by id DESC,name DESC

更多请参考官网

AbstractWrapper 案例:

SQL语句如下:

SELECT     id,user_name,PASSWORD,NAME,age,address FROM     tb_user WHERE     age > 18 or address like 'tb_德玛%'

Wrapper写法如下:

/** AbstractWrapper 应用 **/@Testpublic void testAbsWra(){    QueryWrapper query = new QueryWrapper();    query.gt("age", 18);    query.or();    query.likeRight("address", "tb_德玛");    // 查问传入 wrapper 条件结构器,查问: 年龄大于18 或 地址是 tb_德玛 结尾的;    List<User> users = userMapper.selectList(query);    users.forEach(System.out::println);}

罕用QueryWrapper办法

QueryWrapper的 select 能够设置要查问的列

示例一:

select(String... sqlSelect) 办法的指定要查问的列名

SQL语句如下:

SELECT id,`name`FROM tb_user WHERE age > 18

Wrapper写法如下:

// select(String... sqlSelect) 办法的指定要查问的列名public void testQueryWra1(){    // 创立QueryWrapper<T> 实例;    QueryWrapper<User> query = new QueryWrapper<>();    // .select('列1','列2',...); 指定要查问的列, QueryWrapper反对链式编程...    query.select("id","name").gt("age", 18);    // 传入QueryWrapper 开始查问~    List<User> users = userMapper.selectList(query);    users.forEach(System.out::println);}

示例二:

select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)

  • 办法第一个参数为实体类的字节码对象
  • 第二个参数为Predicate类型,能够应用lambda的写法,过滤要查问的字段 (主键除外) 因为有局限性,并不是罕用理解即可!

SQL语句如下:

SELECT id,user_name,`name`,age,address FROM tb_user WHERE age > 18

Wrapper写法如下:

// select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)@Testpublic void testQueryWra2(){    // 创立QueryWrapper<T> 实例;    QueryWrapper<User> query = new QueryWrapper<>();    // .select(类对象,Predicate); 外部类实现;    //        query.select(User.class, new Predicate<TableFieldInfo>() {    //            @Override    //            public boolean test(TableFieldInfo tableFieldInfo) {    //                return !"password".equals(tableFieldInfo.getColumn());    //            }    //        }).gt("age", 18);    // .select(类对象,Predicate); 反对应用Lambda表达式实现;    // Predicate中的test(); 办法返回 boolean类型, 程序会循环对每个字段进行比拟,为 true 的才会查问该列~    query.select(User.class,u-> !"password".equals(u.getColumn())).gt("age", 18);    // 传入QueryWrapper 开始查问~    List<User> users = userMapper.selectList(query);    users.forEach(System.out::println);}

罕用UpdateWrapper办法

咱们后面在应用update办法时须要创立一个实体类对象传入,用来指定要更新的列及对应的值

  • 然而如果须要更新的列比拟少时,创立这么一个对象显的有点麻烦和简单
  • 咱们能够应用UpdateWrapper的set办法来设置要更新的列及其值。同时这种形式也能够应用Wrapper去指定更简单的更新条件

sql语句如下:

-- 小于等于 18 岁的用户,都更新为 540;UPDATE tb_user SET age = 540 WHERE age <= 18

Wrapper写法如下:

/** UpdateWrapper应用 **/@Testpublic void tetsUpdWra(){    UpdateWrapper<User> updwra = new UpdateWrapper<>();    // 小于等于 18 岁的用户,都更新为 540    updwra.le("age",1).set("age","540");    // 传入UpdateWrapper 开始查问~    int update = userMapper.update(null, updwra);    // 判断影响行数,是否批改胜利!    if(update >0)        System.out.println("数据批改胜利!");    else        System.out.println("数据批改失败!");}

LambdaQueryWrapper 应用

咱们后面在应用条件结构器时列名都是用字符串的模式去指定,这种形式无奈在编译期确定列名的合法性 无奈更加精确的保障列匹配正确;

MP提供了一个Lambda条件结构器能够让咱们间接以实体类的办法援用的模式来指定列名

SQL语句如下:

SELECT id,user_name,`name`,age,address FROM tb_user WHERE age > 18

Wrapper写法如下:

/** LambdaQueryWrapper应用 **/// Lamdba 表达式实现的@Testpublic void testQueryWralmd(){    LambdaQueryWrapper<User> lam = new LambdaQueryWrapper<>();    // gt(User::getAge,18); 应用办法援用的模式,对参数进行绑定,防止了编译期不确定数据库列,而造成的失误~    lam.select(User.class,u-> !"password".equals(u.getColumn())).gt(User::getAge,18);    // 传入QueryWrapper 开始查问~    List<User> users = userMapper.selectList(lam);    users.forEach(System.out::println);}

自定义Mapper sql:

尽管MP为咱们提供了很多罕用的办法,并且也提供了条件结构器

  • 然而如果真的遇到了简单的SQL时,咱们还是须要本人去定义方法,本人去写对应的SQL,这样SQL也更有利于前期保护
  • 因为MP是对mybatis做了加强,所以还是反对之前Mybatis的形式去自定义办法
  • 同时也反对在应用Mybatis的自定义办法时应用MP的条件结构器帮忙咱们进行条件结构

MP 中应用 Mybatis 形式:

其实实质上是没有太大变动的,还是失常的:定义Mapper接口 创立对应的映射文件 映射文件中编写sql 调用测试

定义Mapper接口:

com.wsm.mapper 包下的 xxxMapper 接口

@Mapper//@Mapper 注解,使以后的Mapper 接口,被Spring进行治理,不然须要在,启动类上申明 @MapperScan("com.wsm.mapper") 类扫描指定包下,mapper接口文件;public interface UserMapper  extends BaseMapper<User> {    //Mapper 接口 extends集成 BaseMapper<T> 泛型对应的实体类;    //  Ctrl+右击, 进入BaseMapper 中能够看到, MP 默认给对应实体类提供好的实现办法();    //  增删改查... 即各种的, 重载 CRUD 的操作;    //如果,BaseMapper<T> 中,没有提供的,前面还能够在,该 xxxMapper 文件中, 自定义本人须要的办法();    /** 自定义办法 **/    // 依据id查问对象;    User findMyUser(Long id);}

创立对应的映射文件, 编写sql:

为了方便管理在: resources 资源目录下创立 mapper 文件夹,中创立 对应的sql映射文件 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!-- 指定映射的mapper --><mapper namespace="com.wsm.mapper.UserMapper">    <!-- 指定sql 对应的办法名,返回的后果集类型; -->    <select id="findMyUser" resultType="com.wsm.entity.User">       select * from user where id = #{id}    </select></mapper>

yaml 设置我的项目 扫描对应的 sql映射文件:

mybatis-plus:  # 设置扫描的sql映射文件,加载至环境中;  mapper-locations: classpath*:/mapper/**/*.xml

程序测试:

test模块下:com.wsm.MPTest.Java

/** 测试自定义办法查问后果 **/@Testpublic void testfindMyUser(){    User myUser = userMapper.findMyUser(1L);    System.out.println(myUser);}

运行,ok 能够查问到数据!

总结:

MyBatis-plus 归根结底底层也还是 Mybatis 所以,依照失常 Mybatis 写法来对MP进行扩大Mybatis写法,没有任何影响

Mybatis形式联合 MP条件结构器

咱们在应用上述形式自定义办法时, 如果也心愿咱们的自定义办法能像MP自带办法一样应用条件结构器来进行条件结构的话只须要应用如下形式即可

在SQL语句中获取Warpper拼接的SQL片段进行拼接

增加Warpper类型的参数,并且要留神给其指定参数名 UserMapper.Java

@Mapper//@Mapper 注解,使以后的Mapper 接口,被Spring进行治理,不然须要在,启动类上申明 @MapperScan("com.wsm.mapper") 类扫描指定包下,mapper接口文件;public interface UserMapper  extends BaseMapper<User> {    //Mapper 接口 extends集成 BaseMapper<T> 泛型对应的实体类;    //  Ctrl+右击, 进入BaseMapper 中能够看到, MP 默认给对应实体类提供好的实现办法();    //  增删改查... 即各种的, 重载 CRUD 的操作;    //如果,BaseMapper<T> 中,没有提供的,前面还能够在,该 xxxMapper 文件中, 自定义本人须要的办法();    /** 自定义办法 **/    // 依据id查问对象;    User findMyUser(Long id);    // 自定义办法,应用MP 的Wrapper    List<User> findUsers(@Param(Constants.WRAPPER) Wrapper<User> wrapper);  //wrapper 是MP包下的依赖~}

办法定义中增加Warpper类型的参数

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!-- 指定映射的mapper --><mapper namespace="com.wsm.mapper.UserMapper">    <!-- 指定sql 对应的办法名,返回的后果集类型; -->    <select id="findMyUser" resultType="com.wsm.entity.User">       select * from user where id = #{id}    </select>    <!-- 指定sql 对应的办法名,返回的后果集类型; -->    <select id="findUsers" resultType="com.wsm.entity.User">       select * from user  ${ew.customSqlSegment}    </select></mapper>

程序测试:

test模块下:com.wsm.MPTest.Java

/** 自定义办法查问 +MP的 wrapper 条件结构 **/@Testpublic void testfindusers(){    // 定义QueryWrapper 结构器,查问 age 大于 18 的数据;    QueryWrapper<User> query = new QueryWrapper<>();    query.gt("age", 18);    // 调用 自定义的办法(wrapper 参数);    List<User> users = userMapper.findUsers(query);    users.forEach(System.out::println);}

运行,ok 能够查问到数据!

分页查问 Page

单表分页查问:

分页查问是一个应用十分频繁的性能,通常实现形式:

  • 定义一个Page分页类,类中属性:当前页int 每页行int 总记录数int 总页数int 每页的数据汇合List
  • 分页前先依据条件查问出总记录数,给Page对象的属性赋值,同时依据分页算法

    总记录数%每页行==0?总记录数/每页行:总记录数/每页行+1 得总页数赋值 总记录 整除 每页行 不整除+1 得总页数;

  • MySQL分页: 应用 limit x,y 关键字:获取查问后果的 第x行 往下 y个记录数;

    以后端,申请后端传入:分页条件 查问第几页x 每页几行y

    后盾Java 会依据:x=(x-1)乘y 失去limit 起始行,0开始 y 并将 x y传入sql 中执行 limit x,y 返回分页的后果集存入 Page类对象的每页的数据汇合List

MP 为了不便里面操作,页对分页进行了封装, 咱们不须要关注太多就能够实现分页操作!

① 配置分页查问拦截器

MP 应用拦截器进行分页解决,所以创立一个 util工具包来, 寄存分页配置类

MPPageConfig.Java

  • MP不同的版本,分页的配置也有些区别,不过这个不是咱们关系的copy 过去能用就了
/** MP 分页,配置类 */// @Configuration 将类加载至Spring容器中去;@Configurationpublic class MPPageConfig {    /**     * 3.4.0之前的版本     * @return     *///    @Bean//    public PaginationInterceptor paginationInterceptor(){//        return  new PaginationInterceptor();//    }    /**     * 3.4.0之后版本     * @return     */    @Bean    public MybatisPlusInterceptor mybatisPlusInterceptor(){        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());        return mybatisPlusInterceptor;    }}

② 进行分页查问

/** selectPage(); Mapper 单表分页 */@Testpublic void testPage(){    // 定义分页对象    Page<User> userPage = new Page<>();    // 设置分页参数    // 每页行数    userPage.setSize(2);    // 以后查问的第几页    userPage.setCurrent(1L);    //后盾会主动依据 每页行 第几页 计算拼接limit sql;    // 执行分页查问 selectPage(page,wrapper); 两个参数: 分页对象,条件结构器;    userMapper.selectPage(userPage, null);        // 返回的分页对象,就算传入的对象 userPage    System.out.println("获取当前页的数据");    System.out.println(userPage.getRecords());    System.out.println("获取总记录数");    System.out.println(userPage.getTotal());    System.out.println("以后页码");    System.out.println(userPage.getCurrent());}

多表分页查问:

筹备工作:

定义须要的数据库sql

CREATE TABLE `orders` (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `price` int(11) DEFAULT NULL COMMENT '价格',  `remark` varchar(100) DEFAULT NULL COMMENT '备注',  `user_id` int(11) DEFAULT NULL COMMENT '用户id',  `update_time` timestamp NULL DEFAULT NULL COMMENT '更新工夫',  `create_time` timestamp NULL DEFAULT NULL COMMENT '创立工夫',  `version` int(11) DEFAULT '1' COMMENT '版本',  `del_flag` int(1) DEFAULT '0' COMMENT '逻辑删除标识,0-未删除,1-已删除',  `create_by` varchar(100) DEFAULT NULL COMMENT '创建人',  `update_by` varchar(100) DEFAULT NULL COMMENT '更新人',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;/*Data for the table `orders` */insert  into `orders`(`id`,`price`,`remark`,`user_id`,`update_time`,`create_time`,`version`,`del_flag`,`create_by`,`update_by`) values (1,2000,'无',2,'2021-08-24 21:02:43','2021-08-24 21:02:46',1,0,NULL,NULL),(2,3000,'无',3,'2021-08-24 21:03:32','2021-08-24 21:03:35',1,0,NULL,NULL),(3,4000,'无',2,'2021-08-24 21:03:39','2021-08-24 21:03:41',1,0,NULL,NULL);

com.wsm.entity 包下的实体类:Orders.Java

@Data@NoArgsConstructor@AllArgsConstructor// 因为设置了全局的表前缀 tb_ 为了不便操作,@TableName 指定表;@TableName("orders")public class Orders {    // Orders 表属性:    private Long id;    private Integer price;    private String remark;    private Integer userId;    private LocalDateTime updateTime;    private LocalDateTime createTime;    private Integer version;    private Integer delFlag;    // 多表查问,User表扩大属性;    // MP 默认的CRUD 不对该属性进行sql映射,自定义Mapper 能够通过同名/取别名 主动映射;    @TableField(exist = false)    public String userName;}

多表 sql 筹备:

① 定义OrdersMapper.Java 接口

OrdersMapper.Java

  • 因为是多表分页,所以须要自定义sql 和 Mapper接口,自定义分页须要传入参数 Page<T> 并且返回类型也是 Page<T>
@Mapperpublic interface OrdersMapper extends BaseMapper<Orders> {    // 自定义多表分页办法    public Page<Orders> selPageOrdUs(Page<Orders> page);}

② 定义OrderMapper.xml 映射文件

OrdersMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!-- 指定映射的mapper --><mapper namespace="com.wsm.mapper.OrdersMapper">    <!-- MP多表分页 -->    <select id="selPageOrdUs" resultType="com.wsm.entity.Orders">        SELECT            o.*,u.`user_name` as userName        FROM  orders o        INNER JOIN tb_user u        ON u.id = o.user_id    </select></mapper>

③ 编写测试程序:

MPTest.Java

/** selectPage(); Mapper 多表分页 *///定义 OrdersMapper 对象;@Autowired        OrdersMapper ordersMapper;@Testpublic void testselPageOrdUs(){     // 定义MP 分页对象,并设置分页属性    Page<Orders> ordersPage = new Page<>();    // 每页行,当前页    ordersPage.setSize(2);    ordersPage.setCurrent(1);    // 进行查问,返回分页对象, 因为: 援用类型的实参扭转形参会受影响~所以, ordersPage == ordersIPage    Page<Orders> ordersIPage = ordersMapper.selPageOrdUs(ordersPage);    System.out.println("ordersPage == ordersIPage是否相等:"+(ordersPage == ordersIPage));    // 返回的后果    System.out.println("获取总记录数");    System.out.println(ordersIPage.getTotal());    System.out.println("获取当前页的数据");    System.out.println(ordersIPage.getRecords());}

④ 运行测试:

ordersPage == ordersIPage是否相等:true获取总记录数3获取当前页的数据[Orders(id=1, price=2000, remark=无, userId=2, updateTime=2021-08-24T21:02:43, createTime=2021-08-24T21:02:46, version=1, delFlag=0, userName=gailun), Orders(id=3, price=4000, remark=无, userId=2, updateTime=2021-08-24T21:03:39, createTime=2021-08-24T21:03:41, version=1, delFlag=0, userName=gailun)]

Service CRUD接口:

MP 也为咱们提供了Service 层的接口来实现 CRUD的操作:

为什么MP 有了 Mapper接口, 还要 Service接口:

Why?,屡次一举?最开始学习的时候,我也很纳闷,对呀为啥呢?

因为:为了不便开发程序当初程序大部分都是 三层架构`Dao长久 Service业务 Controller 管制`

  • 而,定义的Mapper接口,很多时候又要在Service 外面进行从新调用 而,很多时候Servcie又很简略只能调用了Mapper的办法();
  • MP 为了简化开发者工作,对Service接口也进行了继承,这样对于一些简略的性能,只须要编写 Controller管制层就能够实现开发,开发者不须要在写简略的Service代码
  • 还有,Service 中有很多是对Mapper 的整合办法();

    SaveOrUpdate(T entity) 更新记录T 如果不存在,插入一条记录;

    saveOrUpdate(T entity, Wrapper<T> updateWrapper); 依据条件批改一条数据, 如果没有匹配则删除

    saveOrUpdateBatch(Collection<T> entityList); 批量批改插入

    Service 对 Mapper 多了更多的组合批量操作 算是, 节俭了开发者的工作量; 官网

编写User Service 的CRUD操作:

<img src="https://wsm540.oss-cn-beijing.aliyuncs.com/Java框架/MybatisPlusimage-20220106215914269.png" alt="image-20220106215914269" style="zoom:50%;" />

在应用MP Service 的 CRUD 之前还是须要确保,Mapper 继承 BaseMapper<T>

① 编写 Service

UserService.Java

  • 创立 Service 接口,extends继承 IService<操作的实体类T> IService类中,定义了Service的很多批量CRUD办法~
// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;public interface UserService extends IService<User> {       }

② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

// Spring注解,示意改类是一个 Service 业务逻辑类,并交给Spring容器治理;@Service// ServiceImpl 是 Service 的实现// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类> // 因为是 Service的实现, 实现对应的接口 implement Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {   }

③ 测试:

MPTest.Java

/** Service 的CRUD **/// 从Spring容器中获取Service对象;@AutowiredUserService userService;// 只获取匹配的第一条数据;@Testpublic void testGetOne(){    LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();    wrapper.gt(User::getAge, 28);    User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异样,而返回第一条记录    System.out.println(one);}

自定义 Service

就是失常的援用 Mapper

① 编写 Service

UserService.Java

// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;public interface UserService extends IService<User> {    //自定义Service 实现:    List<User> selall();}

② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

// Spring注解,示意改类是一个 Service 业务逻辑类,并交给Spring容器治理;@Service// ServiceImpl 是 Service 的实现// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类>// 因为是 Service的实现, 实现对应的接口 implement Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {    // 创立UserMapper 对象实例;    @Autowired    UserMapper userMapper;    @Override    public List<User> selall() {        // 间接调用Mapper 的查问全副~ 当然Service业务逻辑层,能够写很多更加简单的操作...        return userMapper.selectList(null);    }}

MP 的代码生成器:

MP提供了一个代码生成器,能够让咱们一键生成实体类,Mapper接口,Service,Controller等全套代码

  • 只须要,小手一点:实体 mapper service controller 啥啥都不须要本人写了! 切实是太牛逼了!

① 增加依赖

<!-- Mybatsi-Plus 代码生成器依赖: --><!--mybatisplus代码生成器--><dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-generator</artifactId>    <version>3.4.1</version></dependency><!--模板引擎--><dependency>    <groupId>org.freemarker</groupId>    <artifactId>freemarker</artifactId></dependency>

② 增加生成类,运行生成文件

个别状况下,能够将这个文件放在 我的项目Util 包下,作为一个工具类应用:

  • 甚至,能够不申明在我的项目中,因为它能够指定 代码生成的地址... 创立实现之后,将须要的货色拖到我的项目中也能够
  • 很多时候,有些特地长的表的实体,用这个生成,切实是能够省下很多工夫

GeneratorTest.Java

public class GeneratorTest {    @Test    public void generate() {        AutoGenerator generator = new AutoGenerator();                // 全局配置        GlobalConfig config = new GlobalConfig();        String projectPath = System.getProperty("user.dir");                // 设置输入到的目录: 能够更改为任何门路 D盘 C盘...        // config.setOutputDir("D:/MP");        config.setOutputDir(projectPath + "/src/main/java");        // 生成的作者名        config.setAuthor("wsm");        // 生成完结后是否关上文件夹        config.setOpen(false);        // 全局配置增加到 generator 上        generator.setGlobalConfig(config);        // 数据源配置: 配置本人的数据库要生成的数据库 用户/明码         DataSourceConfig dataSourceConfig = new DataSourceConfig();        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&serverTimezone=UTC");        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");        dataSourceConfig.setUsername("root");        dataSourceConfig.setPassword("ok");        // 数据源配置增加到 generator        generator.setDataSource(dataSourceConfig);        // 包配置, 生成的代码放在哪个包下        PackageConfig packageConfig = new PackageConfig();        packageConfig.setParent("com.wsm");        // 包配置增加到 generator        generator.setPackageInfo(packageConfig);                // 策略配置        StrategyConfig strategyConfig = new StrategyConfig();        // 下划线驼峰命名转换        strategyConfig.setNaming(NamingStrategy.underline_to_camel);        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);        // 开启lombok,生成的实体类下面就会又 lombok注解;        strategyConfig.setEntityLombokModel(true);        // 开启RestController        strategyConfig.setRestControllerStyle(true);        generator.setStrategy(strategyConfig);        generator.setTemplateEngine(new FreemarkerTemplateEngine());        // 开始生成        generator.execute();    }}

生成的门路:D:/MP

MP高级 主动填充

在理论我的项目中表不仅仅会有开发中须要的性能字段有时候还会须要很多的从属字段:

  • 更新工夫 创立工夫 创建人 更新人 逻辑删除列 乐观锁Version 备用1 备用2...
  • 而,这些字段须要咱们手动进行保护会很麻烦,每个数据新增 批改都要进行手工保护;
  • MP 提供了 主动填充 来实现对这些数据的操作

实例Demo

① 在须要操作的实体上, 增加 @TableFieId注解

在对应字段上减少注解,@TableFieIdfill属性来设置字段的主动填充; Orders为例子

  • fill 属性:是一个枚举FieldFill

    DEFAULT默认值无任何解决 INSERT新增触发 UPDATE批改时触发 INSERT_UPDATE 新增或批改时触发

② 编写适配器:

MetaObjectHandler.Java

@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {    // 新增时候触发,并设置新增时候对应数据要赋的值    @Override    public void insertFill(MetaObject metaObject) {        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);    }    // 批改时候触发,并设置批改时候对应数据要赋的值    @Override    public void updateFill(MetaObject metaObject) {        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);    }}

③ 测试:

/** MP 主动填充: **/@Testpublic void testinsertOrd(){    // 创立新增对象    Orders orders = new Orders();    orders.setPrice(1000);    orders.setRemark("无");    orders.setUserId(2);    // 执行新增    int insert = ordersMapper.insert(orders);    if(insert>0)        System.out.println("新增胜利");    else        System.out.println("新增失败");}

新增胜利, 查看数据库后果集!

MP高级 逻辑删除

咱们深处大数据时代,个别企业的数据都是不容许实在删除的,这样前面找都不好找

  • 所以 很多公司的数据库都会增加一个字段逻辑删除,用来判断数据是否删除,个别只有两个值:0|1
  • 0: 就是失常的数据
  • 1: 就算被删除的数据,并不会真的删除,而是查问时候默认就不查问 1 的数据;

实现:

通常逻辑删除 只须要在 yaml 配置一下即可

留神:3.3.0版本之前还须要在对应的字段上加上@TableLogic注解

mybatis-plus:  global-config:    db-config:      logic-delete-field: delFlag      # 全局逻辑删除的实体字段名, 3.3.0配置后能够不增加注解,之前的还须要增加注解 @Tablelogic)      logic-delete-value: 1         # 逻辑已删除值(默认为 1)      logic-not-delete-value: 0     # 逻辑未删除值(默认为 0)

测试:

/** MP 逻辑删除 **/@Testpublic void delOrd(){    // 配置了逻辑删除之后,间接调用MP 的删除办法就是进行逻辑删除了! 留神: 自定义sql的操作还须要本人实现留神!    int i = ordersMapper.deleteById(1);    if(i>0)        System.out.println("逻辑删除胜利");    else        System.out.println("逻辑删除失败");}

MP高级 乐观锁

在程序开发中大家应该都很理解吧,为了防止多线程状况下数据错乱须要对数据进行加锁

  • 而当,多个线程同时操作一个数据时候可能呈现数据被反复批改的状况 典型的抵触
  • 失落更新

    一个事务的更新笼罩了其它事务的更新后果,就是所谓的更新失落

    用户A把值从6改为2,用户B把值从2改为6,则用户A失落了他的更新

  • 脏读

    当一个事务读取其它实现一半事务的记录时,就会产生脏读取

    用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6

乐观锁 和 乐观锁

乐观锁:

  • 比拟乐观 假如不会产生并发抵触,只在提交操作时查看是否违反数据完整性。乐观锁不能解决脏读的问题
  • 通常应用数据版本(Version)记录机制实现,

    通过为数据库表减少一个数字类型的 “version” 字段来实现,当读取数据时,将version字段的值一起读出,`数据每更新一次,对此version值加一

    Update set version=version+1 where version = version 每次更新前都要判断传入的 版本 是否是当初最新版本!

    A B 同时要更新数据 1 都获取了version版本 1

    A 先更新:Update set version=version+1 where version = 1 version就是1 所以更新胜利!

    B 在更新:Update set version=version+1 where version = 1 version曾经被A +1 所以version 是2 where 2=1 不成立 B更新失败

乐观锁:

  • 比拟乐观,认为肯定会发送数据扭转
  • 在对数据库的一条记录进行批改时,先间接加锁(数据库的锁机制),锁定这条数据,而后再进行操作. 同一时间只能,容许一个人来批改这条数据!

总结:

  • 在读多写少的场景下,乐观锁比拟实用,可能缩小加锁操作导致的性能开销,进步零碎吞吐量
  • 在写多读少的场景下,乐观锁比拟应用,否则会因为乐观锁一直失败重试,反而导致性能降落

MP 为了不便操作就对次进行了封装解决,更加不便的进行了操作;

MP 实现:

① 配置乐观锁插件

@Configurationpublic class MybatisPlusConfig {    /**     * 旧版     */    @Bean    public OptimisticLockerInterceptor optimisticLockerInterceptor() {        return new OptimisticLockerInterceptor();    }        /**     * 新版     */    @Bean    public MybatisPlusInterceptor mybatisPlusInterceptor() {        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());        return mybatisPlusInterceptor;    }}

目前我的项目中间接运行会报错,因为 MybatisPlusInterceptor 会在很多中央应用到:MP分页 MP乐观锁 ...

  • 只须要配置一个即可,将 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); 定义在对应的 MybatisPlusInterceptor
  • 就 了,保障Spring 一次只注入一个对象类型...

② 在实体类中示意版本的字段上增加注解@Version

Orders.Java 省略其它未更改代码;

// 增加乐观锁的注解,应用MP 的形式实现乐观锁 保障数据安全;// version字段,类型只反对int,long,Date,Timestamp,LocalDateTime@Versionprivate Integer version;

③ 操作:

留神:

  • 在更新前咱们肯定要先查问到version设置到实体类上再进行更新能力失效 传入的对象肯定要携带 Version列有值
  • 乐观锁插件仅反对updateById(id)update(entity, wrapper)办法 wrapper不能复用!会呈现反复参数应用;

MPTest.Java

/** MP 乐观锁 */// 操作前要去抱必须获取到数据最新的 version: 先查问在批改:@Testpublic void testupdVer(){    // 先查问:    Orders orders = ordersMapper.selectById(1);    // 设置更新列    orders.setPrice(123);    System.out.println(orders);    // 批改: 乐观锁插件仅反对`updateById(id)`与`update(entity, wrapper)`办法    int i = ordersMapper.updateById(orders);    if(i>0)        System.out.println("批改胜利,version+1");    else        System.out.println("批改失败");}

cmd 控制台输入:

查问==>  Preparing: SELECT id,price,remark,user_id,update_time,create_time,version,del_flag,create_by,update_by FROM orders WHERE id=? AND del_flag=0==> Parameters: 1(Integer)<==    Columns: id, price, remark, user_id, update_time, create_time, version, del_flag, create_by, update_by<==        Row: 1, 2000, 无, 2, 2021-08-24 21:02:43, 2021-08-24 21:02:46, 1, 0, null, null<==      Total: 1Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@772caabe]Orders(id=1, price=123, remark=无, userId=2, updateTime=2021-08-24T21:02:43, createTime=2021-08-24T21:02:46, version=1, delFlag=0, createBy=null, updateBy=null, userName=null)Creating a new SqlSessionSqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51da32e5] was not registered for synchronization because synchronization is not active批改JDBC Connection [HikariProxyConnection@2069678360 wrapping com.mysql.cj.jdbc.ConnectionImpl@4538856f] will not be managed by Spring==>  Preparing: UPDATE orders SET price=?, remark=?, user_id=?, update_time=?, create_time=?, version=?, update_by=? WHERE id=? AND version=? AND del_flag=0==> Parameters: 123(Integer), 无(String), 2(Integer), 2022-01-07T00:54:39.578(LocalDateTime), 2021-08-24T21:02:46(LocalDateTime), 2(Integer), www(String), 1(Long), 1(Integer)<==    Updates: 1Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51da32e5]批改胜利,version+1

,MP 的学习就到这里了, 感激点赞

自己对每个案例Demo 都进行了, 本地Git治理,并在 wlog.md 中有更具体的应用阐明:

网盘链接:https://pan.baidu.com/s/1fwm9...
提取码:2540