关于java:新的一年学习-MybatisPlus

35次阅读

共计 30044 个字符,预计需要花费 76 分钟才能阅读完成。

自己学习于 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 启动类注解~
@SpringBootApplication
public class SpringBootRun {public static void main(String[] args) {SpringApplication.run(SpringBootRun.class);
    }
}

③ entity 实体类:

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

  • 次要是,提供 JavaBean 与要查问的数据库,属性 / 列进行关联 …
@Data
@NoArgsConstructor
@AllArgsConstructor
public 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 逻辑删除 **/
@Test
public 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 实现:

① 配置乐观锁插件

@Configuration
public 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
@Version
private Integer version;

③ 操作:

留神:

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

MPTest.Java

/** MP 乐观锁 */
// 操作前要去抱必须获取到数据最新的 version: 先查问在批改:
@Test
public 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: 1
Closing 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 SqlSession
SqlSession [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: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51da32e5]
批改胜利,version+1

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

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

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

正文完
 0