MyBatis-Plus框架(千峰学习笔记)

news2024/11/20 20:39:19

简介

MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,主要作用 为简化开发、提高效率

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。

特性

● 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

● 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

● 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大 部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

● 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字 段写错

● 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可 自由配置,完美解决主键问题

● 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进 行强大的 CRUD 操作

● 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

● 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

● 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分 页等同于普通 List 查询

● 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、 SQLite、Postgre、SQLServer 等多种数据库

● 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快 速揪出慢查询

● 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规 则,预防误操作

支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,几乎都能支持,我们本次课程 主要使用的是MySql数据库

官方网址: https://www.baomidou.com/

为什么要使用

使用MyBatis-Plus的目的是为了大大简化持久层的开发,它可以帮助我们节省大量工作时间,所 有的CRUD代码都可以自动化完成,简单理解学习MyBatis-Plus的目的就是为了 “偷懒”

使用前提:

1. 需要掌握SSM

2. 掌握Maven

3. 掌握SpringBoot

快速开始

具体使用步骤如下:

1、导入对应的依赖

2、对应的配置

3、代码的编写

数据初始化

创建一个User用户表用于测试(直接采用官网提供的测试数据)

create database db_mybatisPlus character set utf8;

use db_mybatisPlus;

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
   id BIGINT(20) NOT NULL COMMENT '主键ID',
   name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
   age INT(11) NULL DEFAULT NULL COMMENT '年龄',
   email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
   PRIMARY KEY (id)
);

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

创建SpringBoot项目

1、导入依赖

        <!--  MyBatisPlus  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--  MySql  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--  lombok  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

注意:一般使用MyBatis-Plus以后就不要再导入MyBatis了,尽量避免它俩同时存在,有可能 会出现一些版本差异等的问题

2、配置application.properties

# MyBatis 8 需要配置时区 serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3307/db_mybatisplus?
useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8

传统方式配置到此,我们如果需要完成业务,就需要完成以下步骤:

● 实体类-dao(连接MyBatis,配置核心配置以及Mapper映射)

使用MyBatis-Plus也是三个步骤,但是却大大简化操作:

● 实体类-mapper-使用

Lombok

Lombok是一个java库,可以自动插入到你的编辑器和构建工具中,让你的java变得更加简单。 再也不用写其getter或equals方法了,只需一个注解,你的类就可以拥有一个功能齐全的 JavaBean

具体功能

为了在IDEA中支持Lombok功能,需要安装Lombok插件,打开File>Settings>安装Lombok插 件,安装以后才能支持Lombok提供的功能。

Lombok提供的常用注解:

● @Getter/@Setter:用在类或属性上,用在类上可以作用于这个类的所有属性,写在属性 上只作用于属性名,再也不用自己手写setter和getter方法了

● @ToString:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如 @ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属

● @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法

● @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用 在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数 的构造函数

● @RequiredArgsConstructor主要是对当前类中带有final 的属性进行属性注入

● @Data:注解在类上,相当于同时使用了 @Getter、 @ToString、 @Setter和 @EqualsAndHashCode、 @RequiredArgsConstrutor这些注解,对于 POJO类 十分有用

● @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter 方法,而不提供setter方法

● @Slf4j: 自动创建Slf4j日志对象log,用于记录系统日志信息

具体步骤

1、创建实体类User

package com.qf.mybatisplusdemo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

2、创建UserMapper

按照传统的MyBatis的方式此时我们需要做的是创建dao层,也就是需要有Mapper接口和对应的 Mapper.xml映射文件,但是一旦使用MyBatis-Plus以后,我们只需要写一个mapper接口继承 Plus提供的BaseMapper接口即可,此时就已经完成了几乎所有的CRUD的操作

package com.qf.mybatisplusdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qf.mybatisplusdemo.entity.User;
import org.springframework.stereotype.Repository;

/**
 * Description:
 */
@Repository
public interface UserMapper extends BaseMapper<User> {
    //自定义CRUD方法
    /**
     * BaseMapper中包含常用的,或者叫做通用的crud方法,但是如果我们自己的业务有
     * 一些特殊的CRUD操作,我们可以再这个接口中自定义,那么这个操作就和之前的MyBatis操作是一样的了
     */
}

我们可以查看BaseMapper的源码,不难看出,所有的crud功能MyBatis-Plus通过此类型已经帮 助我们实现了

3、开启扫描

为了能够让SpringBoot扫描并且识别此组件,我们需要在SpringBoot启动类上开启Mapper接口 扫描功能,添加@MapperScan()注解

//开启扫描,注意包名不要写错
@MapperScan("com.qf.mybatisplusdemo.mapper")
@SpringBootApplication
public class MyBatisPlusDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisPlusDemoApplication.class, args);
    }
}

4、测试

我们在测试类中通过单元测试来测试一下MyBatisPlus提供的通用方法

package com.qf.mybatisplusdemo;

import com.qf.mybatisplusdemo.entity.User;
import com.qf.mybatisplusdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

/**
 * Description:
 */
@SpringBootTest
public class MyBatisPlusDemoApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads(){
        //查询全部用户
        // 此方法需要我们传递Wrapper对象,此对象是条件构造器,我们现在要查询全部用户先不用,所以传null即可
        List<User> list = userMapper.selectList(null);
        list.forEach(user -> System.out.println(user));
    }
}

日志配置

其实这个道理和我们再用MyBatis的时候是一样的,我们希望知道具体执行的SQL语句,包括具 体的执行过程,所以此时我们需要通过日志的方式来完成。

在配置文件中添加日志配置

# 配置日志
mybatis-plus.configuration.logimpl=org.apache.ibatis.logging.stdout.StdOutImpl

重新执行查询最终效果

BaseMapper完成CRUD

BaseMapper中提供了常用的CRUD方法,我们需要测试一下其中复杂的方法,比如说条件查询 等

那我们就通过单元测试来验证一下

删除功能

BaseMapper中提供了5个删除方法,图中第三个方法的参数Wrapper是条件构造器,这个构造 器后续我们在探讨

第一个方法根据id删除,此方法跳过,因为比较简单,这里演示第二个与第四个方法

根据Map删除对应数据

    @Test
    public void deleteTest(){
        Map<String, Object> map = new HashMap<>();
        map.put("name","jerry");
        map.put("age",20);
        int num = userMapper.deleteByMap(map);
        System.out.println(num);
    }

根据多个id进行批量删除

    @Test
    public void deleteTest(){
        List<Long> ids = new ArrayList<>();
        ids.add(1661372335489392642L);
        ids.add(5L);
        int num = userMapper.deleteBatchIds(ids);
        System.out.println(num);
    }

修改功能

修改方法BaseMapper中提供了两个

根据id修改

此方法传入的参数是实体类对象,需要包含id和其他修改的内容

    @Test
    public void updateTest(){
        User user = new User();
        user.setId(4L);
        user.setName("李四");
        int num = userMapper.updateById(user);
        System.out.println(num);
    }

查询功能

查询功能是我们最常用的功能,所以BaseMapper中提供的查询方法也非常多,但是基本操作都 非常简单

其中以下图中的两个方法,分别是通过单个id查询数据,和id集合来查询数据,这两个方法不做 演示

此方法为根据Map中传递的条件进行查询

    @Test
    public void selectTest(){
        Map<String,Object> map = new HashMap<>();
        map.put("id",3L);
        map.put("name","Tom");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(user -> System.out.println(user));
    }

此方法Wrapper条件构造器传递参数为null表示查询全部数据

注意:所有的CRUD方法中只要涉及到Wrapper条件构造器的如果不需要使用都可以传入为 null,但是要注意一般查询可以传入,但是修改和删除一般不可以,因为如果删除和修改没有条 件会导致影响全部数据

新增功能

    @Test
    public void insertTest(){
        User user = new User();
        user.setName("张三");
        user.setAge(20);
        user.setEmail("10000@qq.com");
        int num = userMapper.insert(user);//自动生成id,并且id会自动回填
        System.out.println(num);
    }

但是各位我们要注意的是此时看日志大家会发现我们的id并没有给任何的策略但是这里却是自动 生成了一段id

其实这里MyBatis-Plus是通过雪花算法来进行自动生成的id,但是要注意:我们的主键必须叫 id,并且必须是包装类型,否则不生效

主键生成策略

在我们业务量不大的时候,单库单表完全可以支持现在的业务,数据再大一点读写分离也算 OK。但是随着数据量的增长,单库单表终究是抗不住的。那就需要分库分表。分库分表后肯定 不能依赖分表中的自增主键。

因此需要一个 生成全局唯一ID的 主键生成策略

常见的策略

● 数据库自增长序列或字段:最常见的方式。利用数据库,全数据库唯一。

● UUID:常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

● Redis生成ID:当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成 ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。

● Twitter的snowflake(雪花)算法:snowflake是Twitter开源的分布式ID生成算法,结果是一 个long型的ID。

● zookeeper生成唯一ID:zookeeper主要通过其znode数据版本来生成序列号,可以生成32 位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。

雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。

其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器 ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有 一个符号位,永远是0。

至于算法的具体源码学习,各位可以关注我们发的算法课程来详细学习

主键生成策略

这其中涉及到MyBatis-Plus中提供的一个注解@TableId

属性类型必须指定默认值描述
valueString""主键字段名
typeEnumIdType.NONE指定主键类型

源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
    String value() default "";

    IdType type() default IdType.NONE;
}

这里我们要关注IdType这个枚举类型

public enum IdType {
    AUTO(0),
    NONE(1),
    INPUT(2),
    ASSIGN_ID(3),
    ASSIGN_UUID(4);

    private final int key;

    private IdType(int key) {
        this.key = key;
    }

    public int getKey() {
        return this.key;
    }
}
描述
AUTO数据库 ID 自增,必须设置数据库自增主键
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert 前自行 set 主键值
ASSIGN_ID默认分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口 IdentifierGenerator的方法 DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID

分配 UUID,主键类型为 String(since 3.3.0),使用接口

IdentifierGenerator的方法 nextUUID(默认 default 方法)

主键自增演示(注意:一定要把表改为主键自增)

User类型

@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

最终效果:

自定义CRUD

MyBatis-Plus的BaseMapper本身提供了很多通用的CRUD方法,但是由于我们的业务问题有的 时候必须要自定义一些方法,那么该如何实现那?

其实主要记住MyBatis-Plus是在MyBatis的基础之上只做增强不做修改的即可,所以如果想要实 现自定义的方法,具体操作其实和MyBatis没有什么区别

配置

如果我们需要进行复杂映射就可能会涉及到映射文件mapper.xml那么这个文件位置的配置,我 们可以在properties配置文件中进行配置

其实我们会发现此配置有一个默认配置,就是在类路径下的mapper目录下的任意位置(包含下 一级目录),所以其实我们用这个默认配置即可

# 指定mapper文件位置
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml

注解方式

根据name查询用户信息

    /**
     * 根据name查询数据
     * @param name
     * @return
     */
    @Select("select * from user where name=#{name}")
    List<User> selectByName(String name);

测试

    @Test
    public void selectByNameTest(){
        List<User> users = userMapper.selectByName("李四");
        users.forEach(user -> System.out.println(user));
    }

xml方式

这种方式,我们需要在resource目录中新建一个mapper目录,然后新建UserMapper.xml文件

注意:要把@Select注解注释掉

<?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 namespace="com.qf.mybatisplusdemo.mapper.UserMapper">
    <!--  List<User> selectByName(String name);  -->

    <select id="selectByName" resultType="com.qf.mybatisplusdemo.entity.User">
        select * from user where name=#{name}
    </select>
</mapper>

结果:

IService接口

说明:

● 通用Service CRUD封装IService (opens new windows)接口,进一步封装CRUD采用 get查询单行 remove删除 list查询集合 page分页 前缀命名方式区分 Mapper 层避免混淆,

● 泛型 T 为任意实体对象

● 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类

● 对象 W rapper 为 条件构造器

其实一般情况下,由于项目业务的复杂程度,我们都会使用自定义Service方法,那么这些如果我 们想即使用通用的IService接口提供的方法,又有自定义的方法的话,我们可以参考IService接口 的实现类ServiceImpl

IService源码

这里我们可以关注一下具体有哪些方法

ServiceImpl源码

这里我们要关注当前类型的声明方式,我们可以模仿

m:mapper对象

t:实体

自定义Service实现类

创建UserService接口以及对应实现类,注意包结构

UserService接口

//自定义接口继承通用Iservice接口
public interface UserService extends IService<User> {
    //自定义Service方法
}

UserServiceImpl实现类

//按照ServiceImpl实现类编写自己的业务层实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    //自定义service方法实现
}

测试通用Service方法

批量添加

直接在测试类中进行调用方法即可

@SpringBootTest
public class MyBatisPlusDemoApplicationTests {
    @Autowired
    private UserService userService;

    @Test
    public void insertMoreTest(){
        //批量添加测试
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("qf"+i);
            user.setAge(18+i);
            user.setEmail("1000"+i+"@qq.com");
            users.add(user);
        }
        boolean b = userService.saveBatch(users);
        System.out.println(b);
    }
}

自动填充处理

我们一般按照阿里巴巴开发手册要求:所有数据库的表几乎都要配置以下两个字段,并要求自动 化处理:

● 创建时间(gmt_create、create_time)

● 修改时间(gmt_modified、update_time)

要完成这两个字段的自动化处理有两种方式

1、数据库级别

2、代码级别

前提

修改user表,添加这两个字段

测试数据库级别

注意:此种方式工作中不可以使用,因为工作中是不允许修改数据库的

直接执行更新操作

    @Test
    public void updateTest1(){
        User user = new User();
        user.setId(4L);
        user.setName("王五");
        int num = userMapper.updateById(user);
        System.out.println(num);
    }

测试代码级别

准备:清除数据库所有的默认值和更新操作

这里我们需要使用到MyBatis-Plus提供的另外一个注解@TableField(.. fill = FieldFill.INSERT)

User类型

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableField() //注解填充字段
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

这里我们可以看一下此注解的源码,这里我们要关注以下内容

其中FieldFill是:字段填充策略枚举类

我们现在要求的策略就是,新增数据时,这两个字段都要更新,同时修改的时候update_time字 段要更新,所以具体策略设置如下

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

 最后,我们需要实现并重写处理器MetaObjectHandler来完成具体策略

package com.qf.mybatisplusdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Slf4j
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
    //插入数据时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());
        this.strictInsertFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now());
    }

    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("update insert fill ....");
        this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());
    }
}

 注意:

● 填充原理是直接给 entity的属性设置值!!!

● 字段必须声明 TableField注解,属性 fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段

● 填充处理器MyMetaObjectHandler在Spring Boot中需要声明@Component或@Bean注入

演示插入

演示修改

最终查看数据库

乐观锁插件

乐观锁与悲观锁

在程序世界中,乐观锁和悲观锁的最终目的都是为了保证线程安全,避免在并发场景下的资源竞 争问题。但是,相比于乐观锁,悲观锁对性能的影响更大!

字面意思理解:

乐观锁:总是假设最好的情况,认为别人都是友好的,所以每次获取数据的时候不会上锁,但更 新数据那一刻会判断数据是否被更新过了,如果数据的值跟自己预期一样的话,那么就可以正常 更新数据。

悲观锁:悲观锁就好像一个有迫害妄想症的患者,总是假设最坏的情况,每次拿数据的时候都以 为别人会修改,所以每次拿数据的时候都会上锁,直到整个数据处理过程结束,其他的线程如果 要拿数据就必须等当前的锁被释放后才能操作。

MyBatis-Plus给出的实现方式

● 取出记录时,获取当前 version

● 更新时,带上这个 version

● 执行更新时, set version = newVersion where version = oldVersion

● 如果 version 不对,就更新失败

具体实现

首先第一件事情,需要在当前User表中添加版本version字段

ALTER TABLE db_mybatisplus.`user` ADD version INT DEFAULT 1 NULL COMMENT 
'乐观锁';

更新实体类,并且添加乐观锁注解

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Version//乐观锁注解
    private Integer version;
}

接下里注册组件(乐观锁拦截器),具体组件在官网上,我们可以直接复制粘贴即可

首先我们需要创建一个配置类,此配置类型是MyBatis-Plus的配置类,所以一切有关MyBatis-Plus的配置都可以放到这里,这也是推荐的方式

新建:config.MyBatisPlusConfig

@Configuration
//开启扫描,注意包名不要写错(有了MyBatisPlus的配置类,我们就可以把所有的相关配置都写在这里)
@MapperScan("com.qf.mybatisplusdemo.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //配置MyBatis-Plus插件(拦截器)
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //具体配置插件(乐观锁)
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

单线程演示

在测试类中编写测试方法,来执行一个修改操作

    //成功
    @Test
    public void mybatisPlusInterceptorTest() {
        //查询用户信息
        User user = userMapper.selectById(3L);
        //修改此用户信息
        user.setName("abc");
        user.setAge(10);
        //执行更新
        userMapper.updateById(user);
    }

查看结果,确实这里在修改时会增加判断version条件

多线程争抢演示

在多线程时,我们模拟第二个线程抢先完成修改操作,让乐观锁生效

    //失败
    @Test
    public void mybatisPlusInterceptorTest2() {
        //----------------线程1----------------------
        //查询用户信息(查出version)
        User user1 = userMapper.selectById(3L);
        //修改此用户信息
        user1.setName("abc");
        user1.setAge(10);

        //---------------线程2------------------------
        User user2 = userMapper.selectById(3L);
        user2.setName("efg");
        user2.setAge(15);
        //先执行更新线程2(模拟线程2抢先完成修改操作)
        userMapper.updateById(user2);
        //再执行更新线程1 (如果乐观锁不存在会覆盖线程1的值)
        //可以尝试进行多次提交
        userMapper.updateById(user1);
    }

此时我们可以通过日志来进行查看,会发现,执行了两次的查询,以及两次的修改,并且按照执 行顺序来说,如果没有乐观锁应该是线程1覆盖线程2的值,但是此时乐观锁生效,所以最终的结 果还是线程2抢先执行之后的值

分页插件

MyBatisPlus还提供了分页插件,可以帮助我们快速的完成分页操作,简化原生操作

其实使用分页和乐观锁插件一样,都需要引入组件,步骤和乐观锁插件是一样的,我们都需要在 配置类中配置具体插件

MyBatisPlusConfig

@Configuration
//开启扫描,注意包名不要写错(有了MyBatisPlus的配置类,我们就可以把所有的相关配置都写在这里)
@MapperScan("com.qf.mybatisplusdemo.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //配置MyBatis-Plus插件(拦截器)
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //具体配置插件(乐观锁)
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
}

Page:该类继承了 IPage 类,实现了 简单分页模型 如果你要实现自己的分页模型可以继承 Page 类或者实现 IPage 类

进行单元测试

如果测试的单元测试方法为业务层方法,那么前端传递具体用户点击的页码就能实现分页功能

    @Test
    public void PaginationInnerInterceptorTest(){
        //简单分页模型  current:第几页   size:显示几条数据
        //底层逻辑就是使用Limit分页的公式 (index-1)*pageSize
        Page<User> page = new Page<>(1, 3);
        //条件构造器Wrapper目前没有就写null
        userMapper.selectPage(page,null);
    }

第一页分页,Limit语句可以只有一个参数即可

Page对象常用方法

    @Test
    public void PaginationInnerInterceptorTest(){
        //简单分页模型  current:第几页   size:显示几条数据
        //底层逻辑就是使用Limit分页的公式 (index-1)*pageSize
        Page<User> page = new Page<>(1, 3);
        //条件构造器Wrapper目前没有就写null
        userMapper.selectPage(page,null);
        //获取记录
        List<User> users = page.getRecords();
        users.forEach(user -> System.out.println(user));
        System.out.println(page.getPages());  //获取总页数
        System.out.println(page.getTotal());  //获取总数据量
        System.out.println(page.hasNext());  //是否有下一页
        System.out.println(page.hasPrevious());  //是否有上一页
    }

逻辑删除

讲到这里基本上一些增删改查,包括分页等操作我们都已经搞定了,那么在企业中我们对于删 除,一般情况下是不会真正的把数据删除掉的,而是会进行 逻辑删除

所以大家会发现一些系统,管理员可以看到已经删除的用户信息,实际上这就是一种逻辑删除, 也就是根据数据表中的类似于:deleted的属性来标记用户删除,而并非真正删除。

逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。

具体操作

在User表中添加字段deleted

 ALTER TABLE db_mybatisplus.`user` ADD deleted INT DEFAULT 0 NULL COMMENT 
'逻辑删除 0表示未删除 1表示删除';

更新实体类

在实体类中给对应deleted属性配置注解@TableLogic

此注解有两个属性(可以默认不写)

● value:默认逻辑未删除值

● delval:默认逻辑删除值

@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Version//乐观锁注解
    private Integer version;
    @TableLogic(value = "0",delval = "1") //逻辑删除(这两个值也可以不设置)
    private Integer deleted;
}

测试删除

    @Test
    public void deletedTest(){
        userMapper.deleteById(2L);
    }

逻辑删除会把删除语句变成修改语句

注意:查询的时候因为添加了@TableLogic注解,所以会自动跳过被标记的数据

条件构造器

MyBatis-Plus提供了强大的条件构造器。通过条件构造器可以写一些复杂的SQL语句,从而提高 开发效率。

查询mybatisPlus源码可以看到,条件构造器wrapper继承情况

● Wrapper:条件构造器,最顶端的一个类

    ○ AbstractWrapper:用于sql语句条件的封装,主要是封装where条件

         ■ QueryWrapper:查询条件封装(一般删除的条件也使用QueryWrapper)

         ■ UpdateWrapper:更新条件封装

         ■ AbstractLambdaWrapper:具有Lambda语法的条件封装

              ■ LambdaQueryWrapper:具有Lambda语法查询条件封装

              ■ LambdaUpdateWrapper:具有Lambda语法更新条件封装

所有的条件使用方法,在官网中有很详细的介绍

具体地址: https://www.baomidou.com/pages/10c804/#abstractwrapper

QueryWrapper

一般我们比较常用的就是这个QueryWrapper,因为查询的业务居多,比如我们可以举几个例 子:

首先创建一个测试类WrapperTest,先做一个测试

    // 查询name不为空的用户,并且年龄大于18岁
    @Test
    void selectTest1() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name")
                .ge("age", 18);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

查看结果,主要查看执行的SQL语句

其实看了一个大家应该就明白了,复杂查询我们现在完全可以通过这种方式来进行完成,需要使 用什么条件,通过官网查询一下具体方法即可

比如多做几个实验

    // 查询名字为jack的用户
    @Test
    void selectTest2() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name", "jack");
        User user = userMapper.selectOne(wrapper);//类似于map传入条件
        System.out.println(user);
    }

    // 查询年龄18~25岁之间的用户
    @Test
    void selectTest3() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age", 18, 25);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    // 查询年龄大与等于25岁的用户数量
    @Test
    void selectTest4() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ge("age", 25);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    // 模糊查询:查询名字不包含qf的用户,反之like就是包含
    @Test
    void selectTest5() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.notLike("name", "qf");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    //模糊查询:包含左侧或者右侧具体内容
    @Test
    void selectTest6() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 模糊查询:查询名字不包含qf的用户,反之like就是包含
        // likeLeft和likeRight
        // 左右就是 %在左或者在右
        // 以下就是 e%  相当于以e开头的名字
        wrapper.likeRight("name","e");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    //通过子查询,查询id等于3的用户信息,此方法也可以进行表关联查询
    @Test
    void selectTest7(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 模糊查询:查询名字不包含qf的用户,反之like就是包含
        wrapper.inSql("id","select id from user where id=3");
        userMapper.selectObjs(wrapper).forEach(System.out::println);
    }

总之这些演示,大家要记住每个都要分析执行的SQL,这样才能掌握QueryWrapper的具体用 法。

lambda表达式执行条件

在QueryWrapper中有and或者or这样的方法,要注意的是默认都是通过and来进行连接条件, 但是如果主动调用or方法,将会改变,还有一点如果and或者or表达式时中出现lambda表达式, 将会改变条件的优先级,先来执行lambda表达式的条件

错误写法:

    // 查询用户名中包含qf并且(年龄大于26或者邮箱为null)的用户
    @Test
    void selectTest8(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name","qf").gt("age",26).or().isNull("email");
        userMapper.selectList(wrapper).forEach(System.out::println);
    }
    -----------------执行的SQL--------------------

此时执行的SQL语句不符合要求

and(Consumer<Param> consumer) //此方法要求传入Lambda表达式
or(Consumer<Param> consumer) //此方法要求传入Lambda表达式

这里我们可以查看一下源码,从源码中可以看出当前的泛型就相当于是一个条件构造器

正确写法(采用Lambda表达式写的条件优先执行):

    // 查询用户名中包含qf并且年龄大于26或者邮箱为null的用户
    @Test
    void selectTest8(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name","qf")
                .and(w -> w.gt("age",26).or().isNull("email"));
        userMapper.selectList(wrapper).forEach(System.out::println);
    }
    -----------------执行的SQL--------------------
    SELECT id,name,age,email,version,deleted,create_time,update_time FROM user 
    WHERE deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))

condition

我们在写项目的时候,所有的条件都是由用户进行传递的,那么有的时候就无法避免参数出现空 值null的情况,所以我们应该要做一些判断,其实很多方法都提供了boolean condition这个参 数,表示该条件是否加入最后生成的sql中,也就是可以通过它来进行判断

    // 模糊查询:查询名字包含qf的用户,并且按照age升序排序,注意参数不能为空
    @Test
    void selectTest9(){
        //假设用户传递参数
        String name = "f";
        Integer age = null;
        //先判断条件是否符合,符合才会组合到SQL语句中
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name),"name",name)
                .orderByAsc(age != null,"age");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

QueryWrapper执行修改和删除操作

其实QueryWrapper也是可以进行修改和删除操作的,因为我们的BaseMapper中提供了对应的 方法

修改

    //修改用户id为5的name为tom
    @Test
    void updateTest1(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("id",5L);
        User user = new User();
        user.setName("Tom");
        userMapper.update(user,wrapper);
    }

执行SQL

删除

    //删除name为Tom的用户
    @Test
    void deleteTest1(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","Tom");
        userMapper.delete(wrapper);
    }

UpdateWrapper

继承自 AbstractWrapper ,自身的内部属性entity 也用于生成 where 条件

可以通过set方法来进行修改

    //修改年龄大于26,并且name为mask的用户邮箱为 qf1000@qq.com
    @Test
    void updateTest2(){
        UpdateWrapper<User> wrapper = new UpdateWrapper<>();
        wrapper.gt("age",26)
                .eq("name","mask")
                .set("email","qf1000@qq.com");
        //无需传递user对象,直接赋值为null
        userMapper.update(null,wrapper);
    }

LambdaQueryWrapper&LambdaUpdateWrapper

它们两个的主要目的是为了防止我们在编写的时候,字段名称编写错误,我们可以直接通过 Lambda的方式来直接获取指定字段对应的实体类对应的名称

LambdaQueryWrapper 查询

    @Test
    void selectTest10(){
        //假设用户传递参数
        String name = "f";
        Integer age = null;
        //LambdaQueryWrapper就是为了防止我们写错字段,可以直接通过Lambda的方式来直接获取指定字段对应的实体类对应的名称
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name),User::getName,name)
                .orderByAsc(age != null,User::getAge);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

LambdaUpdateWrapper 修改

    //修改年龄大于26,并且name为mask的用户邮箱为 qf1000@qq.com
    @Test
    void updateTest3(){
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.gt(User::getAge,26)
                .eq(User::getName,"mask")
                .set(User::getEmail,"qf1000@qq.com");
        //无需传递user对象,直接赋值为null
        userMapper.update(null,wrapper);
    }

代码生成器

代码生成器的使用官网有详细的配置,我们可以参考来进行完成

具体地址:代码生成器(新) | MyBatis-Plus (baomidou.com)icon-default.png?t=N7T8https://www.baomidou.com/pages/779a6e/#%E5%AE%89%E8%A3%85

安装

引入对应的依赖

        <!--  MyBatisPlus  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--  代码生成器  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--  模板引擎  -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>

使用

只需要复制官网的代码,创建测试类 FastAutoGeneratorTest类型,把代码复制其中,按照自己 的要求修改之后执行即可

public class FastAutoGeneratorTest {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/db_mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root", "1234")
                .globalConfig(builder -> {
                    builder.author("mask") // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://mybatis_plus"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.qf") // 设置父包名
                            .moduleName("mybatisplus") // 设置父包模块名

                            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatis_plus"));
                    // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("user"); // 设置需要生成的表名
                    //.addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker 引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

MyBatisX插件

MyBatis-Plus确实能够为我们提高很多的开发效率,但是当它碰到一些复杂的sql,比如说多表联 查的时候,可能我们就需要使用原生的MyBatis方式来进行编写了,所以MyBatis-Plus提供了 MybatisX插件来帮助我们解决问题。

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

官网具体配置地址:MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

安装之后,我们所有的Mapper映射图标会发生变化,当我们点击图标时,可以帮助我们快速跳 转到对应的接口以及映射方法上

MyBatisx代码生成器

我们来创建一个新的Boot项目来进行测试 myBatisXDemo,此项目只需要复制Maven依赖,以 及对应的properties配置即可

使用此功能的前提是要保证先在 idea 配置 Database 配置数据源

还要使用DataBase功能

配置连接数据库

选择使用的数据库

找到要使用的表User选择MyBatisX-Generator

next

点击finish以后,我们看一下生成的效果

JPA提示

帮助我们快速生成接口方法以及对应的映射

我们只需要按照“见名知意”的规则来编写即可,会自动生成对应的方法以及Mapper映射

UserMapper

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1466082.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XFF伪造 [MRCTF2020]PYWebsite1

打开题目 直接查看源码 看到一个./flag.php 访问一下 购买者的ip已经被记录&#xff0c;本地可以看到flag&#xff0c;那么使用xff或者client-ip伪造一下ip试试 bp抓包 加一个X-Forwarded-For头 得到flag

无线局域网(WLAN)简单概述

无线局域网 无线局域网概述 无限局域网&#xff08;Wireless Local Area Network&#xff0c;WLAN&#xff09;是一种短距离无线通信组网技术&#xff0c;它是以无线信道为传输媒质构成的计算机网络&#xff0c;通过无线电传播技术来实现在空间传输数据。 WLAN是传输范围在1…

2、Web攻防-SQL注入-联合查询注入

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 声明&#xff1a;只用于学习交流&#xff0c;点到为止&#xff0c;请勿非法测试。 概念&#xff1a; 联合查询注入&#xff1a;联合注入是回显注入的一种&#xff0c;也就是说联合注入的前…

关于Android下gralloc,hwcompoer以及surface模块的重新认识

关于Android下gralloc&#xff0c;hwcompoer以及surface模块的重新认识 引言 欠债还钱天经地义&#xff0c;知识的债也是如此&#xff01;这不必须得将我前面欠下来的债给补上&#xff01;对于任何复杂的知识点&#xff0c;我们都可以采用庖丁解牛的学习方式&#xff0c;一步步…

DSL Query基本语法

DSL Query基本语法 查询的基本语法如下&#xff1a; GET /indexName/_search {"query":{"查询类型":{"查询条件":"条件值"}} }查询所有 GET /indexName/_search {"query":{"match_all":{}} }match查询&#xf…

5分钟让你搞懂什么是Http协议

计算机网络基础课程是计算机专业方向非常重要的一门功课。 所有的互联网都通过网络协议来建立通信连接。 而http协议又是一种无状态的协议&#xff0c;也是工作中最常用的一种基于Web浏览器的网络通信协议。 如何学习http协议&#xff1f;提供三种方法供参考&#xff1a; 第…

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测 目录 分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测分类效果基本描述程序设计参考资…

4.网络游戏逆向分析与漏洞攻防-游戏启动流程漏洞-模拟游戏登陆器启动游戏并且完成注入

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏启动流程的分析 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;bcf7559184863febdcad819e48aaacad9f25d633 代码下…

第三百六十二回

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何创建垂直方向的Switch"相关的内容&#xff0c;本章回中将介绍SlideSwitch组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

在ubuntu20.04 上配置 qemu/kvm linux kernel调试环境

一&#xff1a;安装qemu/kvm 和 virsh qemu/kvm 是虚拟机软件&#xff0c;virsh是管理虚拟机的命令行工具&#xff0c;可以使用virsh创建&#xff0c;编辑&#xff0c;启动&#xff0c;停止&#xff0c;删除虚拟机。 &#xff08;1&#xff09;&#xff1a;安装之前&#xff0c…

九州金榜|家庭教育小技巧,孩子好习惯养成记

家庭教育对于孩子的发展至关重要&#xff0c;家长一定要重视孩子在家里的举动&#xff0c;要及时纠正孩子的不足&#xff0c;发展孩子的优良品德和教孩子养成勤俭朴素的的好习惯。九州金榜家庭教育将从以下方面说一下家庭教育中的方法技巧。 一、家长以身作则 家长教育孩子&a…

js设计模式:策略模式

作用: 根据不同的条件去进行相应的业务逻辑处理 就好比针对每种情况都制定对应的方案,触发条件就启动某项方案策略 示例: //策略对象const arrangeFun {model1:(value1,value2,value3,value4)>{return ${value1}${value2}${value3}:${value4}},model2:(value1,value2,va…

【Postgres】11、PROCEDURE 存储过程、FUNCTION 函数、使用方式和区别

文章目录 一、PROCEDURE1.1 语法1.2 描述1.3 参数1.4 示例 二、FUNCTION2.1 语法2.2 重载2.3 示例2.4 兼容性2.5 示例2.5.1 declare variable 定义变量2.5.2 declare、ARRAY、ANY2.5.2.1 ARRAY 和 ANY 三、其他3.1 PL/pgSQL 在PostgreSQL中&#xff0c;存储过程&#xff08;Pro…

Nginx跳转模块之rewrite

一.location与rewrite模块的区别 rewrite&#xff1a;对访问的域名或者域名内的URL路径地址重写 location&#xff1a;对访问的路径做访问控制或者代理转发 二.rewrite模块基本内容 1.功能 通过正则表达式的匹配来改变URI&#xff0c;可以同时存在一个或多个指令&#xff0c…

前端工程Bem架构及其封装

文章目录 简介语法在vue3项目中引用sass创建bem.scss文件修改vite.config.tsvue文件中使用结果 这是我学习记录的笔记&#xff0c;如有不正&#xff0c;欢迎补充 简介 首先认识一下什么是bem架构&#xff1f;BEM的意思就是块&#xff08;block&#xff09;、元素&#xff08;e…

学校档案室管理制度内容

学校档案室管理制度是指对学校档案室进行管理的规定和流程。以下是一个示例的学校档案室管理制度的内容&#xff1a; 1.档案室的管理部门和责任人员&#xff1a; 学校档案室由学校行政部门负责管理&#xff0c;行政部门指定专门的档案管理员负责档案室的日常管理工作。 2.档案室…

ChatGPT在数据处理中的应用

ChatGPT在数据处理中的应用 今天的这篇文章&#xff0c;让我不断体会AI的强大&#xff0c;愿人类社会在AI的助力下走向更加灿烂辉煌的明天。 扫描下面二维码注册 ​ 数据处理是贯穿整个数据分析过程的关键步骤&#xff0c;主要是对数据进行各种操作&#xff0c;以达到最终的…

Boom 3D for Mac 破解版(3D环绕立体声音效增强软件)2.0.2中文支持M3

Mac上想要听一场极致的音乐或看一场畅快淋漓的电影&#xff1f;这些Boom 3D for Mac都可以帮您实现&#xff0c;是一款Mac音效增强工具&#xff0c;可以将二维度的音效转换成三维度&#xff0c;让您彻底的享受一下极致的听觉盛宴&#xff01; Boom 3D 2.0.2 Mac版主打音乐播放器…

【计算机毕业设计】541鲜花商城系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

驾校预约|驾校预约小程序|基于微信小程序的驾校预约平台设计与实现(源码+数据库+文档)

驾校预约小程序目录 目录 基于微信小程序的驾校预约平台设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户​微信端功能模块​ 2、管理员服务端功能模块 &#xff08;1&#xff09;学员信息管理 &#xff08;2&#xff09; 教练信息管理 &#xff08;3&…