首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MyBatisPlus

MyBatisPlus

作者头像
Java_慈祥
发布于 2024-08-06 06:40:16
发布于 2024-08-06 06:40:16
33300
代码可运行
举报
运行总次数:0
代码可运行

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文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 工程:

① 添加依赖

pom.xml

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//SpringBoot 启动类注解~
@SpringBootApplication
public class SpringBootRun {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootRun.class);
    }
}
③ entity 实体类:

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

  • 主要是,提供JavaBean 与要查询的数据库,属性/列进行关联…
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 的语法结构,比较清晰明了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 启动类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.0)

启动成功, 准备Maven 测试类,进行测试程序:

Maven test模块下测试程序:

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* 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_ 这可以使用如下方式配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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
全局设置 主键生成策略:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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语句等,可以配置日志输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// insert(); 新增用户
@Test
public 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// deleteById(); 根据id 删除数据;
@Test
public 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 集合,批量删除数据!
@Test
public 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作为列值进行等值匹配查找;
@Test
public 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// updateByid(T) 根据传入对象,id属性来修改对应数据的对应字段值...
@Test
public 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语句中

举例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 假设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方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
eq	等于 =
	eq("name", "老王") ---> name = '老王'
ne  不等于 <>
	ne("name", "老王")---> name <> '老王'
gt	大于 >
	gt("age", 18) ---> age > 18
ge	大于等于≥
	ge("age", 18) ---> age >= 18
lt	小于<
	lt("age", 18) ---> age < 18
le	小于等于≤
	le("age", 18) ---> age <= 18
between		相当于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 30
like		模糊匹配
			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 null
isNotNull	字段不为空的匹配
			isNotNull("name") ---> name is not null
and			嵌套
			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,name

orderByAsc 	正排序
			orderByAsc("id", "name")--->order by id ASC,name ASC

orderByDesc 倒排
			orderByDesc("id", "name")--->order by id DESC,name DESC

**更多请参考官方 👉 **

AbstractWrapper 案例:

SQL语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT 
	id,user_name,PASSWORD,NAME,age,address 
FROM 
	tb_user 
WHERE 
	age > 18 or address like 'tb_德玛%'

Wrapper写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** AbstractWrapper 使用 **/
@Test
public 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语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT id,`name`
FROM tb_user 
WHERE age > 18

Wrapper写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT id,user_name,`name`,age,address FROM tb_user WHERE age > 18

Wrapper写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
@Test
public 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语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- 小于等于 18 岁的用户,都更新为 540;
UPDATE tb_user SET age = 540 WHERE age <= 18

Wrapper写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** UpdateWrapper使用 **/
@Test
public 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语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT id,user_name,`name`,age,address FROM tb_user WHERE age > 18

Wrapper写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** LambdaQueryWrapper使用 **/
// Lamdba 表达式实现的
@Test
public 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 接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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映射文件:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mybatis-plus:
  # 设置扫描的sql映射文件,加载至环境中;
  mapper-locations: classpath*:/mapper/**/*.xml
程序测试:

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 测试自定义方法查询结果 **/
@Test
public 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 自定义方法查询 +MP的 wrapper 条件构造 **/
@Test
public 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 过来能用就🆗了
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** MP 分页,配置类 */
// @Configuration 将类加载至Spring容器中去;
@Configuration
public 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;
    }
}
② 进行分页查询
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** selectPage(); Mapper 单表分页 */
@Test
public 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Mapper
public interface OrdersMapper extends BaseMapper<Orders> {
    // 自定义多表分页方法
    public Page<Orders> selPageOrdUs(Page<Orders> page);
}
② 定义OrderMapper.xml 映射文件

OrdersMapper.xml

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** selectPage(); Mapper 多表分页 */
//定义 OrdersMapper 对象;
@Autowired        
OrdersMapper ordersMapper;

@Test
public 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());
}
④ 运行测试:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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操作:

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

① 编写 Service

UserService.Java

  • 创建 Service 接口,extends继承 IService<操作的实体类T> IService类中,定义了Service的很多批量CRUD方法~
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {       }
② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类> 
// 因为是 Service的实现, 实现对应的接口 implement Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {   }
③ 测试:

MPTest.Java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** Service 的CRUD **/
// 从Spring容器中获取Service对象;
@Autowired
UserService userService;
// 只获取匹配的第一条数据;
@Test
public 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {
    //自定义Service 实现:
    List<User> selall();
}
② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类>
// 因为是 Service的实现, 实现对应的接口 implement Service
public 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 啥啥都不需要自己写了! 实在是太牛逼了!

① 添加依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public 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);
    }
}
③ 测试:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** MP 自动填充: **/
@Test
public 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注解

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)

测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 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 实现:

① 配置乐观锁插件
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 省略其它未更改代码;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 添加乐观锁的注解,使用MP 的方式实现乐观锁 保证数据安全;
// version字段,类型只支持int,long,Date,Timestamp,LocalDateTime
@Version
private Integer version;
③ 操作:

注意:

  • 在更新前我们一定要先查询到version设置到实体类上再进行更新才能生效 传入的对象一定要携带 Version列有值
  • 乐观锁插件仅支持updateById(id)update(entity, wrapper)方法 wrapper不能复用!会出现重复参数使用;

MPTest.Java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 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 控制台输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
查询
==>  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/1fwm9GO4vRvnJGFSEEj2Xuw 提取码:2540

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Linux发行版:CentOS、Ubuntu、RedHat、Android、Tizen、MeeGo
Linux,最早由Linus Benedict Torvalds在1991年开始编写。在这之前,Richard Stallman创建了Free Software Foundation(FSF)组织以及GNU项目,并不断的编写创建GNU程序(此类程序的许可方式均为GPL: General Public License)。在不断的有杰出的程序员和开发者加入到GNU组织中后,便造就了今天我们所看到的Linux,或称GNU/Linux。 Linux的发行版本可以大体分为两类,一类是商业公司维护的发行版本,一类是社区组织维护的发行版本
阳光岛主
2019/02/19
19.7K0
Linux发行版:CentOS、Ubuntu、RedHat、Android、Tizen、MeeGo
常见Linux系统下载【发行版】
Linux Mint 由Linux Mint Team团队于2006年开始发行,是一份基于Debian和Ubuntu的Linux发行版。其目标是提供一种更完整的即刻可用体验,这包括提供浏览器插件、多媒体编解码器、对DVD播放的支持、Java和其他组件,它也增加了一套定制桌面及各种菜单,一些独特的配置工具,以及一份基于web的软件包安装界面。Linux Mint是对用户友好而功能强大的操作系统。它诞生的目的是为家庭用户和企业提供一个免费的,易用的,舒适而优雅的桌面操作系统。
Lcry
2022/11/29
11.5K0
各发行版Linux介绍
可能这是最著名的Linux版本了,Red Hat Linux已经创造了自己的品牌,越来越多的人听说过它。Red Hat在1994年创业,当时聘用了全世界500多名员工,他们都致力于开放的源代码体系。
星哥玩云
2022/09/15
3.5K0
各发行版Linux介绍
Linux 10个主流发行版本
这里只表示做个记录。其实相关的介绍已经很多了。但是还是想怀缅一下青春。这10个Linux发行版来源于国外网站(最后给出链接)。它列出了10个Linux发行版(包含一个FreeBSD,Linux的胞兄弟),通常被认为是全球Linux用户最广泛使用的。当然并没有经过详细的数字统计,大家仍然可以选择其他的发行版。但是一般来讲,这几个都有非常活跃的论坛或邮件列表,遇到困难,也能比较迅速的解决。
狼啸风云
2019/10/24
19.4K0
linux内核版本和发行版本的区别_linux内核版本号的构成
1.内核版本 内核是系统的心脏,是运行程序和管理像磁盘和打印机等硬件设备的核心程序,它提供了一个在裸设备与应用程序间的抽象层。例如,程序本身不需要了解用户的主板芯片集或磁盘控制器的细节就能在高层次上读写磁盘。
全栈程序员站长
2022/09/25
6.6K0
linux内核版本和发行版本的区别_linux内核版本号的构成
国内外流行的Linux发行版
近年来,Linux 开始获得越来越多的关注,那你知道国内外流行的 Linux 版本都有哪些吗?本文中整理了五大 Linux 发行版,他们分别是 RedHat、SUSE、Debian、ubuntu、Gentoo,一起来看看吧。 Linux 长时间以来都是极客圈子内的玩物,大众对其知之甚少,但谁都无法否认其重要性。近年来,Linux 开始获得越来越多的关注,那你知道国内外流行的 Linux 版本都有哪些吗?一起来看看网友整理的这些 Linux 版本吧。 1. RedHat 国内乃至全世界的 Linux 用户最熟
张戈
2018/03/26
2.9K0
国内外流行的Linux发行版
Linux常见发行版初体验
用户可免费体验到最新的技术或工具,而功能成熟后会被加入到RHEL系统中。因此也被称为RHEL系统的“试验田”。
glm233
2020/09/28
4K0
Linux常见发行版初体验
三十种Linux操作系统发行版名称含义详解
独特的开发模式造就了今天百家争鸣的Linux,各个发行版的名字都代表哪些含义呢?下面是一些常用的版本,感兴趣的朋友一起来看:   1 Ubuntu   这或许是今天最常见的一个Linux发行版了,Ubuntu是一个南非的民族观念,着眼于人们之间的忠诚和联系。该词来自于祖鲁语和科萨语。大意是“人道待人”(对他人仁慈)。另一种翻译是:“天下共享的信念,连接起每个人”。   2 PCLinuxOS   很明显它的名称是PC+Linux+Operating System组合在一起,拥有类似Windows的图形用户界面,方便用户从Windows转向Linux。   3 openSUSE    openSUSE由AMD和Novell共同发起,SUSE是德语“Software-und System-Entwicklung”的缩写(software and system development)。另有一说是为了纪念德国计算机工先驱Konrad Zuse。   4 Fedora   Fedora由Red Hat发起,正是Red Hat标志中人物所戴的那顶帽子。   5 Mandriva    在以前称为Mandrake,并由MandrakeSoft公司进行维护。当MandrakeSoft输掉了与Hearst Corporation争夺Mandrake名称的官司,并收购了Connectiva后,名称变为了Mandriva。   6 Sabayon   一种意大利甜点,也叫Zabaglion,用蛋黄、糖等原料烹饪而成。     7 Debian   这个名字是Ian Murdock在1998年取的,由当初是女朋友现在是妻子的Debra和他的名字混合而来。   8 Damn Small Linux   像它的名字所说那样,这个Linux发行版体积只有50MB。   9 MEPIS   根据创始人Warren Woodford所说,这个名字最初没有任何含义,只是由于朋友在Skype上误听得来的名字。   10 CentOS   CentOS基于Red Had Enterprise Linux(RHEL),代表Community Enterprise Operationg System。   11 Dreamlinux   这是一个巴西的Linux发行版,基于Debian,外观类似苹果的Mac OS X,名字就像介绍中说的那样。   12 Puppy Linux   一种小型的Live CD系统,目标是简单易用,吉祥物是墨西哥小狗吉娃娃。   13 Kubuntu   KDE + Ubuntu的产物   14 Zenwalk   有禅意的名字,不过根据创始人JP Guillemin所说,这个名字是从发音和含义两个方面模仿Neststep操作系统所起。   15 Slackware   创始人是Patrick Volkerding,最初是个人项目,这个名字借用自Church of the SubGenius中的术语 "Slack"。   16 Knoppix   著名的Live CD发行版,基于Dedian,创始人是Klaus Knopper并以此命名。   17 Gentoo   Gentoo是一种体形小巧的企鹅,据说是企鹅中游泳速度最快的一种。   18 Slax   一种基于Slackware的live CD发行版,它的名称并没有特别含义。   19 Sidux   基于Debian尚不稳定,代号“Sid”的一支,名称来自玩具总动员中的角色Sid Phillips。   20 Ubuntu Studio   Ubuntu的派生,以多媒体应用为主。   21 Xubuntu   XFCE + Ubuntu的产物   22 Foresight   Foresight使用Conary作为包管理器,据称,不像大多数发行版一年两次升级那样,它采用滚动升级的方式。   23 Red Hat   创始人之一的Bob Young为您解释其中含义。   24 OpenGEU   OpenGEU最初称为Geubuntu,使用Gnome和Enlightenment作为桌面管理器。   25 Elive   从图标中也可以看出这同样是一款采用Enlightenment作为窗口管理器的发行版,它是基于Debian的Live Cd。   26 Freespire   微软2千万美元买下了“Lindows”这个名字后,Lindows更名为Linspire,Freespire是基于Linspire的源码的Live CD版本。   27 Fluxbuntu   又是Ubuntu的派生版,采用Fluxbox作为窗口管理器。   28 Xandors   X代表X Windows系统,Andros是希腊的岛名。   29 T
苦叶子
2021/10/20
1.2K0
Linux各种版本
 1.1   Fedora Core和Fedora     一开始总搞不清楚 Fedora Core和Fedora有什么关系?有什么不同,现在终于明白了,自从Fedora Core 第七版开始以后就开始称作Fedora 7.0,8.0等等之类的了。      Fedora Core(自第七版直接更名为Fedora)是众多 Linux 发行套件之一。它是一套从Red Hat Linux发展出来的免费Linux系统。现时Fedora最新的版本是Fedora 13,Fedora是linux发行版中更新最快的之一,
233333
2018/03/07
6.8K0
Linux各种发行版,你会选吗
Linux最早由Linus Benedict Torvalds在1991年开始编写。在这之前,Richard Stallman创建了Free Software Foundation(FSF)组织以及GNU项目,并不断的编写创建GNU程序(此类程序的许可方式均为GPL: General Public License)。在不断的有杰出的程序员和开发者加入到GNU组织中后,便造就了今天我们所看到的Linux,或称GNU/Linux。 Linux发行版本可以大体分为两类,一类是商业公司维护的发行版本,一类是社区组织维护的发行版本,前者以著名的Redhat(RHEL)为代表,后者以Debian为代表。下面介绍一下各个发行版本的特点: Redhat,应该称为Redhat系列,包括RHEL(Redhat Enterprise Linux,也就是所谓的Redhat Advance Server,收费版本)、Fedora Core(由原来的Redhat桌面版本发展而来,免费版本)、CentOS(RHEL的社区克隆版本,免费)。Redhat应该说是在国内使用人群最多的Linux版本,甚至有人将Redhat等同于Linux,而有些老鸟更是只用这一个版本的Linux。所以这个版本的特点就是使用人群数量大,资料非常多,言下之意就是如果你有什么不明白的地方,很容易找到人来问,而且网上的一般Linux教程都是以Redhat为例来讲解的。Redhat系列的包管理方式采用的是基于RPM包的YUM包管理方式,包分发方式是编译好的二进制文件。稳定性方面RHEL和CentOS的稳定性非常好,适合于服务器使用,但是Fedora Core的稳定性较差,最好只用于桌面应用。 Debian,或者称Debian系列,包括Debian和Ubuntu等。Debian是社区类Linux的典范,是迄今为止最遵循GNU规范的Linux系统。Debian最早由Ian Murdock于1993年创建,分为三个版本分支(branch): stable, testing和unstable。其中unstable为最新的测试版本,包括最新的软件包,但是也有相对较多的bug,适合桌面用户。testing的版本都经过unstable中的测试,相对较为稳定,也支持了不少新技术(比如SMP等)。而stable一般只用于服务器,上面的软件包大部分都比较过时,但是稳定和安全性都非常的高。Debian最具特色的是apt-get/dpkg包管理方式,其实Redhat的YUM也是在模仿Debian的APT方式,但在二进制文件发行方式中,APT应该是最好的了。 Ubuntu严格来说不能算一个独立的发行版本,Ubuntu是基于Debian的unstable版本加强而来,可以这么说,Ubuntu就是一个拥有Debian所有的优点,以及自己所加强的优点的近乎完美的Linux桌面系统。根据选择的桌面系统不同,有三个版本可供选择,基于Gnome的Ubuntu,基于KDE的Kubuntu以及基于Xfc的Xubuntu。特点是界面非常友好,容易上手,对硬件的支持非常全面,是最适合做桌面系统的Linux发行版本。 伟大的Gentoo是Linux世界最年轻的发行版本,正因为年轻,所以能吸取在她之前的所有发行版本的优点,这也是Gentoo被称为最完美的Linux发行版本的原因之一。Gentoo最初由Daniel Robbins(FreeBSD的开发者之一)创建,首个稳定版本发布于2002年。由于开发者对FreeBSD的熟识,所以Gentoo拥有媲美 FreeBSD的广受美誉的ports系统——Portage包管理系统。不同于APT和YUM等二进制文件分发的包管理系统,Portage是基于源代码分发的,必须编译后才能运行,对于大型软件而言比较慢,不过正因为所有软件都是在本地机器编译的,在经过各种定制的编译参数优化后,能将机器的硬件性能发挥到极致。Gentoo是所有Linux发行版本里安装最复杂的,但是又是安装完成后最便于管理的版本,也是在相同硬件环境下运行最快的版本。 最后,介绍一下FreeBSD,需要强调的是:FreeBSD并不是一个Linux系统!但FreeBSD与Linux的用户群有相当一部分是重合的,二者支持的硬件环境也比较一致,所采用的软件也比较类似,所以可以将FreeBSD视为一个Linux版本来比较。FreeBSD拥有两个分支: stable和current。顾名思义,stable是稳定版,而current则是添加了新技术的测试版。FreeBSD采用Ports包管理系统,与Gentoo类似,基于源代码分发,必须在本地机器编后后才能运行,但是Ports系统没有Portage系统使用简便,使用起来稍微复杂一些。 FreeBSD的最大特点就是稳定和高效,是作为服务器操作系统的最佳选择,但对硬件的支持没有Linux
后端技术探索
2018/08/09
2.1K0
这么多Linux版本,你究竟该怎么选择?
Linux有非常多的版本,比如世面上常见的有 Ubuntu、RedHat、Fedora、Centos等等,这么多的版本我们究竟该选哪一个呢?今天我带大家对各个版本进行一下分析和比较,帮助大家来做出更好的选择。
Coding十日谈
2020/08/13
2K0
这么多Linux版本,你究竟该怎么选择?
最佳 Linux 发行版汇总
Linux入门 Ubuntu Ubuntu是一款基于Debian发行版,以Unity作为默认桌面环境的Linux操作系统。他是世界上最流行的发行版之一,最新发行版为桌面、移动及其桌面移动混合版的优化。
小小科
2018/05/04
7.2K0
最佳 Linux 发行版汇总
oeasy教您玩转linux010102查看发行版
只靠 linux内核,咱们无法做任何事情,只有内核和各种应用程序在一起,咱们才能使用.
oeasy
2020/08/23
1K0
oeasy教您玩转linux010102查看发行版
Linux不同版本的区别以及使用建议
官网地址:https://www.redhat.com/zh/global/china
谭庆波
2018/08/10
2.8K0
Linux不同版本的区别以及使用建议
Linux发行版该如何选择?
对于很多刚使用Linux的人来说,选择使用哪个Linux发行版着实是一件头疼的事情,特别是对于有选择困难症的人们就更是如此了。其实对于那些著名的Linux发行版本,都有比较明显的个性,具体选择哪些,也只是由自己的需求而定,当然,如果你很熟悉Linux的话,那么版本的问题就不是问题了,因为在根本上它们都是相似的。
用户6543014
2019/10/25
7K0
Linux发行版该如何选择?
Linux阵营各版本的特点
  Linux最早由Linus Benedict Torvalds在1991年开始编写。在这之前,Richard Stallman创建了Free Software Foundation(FSF)组织以及GNU项目,并不断的编写创建GNU程序(此类程序的许可方式均为GPL: General Public License)。在不断的有杰出的程序员和开发者加入到GNU组织中后,便造就了今天我们所看到的Linux,或称GNU/Linux。
魏杰
2022/12/23
1.3K0
塔荐 | 2017 最优秀的十大 Linux 服务器发行版
前 言 在这篇文章中,我们将基于以下的考虑因素列出 2017 最优秀的十大 Linux 服务器发行版:与受支持的功能和硬件相关的数据中心的性能和可靠性;是否易于安装和使用;在许可方面的所有权和维护相关
灯塔大数据
2018/04/04
1.6K0
塔荐 | 2017 最优秀的十大 Linux 服务器发行版
盘点 2017 年度最受欢迎的十大 Linux 服务器发行版
本文列出了 2017 年十大 Linux 服务器发行版,包括 Ubuntu、Red Hat Enterprise Linux、SUSE Linux Enterprise Server、CentOS、Debian、Oracle Linux、Mageia、ClearOS、Arch Linux 和 Slackware。这些发行版各有特点,适用于不同规模、场景和技能水平的 Linux 服务器。在选择适合你的服务器发行版时,可以考虑操作系统支持的架构、软件包管理、许可证、成本以及社区支持等因素。
企鹅号小编
2018/01/05
2.2K0
盘点 2017 年度最受欢迎的十大 Linux 服务器发行版
一文搞懂 linux 常见的版本区别
目前在用 mac 通过虚拟机的方式安装 linux,不要问题为什么这样做,哈哈哈哈。遇到了关于 liunx 版本的问题,特地来总结一下
shengjk1
2022/05/07
1.3K0
初探Linux
操作系统(Operating System,OS)是软件的一部分,它是硬件基础上的第一层软件,是硬件和其它软件沟通的桥梁。
鱼丸葱面
2020/11/28
5710
初探Linux
相关推荐
Linux发行版:CentOS、Ubuntu、RedHat、Android、Tizen、MeeGo
更多 >
LV.3
这个人很懒,什么都没有留下~
目录
  • 介绍:
  • SpringBoot 快速入门:
    • 准备工作🐱‍👤:
    • 创建一个SpringBoot 的 Maven 工程:
      • ① 添加依赖
      • ② SpringBoot 启动类:
      • ③ entity 实体类:
      • ④ mapper 接口文件:
      • ⑤ SpringBoot 配置文件⚙:
      • ⑥ 运行测试:
    • Maven test模块下测试程序:
    • 总结:
  • MP 常用配置⚙:
    • 表/实体 映射:@TableName
      • 局部设置单独,针对某些表:
      • 全局设置表前缀:
    • 设置主键生成策略:@TableId
      • type 主键生成策略:IdType
      • 全局设置 主键生成策略:
    • 表字段/实体属性 映射:@TableFieid
    • MP 打印日志:
  • Mapper CRUD接口:
    • BaseMapper 接口方法:
    • 插入数据: Insert
    • 删除数据: Deletexxx
    • 修改数据: Updatexxx
  • Wrapper 调节构造器:
    • Condition
    • 常用AbstractWrapper方法
      • AbstractWrapper 案例:
    • 常用QueryWrapper方法
      • 示例一:
      • 示例二:
    • 常用UpdateWrapper方法
    • LambdaQueryWrapper 使用
  • 自定义Mapper sql:
    • MP 中使用 Mybatis 方式:
      • 定义Mapper接口:
      • 创建对应的映射文件, 编写sql:
      • yaml 设置项目 扫描对应的 sql映射文件:
      • 程序测试:
      • 总结:
  • Mybatis方式结合 MP条件构造器
    • 在SQL语句中获取Warpper拼接的SQL片段进行拼接
    • 方法定义中添加Warpper类型的参数
      • 程序测试:
  • 分页查询 Page
    • 单表分页查询:
      • ① 配置分页查询拦截器
      • ② 进行分页查询
    • 多表分页查询:
      • 准备工作:
      • 多表 sql 准备:
      • ① 定义OrdersMapper.Java 接口
      • ② 定义OrderMapper.xml 映射文件
      • ③ 编写测试程序:
      • ④ 运行测试:
  • Service CRUD接口:
    • 为什么MP 有了 Mapper接口, 还要 Service接口:
    • 编写User Service 的CRUD操作:
      • ① 编写 Service
      • ② 编写 Service 实现 ServiceImpl
      • ③ 测试:
    • 自定义 Service
      • ① 编写 Service
      • ② 编写 Service 实现 ServiceImpl
  • MP 的代码生成器:
    • ① 添加依赖
    • ② 添加生成类,运行生成文件
  • MP高级 自动填充
    • 实例Demo
      • ① 在需要操作的实体上, 添加 @TableFieId注解
      • ② 编写适配器:
      • ③ 测试:
  • MP高级 逻辑删除
    • 实现:
    • 测试:
  • MP高级 乐观锁
    • 乐观锁 和 悲观锁
    • MP 实现:
      • ① 配置乐观锁插件
      • ② 在实体类中表示版本的字段上添加注解@Version
      • ③ 操作:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档