MybatisPlus

目录

MybatisPlus概述

MybatisPlus特性

MP快速入门

第一步:数据库准备

第二步:导入依赖

第三步:编写实体类

第四步:编写mapper接口

第五步:测试

MP实现增删查改功能

插入功能

主键属性使用的注解@TableId

主键生成策略

普通列注解-@TableField

1.成员变量名和数据库字段名名字不一样,且不符合驼峰映射时

2.类的成员变量不需要进行映射,即没有匹配的数据库字段名

删除功能

修改功能

查询功能

MP实现分页查询

QueryWrapper实现基础查询

QueryWrapper常用API

基础查询实现

OR查询

QueryWrapper模糊查询like

模糊查询常用方法

QueryWrapper排序查询

核心方法

QueryWrapper限定字段查询

QueryWrapper实现分页条件查询

LambdaQueryWrapper查询

使用QueryWrapper开发存在的问题

LambdaQueryWrapper实现条件删除和更新操作

自定义接口

MP实现Service封装

实现流程

快速使用

一.自定义一个接口继承IService接口

二.自定义一个类继承ServiceImpl类和实现自定义的接口

MP封装Service实现CRUD操作

使用MybatisX生成代码

逻辑删除

添加逻辑删除字段

[​编辑 配置MP让其走逻辑删除](#编辑 配置MP让其走逻辑删除)

使用@TableLogic注解指定逻辑删除的字段

测试

乐观锁

乐观锁在数据库中有什么优势?

使用乐观锁

一.给数据库表添加一个乐观锁字段

二.在配置类中的插件bean添加乐观锁

三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)

四.测试

实现字段自动填充

一.添加成员变量

二.配置拦截器实现MetaObjectHandler

三.测试


MybatisPlus概述

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生(提供了快速使用mybatis的方式)。

官网:MyBatis-PlusRedirect

MybatisPlus特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

MP快速入门

第一步:数据库准备

sql 复制代码
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_user  没有给自增
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` VALUES (1, '赵一伤', '123456', 'zys', 19, 'zys@itcast.cn');
INSERT INTO `tb_user` VALUES (2, '钱二败', '123456', 'qes', 18, 'qes@itcast.cn');
INSERT INTO `tb_user` VALUES (3, '孙三毁', '123456', 'ssh', 20, 'ssh@itcast.cn');
INSERT INTO `tb_user` VALUES (4, '李四摧', '123456', 'lsc', 20, 'lsc@itcast.cn');
INSERT INTO `tb_user` VALUES (5, '周五输', '123456', 'zws', 20, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (6, '吴六破', '123456', 'wlp', 21, 'wlp@itcast.cn');
INSERT INTO `tb_user` VALUES (7, '郑七灭', '123456', 'zqm', 22, 'zqm@itcast.cn');
INSERT INTO `tb_user` VALUES (8, '王八衰', '123456', 'wbs', 22, 'wbs@itcast.cn');
INSERT INTO `tb_user` VALUES (9, '张无忌', '123456', 'zwj', 25, 'zwj@itcast.cn');
INSERT INTO `tb_user` VALUES (10, '赵敏', '123456', 'zm', 26, 'zm@itcast.cn');
INSERT INTO `tb_user` VALUES (11, '赵二伤', '123456', 'zes', 25, 'zes@itcast.cn');
INSERT INTO `tb_user` VALUES (12, '赵三伤', '123456', 'zss1', 28, 'zss1@itcast.cn');
INSERT INTO `tb_user` VALUES (13, '赵四伤', '123456', 'zss2', 29, 'zss2@itcast.cn');
INSERT INTO `tb_user` VALUES (14, '赵五伤', '123456', 'zws', 39, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (15, '赵六伤', '123456', 'zls', 29, 'zls@itcast.cn');
INSERT INTO `tb_user` VALUES (16, '赵七伤', '123456', 'zqs', 39, 'zqs@itcast.cn');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

第二步:导入依赖

html 复制代码
 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--mybatisplus起步依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

第三步:在application配置文件中编写数据源配置,连接数据库,并让sql语句打印在终端

html 复制代码
#配置数据源
spring:
  datasource:
    username: root
    password: 1234
    url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第三步:编写实体类

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private String email;
}

第四步:编写mapper接口

java 复制代码
/**
 * 编写接口继承BaseMapper接口,
 * 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

第五步:测试

java 复制代码
@SpringBootTest
public class TestUserMapper {
    @Autowired
    private UserMapper userMapper;
    @Test
    public void test01(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }
}

可以发现usrName成员变量已经自动驼峰映射成use_name,因为mp默认开启驼峰映射

MP实现增删查改功能

插入功能

注意事项:

1.如果主键对应的实体类属性中没有设置主键的生成策略,那么MP自动使用雪花算法为主键生成值,且回填到实体对象下;

2.如果未指定主键生成策略,即使表的主键是主键自增,也不会使用主键自增,因为默认的生成策略是使用算法算法生成一个主键id;

测试

java 复制代码
    @Test
    public void test02(){
        User user=User.builder()
                .userName("hhh")
                .email("153@qq.com")
                .age(18)
                .name("a")
                .password("123")
                .build();
        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

可以看到主键id是使用雪花算法生成的一个Long型id,即使我们已经为id主键设置了主键自增

并且这个生成的主键id已经回填到了实体类中

主键属性使用的注解@TableId

使用注解@TableId指定id生成策略,让其使用主键自增,而不是使用雪花算法生成一个不重复的Long型id

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
    /**
     * value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
     * 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
     */
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private String email;
}

主键生成策略

MP提供的常用主键生成策略如下:

生成策略 应用场景 特点
IdType.AUTO 数据库主键自增(确保数据库设置了 主键自增 否则无效) 1.使用数据库自带的主键自增值; 2.数据库自增的主键值会回填到实体类中; 3.数据库服务端生成的;
IdType.ASSIGN_ID 主键类型为number类型或数字类型String 1.MP客户端生成的主键值; 2.生成的主键值是数字形式的字符串 3.主键对应的类型可以是数字类型或者数字类型的字符串 4.底层基于雪花算法,让数据库的唯一标识也参与id的生成运算,保证id在分布式环境下,全局唯一(避免id的主键冲突问题);
IdType.ASSIGN_UUID 主键类型为 string(包含数字和字母组成) 1.生成的主键值包含数字和字母组成的字符串; 2.注意事项:如果数据库中主键值是number类型的,可不可用

普通列注解-@TableField

注解@TableField作用:

  • 指定表中普通字段与实体类属性之间的映射关系;
  • 忽略实体类中多余属性与表中字段的映射关系(@TableField(exist = false));

以下情况可以省略:

  • 名称一样
  • 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)

1.成员变量名和数据库字段名名字不一样,且不符合驼峰映射时

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
    /**
     * value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
     * 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
     */
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    /**
     * 使用@TableField注解让这个成员变量与字段进行映射
     */
    @TableField(value = "user_name")
    private String realName;
    private String password;
    private String name;
    private Integer age;
    private String email;
}

可以看到自动生成的sql语句使用了AS关键字

2.类的成员变量不需要进行映射,即没有匹配的数据库字段名

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
    /**
     * value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
     * 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
     */
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    /**
     * 使用@TableField注解让这个成员变量与字段进行映射
     */
    @TableField(value = "user_name")
    private String realName;
    private String password;
    private String name;
    private Integer age;
    private String email;
    /**
     * 让这个成员变量不进行sql语句的赋值
     */
    @TableField(exist = false)
    private String address;
}

可以看到address成员变量并没有在自动生成的sql语句里显示

删除功能

1.根据id删除

java 复制代码
 int i = userMapper.deleteById(8L);

2.根据id批量删除

java 复制代码
 int i = userMapper.deleteBatchIds(Arrays.asList(10L, 11));

3.根据map构造条件,删除

java 复制代码
Map<String, Object> map = new HashMap<>();

//delete from tb_user where user_name = ? and age = ?
map.put("user_name","itcast");
map.put("age","18");

userMapper.deleteByMap(map);

修改功能

java 复制代码
    @Test
    public void test04(){
        User user=User.builder()
                .id(2L)
                .password("1277")
                .build();
        //根据主键id来修改数据库的信息,如果这个对象的成员变量值为null,就不修改数据库的字段名
        int i = userMapper.updateById(user);
        System.out.println(i);
    }

查询功能

MP实现分页查询

MP的分页拦截器并没有自动装配,所以需要我们在配置类自己维护一个分页拦截器的bean

java 复制代码
@Configuration
public class MybatisPlusConfig {
    /**
     * 用于批量注册mp的插件bean
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //构建批量注册插件的对象
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //构建分页插件对象,并指定mysql数据库
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        //配置每页的大小,-1为无上限
        paginationInnerInterceptor.setMaxLimit(-1L);
        //将分页插件注册
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);

        return mybatisPlusInterceptor;
    }
}

测试

java 复制代码
    @Test
    public void test05(){
        int page=2;
        int pageSize=4;
        //构建分页对象
        IPage<User> pageInfo =  new Page<>(page, pageSize);
        //pageInfo==userIPage-->true,这两个对象是同一个,地址相同
        //null为无条件查询
        IPage<User> userIPage = userMapper.selectPage(pageInfo, null);
        System.out.println(pageInfo==userIPage);
        //获取分页参数
        long total = userIPage.getTotal();//获取总记录数
        long pages = userIPage.getPages();//获取总页数
        long size = userIPage.getSize();//获取当前页大小
        long current = userIPage.getCurrent();//获取当前的页数
        List<User> users = userIPage.getRecords();//获取当前页的对象集合

    }

改进:只使用一个分页对象

java 复制代码
    @Test
    public void test05(){
        int page=2;
        int pageSize=4;
        //构建分页对象
        IPage<User> pageInfo =  new Page<>(page, pageSize);
        //pageInfo==userIPage-->true,这两个对象是同一个,地址相同
        //null为无条件查询
//        IPage<User> userIPage = userMapper.selectPage(pageInfo, null);
        //TODO:因为这两是同一个对象,所以可以直接不定义一个新对象
        userMapper.selectPage(pageInfo,null);
        //获取分页参数
        long total =pageInfo.getTotal();//获取总记录数
        long pages =pageInfo.getPages();//获取总页数
        long size = pageInfo.getSize();//获取当前页大小
        long current= pageInfo.getCurrent();//获取当前的页数
        List<User> users =pageInfo.getRecords();//获取当前页的对象集合

    }

QueryWrapper实现基础查询

QueryWrapper常用API

java 复制代码
eq( ) :  等于 =
ne( ) :  不等于 <> 或者 !=
gt( ) :  大于 >
ge( ) :  大于等于  >=
lt( ) :  小于 <
le( ) :  小于等于 <=
between ( ) :  BETWEEN 值1 AND 值2 
notBetween ( ) :  NOT BETWEEN 值1 AND 值2 
in( ) :  in
notIn( ) :not in
like() : like 模糊查询,左右都加%
RightLike()  :只有右边加%
LeftLike():只有左边加%

sql中反向查询eg:not like != 等等,查询时是不会走索引的;

所以尽量不使用not

基础查询实现

要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;

java 复制代码
    @Test
    public void test06(){
        //要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //设置查询条件
        //TODO:条件之间默认使用and关键字
        //第一个参数是数据库字段名
        wrapper.like("user_name","伤")
                .eq("password","123456")
                .in("age",Arrays.asList(19,25,29))
                .orderByDesc("age");
        List<User> users = userMapper.selectList(wrapper);
        
    }

OR查询

查询用户名为"lisi"或者年龄大于23的用户信息;

java 复制代码
    @Test
    public void test07(){
        //"lisi"或者年龄大于23的用户信息;
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_name","lisi")
                .or()//使用or关键字关联条件,默认失and
                .gt("age",23);
        List<User> users = userMapper.selectList(wrapper);
    }

QueryWrapper模糊查询like

模糊查询常用方法

  • like("表列名","条件值"); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
  • likeLeft("表列名","条件值"); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
  • likeRight("表列名","条件值");作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%

左边模糊

java 复制代码
    @Test
    public void test08(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.likeLeft("user_name","二");
        List<User> users = userMapper.selectList(wrapper);
    }

QueryWrapper排序查询

核心方法

  • orderByAsc 升序排序,方法内可传入多个字段
  • orderByDesc 降序排序,方法内可传入多个字段

QueryWrapper限定字段查询

MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;

如果不写as realName就无法映射到realName成员变量

mp是直接使用select方法的值进行拼接,这样一来就不会识别到realName成员变量的@TableFileld注解

java 复制代码
    @Test
    public void test09(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .select("user_name as realName","email")
                .orderByDesc("age");
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

QueryWrapper实现分页条件查询

java 复制代码
    @Test
    public void test10(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ge("age",20)
                .orderByDesc("age");
        //构建分页对象
        IPage<User> page = new Page<>(2,2);
        userMapper.selectPage(page, wrapper);
        //获取当前页的对象
        List<User> users = page.getRecords();
        System.out.println(users);
        long pages = page.getPages();
    }

LambdaQueryWrapper查询

使用QueryWrapper开发存在的问题

  1. 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
  2. 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;

LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;

java 复制代码
    @Test
    public void test11(){
        //直接new一个对象
        //LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        //使用工具类生成一个对象
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
        //TODO:数据中的字段名通过类的成员变量名以及成员变量上的注解获取,解耦合
        wrapper.select(User::getRealName,User::getEmail)
                .like(User::getRealName,"伤")
                .eq(User::getPassword,"123456");
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

LambdaQueryWrapper实现条件删除和更新操作

java 复制代码
    @Test
    public void test12(){
        //使用wrapper实现条件删除
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getRealName, "hhh");
        int delete = userMapper.delete(wrapper);
        System.out.println(delete);
    }
java 复制代码
    @Test
    public void test13(){
        User user=User.builder()
                .realName("hhh")
                .build();
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getId, 1L);
        int update = userMapper.update(user, wrapper);
    }

或者直接使用lambdaUpdate的set方法直接进行赋值

java 复制代码
    @Test
    public void test14(){
        //直接使用lambdaUpdate
        LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
                .eq(User::getId, 1L)//更新条件
                .set(User::getRealName, "aaa")
                .set(User::getPassword, "1243");
        userMapper.update(null,wrapper);

    }

自定义接口

java 复制代码
/**
 * 编写接口继承BaseMapper接口,
 * 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 分页查询id大于id的用户集合
     * @param page 分页查询对象,分页拦截器会根据先根据这个分页查询对象来拦截sql语句进行分页查询
     *             然后将查询到的用户集合会封装到这个分页对象中
     * @param id id
     */
    IPage<User> findUserGtId(IPage<User>page, @Param("id") Long id);
}

xml文件写在哪呢?

直接在resources目录下创建mapper目录即可

因为mp默认的扫描xml文件是mapper目录下的所有文件

xml文件

html 复制代码
<?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.itheima.mapper.UserMapper">

    <select id="findUserGtId" resultType="com.itheima.pojo.User">
        select id,user_name AS realName,password,name,age,email from tb_user where id>#{id}
    </select>

</mapper>

测试

java 复制代码
    @Test
    public void test15(){
        IPage<User> page = new Page<>(2, 5);
        userMapper.findUserGtId(page,3L);
        //获取当前页的对象集合
        List<User> users = page.getRecords();
        System.out.println(users);
    }

可以看到分页查询拦截器会根据分页对象自动为sql语句添加limit关键字进行分页查询

MP实现Service封装

MybatisPlus为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类; 我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效;

实现流程

  1. 定义一个服务扩展接口,该接口继承公共接口IService;
  2. 定义一个服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口;

注意事项:

1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作 2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现

思想:共性的业务代码交给框架封装维护,非共性的业务,在接口UserService定义,然后在当前的服务类下实现;

快速使用

一.自定义一个接口继承IService接口

java 复制代码
/**
 * 自定义一个服务层接口继承公共服务接口,泛型是实体类对象(通过@TableName注解知道要操作的数据表)
 */
public interface UserService extends IService<User> {
    IPage<User>  getUserGtId(IPage<User>page,Long id);
}

二.自定义一个类继承ServiceImpl类和实现自定义的接口

/**

* 自定义一个服务层实现类,实现自定义的服务层接口

* 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法

* 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法

* 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了

* 注入的这个mapper成员变量叫baseMapper

*/

java 复制代码
/**
 * 自定义一个服务层实现类,实现自定义的服务层接口
 * 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法
 * 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法
 * 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了
 * 注入的这个mapper成员变量叫baseMapper
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    /**
     * 只需要实现UserService接口的方法
     */
    @Override
    public IPage<User> getUserGtId(IPage<User> page, Long id) {
        //直接使用ServiceImpl注入的mapper bean即可
        return baseMapper.findUserGtId(page,3L);
    }
}

MP封装Service实现CRUD操作

java 复制代码
   /**
     * @Description 测试条件查询,且仅返回一个
     * getOne:sql查询的结果必须为1条或者没有,否则报错 !!!!
     */
    @Test
    public void test2(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        User one = userService.getOne(wrapper);
        System.out.println(one);
    }

    /**
     * @Description 根据条件批量查询
     */
    @Test
    public void test3(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        List<User> list = userService.list(wrapper);
        System.out.println(list);
    }

    /**
     * @Description 根据条件批量查询并分页
     */
    @Test
    public void test4(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        //构建分页对象
        IPage<User> page=new Page<>(2,3);
        userService.page(page,wrapper);
        System.out.println(page.getRecords());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
    }

    /**
     * @Description 测试服务层save保存单条操作
     */
    @Test
    public void test5(){
        User user1 = User.builder().name("wangwu").userName("laowang4").
                email("444@163.com").age(20).password("333").build();
        boolean isSuccess = userService.save(user1);
        System.out.println(isSuccess?"保存成功":"保存失败");
    }

    /**
     * @Description 测试服务层批量保存
     */
    @Test
    public void test6(){
        User user2 = User.builder().name("wangwu2").userName("laowang2").
                email("444@163.com").age(20).password("333").build();
        User user3 = User.builder().name("wangwu3").userName("laowang3").
                email("444@163.com").age(20).password("333").build();
        boolean isSuccess = userService.saveBatch(Arrays.asList(user2, user3));
        System.out.println(isSuccess?"保存成功":"保存失败");
    }
    
    /**
     * @Description 根据id删除操作
     */
    @Test
    public void test7(){
        boolean isSuccess = userService.removeById(17l);
        System.out.println(isSuccess?"保存成功":"保存失败");
    }

    /**
     * @Description 根据条件批量删除
     */
    @Test
    public void test8(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getId,12)
                .gt(User::getAge,20);
        boolean remove = userService.remove(wrapper);
        System.out.println(remove);
    }

    /**
     * @Description 测试根据id更新数据
     */
    @Test
    public void test9(){
        //UPDATE tb_user SET password=?, t_name=? WHERE id=?
        User user2 = User.builder().name("wangwu2").password("333").id(3l).build();
        boolean success = userService.updateById(user2);
        System.out.println(success);
    }

    /**
     * @Description 测试根据条件批量更新
     */
    @Test
    public void test10(){
        LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(User.class);
        //UPDATE tb_user SET age=? WHERE (id IN (?,?,?))
        wrapper.in(User::getId,Arrays.asList(1l,3l,5l)).set(User::getAge,40);
        boolean update = userService.update(wrapper);
        System.out.println(userService);
    }
java 复制代码
    @Test
    public void test02(){
        User user1 = User.builder().name("wangwu2").realName("laowang2").
                email("444@163.com").age(20).password("333").build();
        
        userService.saveOrUpdate(user1);//没有设置主键id就是save(新增操作)
        
        User user2 = User.builder().id(user1.getId()).name("wangwu3").realName("laowang3").
                email("444@163.com").age(20).password("333").build();
        userService.saveOrUpdate(user2);//设置了主键id就是update(修改操作)
    }

使用MybatisX生成代码

逻辑删除

给表中新增加一个字段,0代表未删除,1代表以删除

这样一来删除的行依然存在于表中

添加逻辑删除字段

配置MP让其走逻辑删除

java 复制代码
#配置数据源
spring:
  datasource:
    username: root
    password: 1234
    url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted #指定逻辑删除的表字段
      logic-delete-value: 1 #指定成功删除后的值
      logic-not-delete-value: 0 #指定未被删除的值
    

使用@TableLogic注解指定逻辑删除的字段

java 复制代码
    /**
     * 逻辑删除的字段对于的成员变量
     */
    @TableLogic
    private Integer deleted;

测试

java 复制代码
  @Test
    public void test01(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }

查询时mp给sql语句自动添加 and deleted=0;确保该数据没有没删除

java 复制代码
@Test
    public void test03(){
        int i = userMapper.deleteById(1);
        System.out.println(i);

    }

删除时底层sql是修改deleted字段的值

乐观锁

乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;

悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;

乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了某些操作,然后让版本号改变如果当前线程发现版本号改变,则当前操作失败

乐观锁在数据库中有什么优势?

​ 避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;

​ 本质上就是在操作前,先获取操作行的version版本号,如果当前的版本号与指定的版本号不同则不进行其他操作,相同才进行操作,操作后就让版本号加一。

使用场景:

​ 1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;

​ 2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;

使用乐观锁

一.给数据库表添加一个乐观锁字段

二.在配置类中的插件bean添加乐观锁

java 复制代码
@Configuration
public class MybatisPlusConfig {
    /**
     * 用于批量注册mp的插件bean
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //构建批量注册插件的对象
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //构建分页插件对象,并指定mysql数据库
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        //配置每页的大小,-1为无上限
        paginationInnerInterceptor.setMaxLimit(-1L);
        //将分页插件注册
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);

        //配置乐观锁插件
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);

        return mybatisPlusInterceptor;
    }
}

三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)

java 复制代码
   /**
     * 乐观锁字段对应的成员变量
     */
    @Version
    private Integer version;

四.测试

java 复制代码
@Test
    public void test01(){
        //先做查询,获取当前对象的version值
        User dbUser = userMapper.selectById(2L);
        /*
        进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同
        如果没有进行修改,那么version值还是和dbUser对象的version相同
         */
        User user = User.builder()
                .id(dbUser.getId())//修改这个id的数据表数据
                .realName("abc")
                .version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一
                .build();
        userMapper.updateById(user);
    }

可以发现dbUser当前查询到的version值为3

进行更新操作时,根据dbUser的version值给entity对象的version属性进行赋值,如果当前数据表中id=2的version值还是3则说明表数据没有被修改 ,则entity对象的version值与数据库中的version值相同,就可以进行修改操作,且让version值加一(因为对表数据进行了修改)

如果进行更新的entity对象version属性没有进行赋值,就不会有 and version=? 和set version=?

java 复制代码
 @Test
    public void test01(){
        //先做查询,获取当前对象的version值
        User dbUser = userMapper.selectById(2L);
        /*
        进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同
        如果没有进行修改,那么version值还是和dbUser对象的version相同
         */
        User user = User.builder()
                .id(dbUser.getId())//修改这个id的行
                .realName("abc")
                //.version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一
                .build();
        userMapper.updateById(user);
    }

实现字段自动填充

先在我新增加了两个字段,给这两个字段填充值时需要频繁的写new Date()进行赋值,我们可以配置让其自动填充

一.添加成员变量

java 复制代码
/**
     * 插入时给这个成员变量赋值
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    /**
     * 插入或者更新时给这个成员变量赋值
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

二.配置拦截器实现MetaObjectHandler

java 复制代码
/**
 * 一个拦截器,在数据准备插入数据库时进行拦截
 */
@Component
public class FillHandler implements MetaObjectHandler {
    /**
     * 在进行插入数据时进行拦截
     * @param metaObject 封装了准备插入数据库的entity对象
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        //第一个参数时类的成员变量名
        metaObject.setValue("createTime",new Date());
        metaObject.setValue("updateTime",new Date());
    }

    /**
     * 在进行更新数据时进行拦截
     * @param metaObject 封装了准备更新数据库数据的entity对象
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime",new Date());
    }
}

三.测试

java 复制代码
 @Test
    public void test02(){
        User user=User.builder()
                .realName("hah")
                .email("153@qq.com")
                .age(18)
                .name("a")
                .password("123")
                .build();
        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

拦截到的数据,此时createTime和updateTime成员变量没有值

赋值后,再进行插入

相关推荐
飞滕人生TYF4 分钟前
java 排序 详解
java·算法·排序算法·方法
YaYicho25 分钟前
Mybatis入门
mybatis
Java小王子呀28 分钟前
java使用itext生成pdf
java·pdf
JWASX30 分钟前
定时/延时任务-Timer用法
java·定时器·timer
techdashen34 分钟前
Go与黑客(第四部分)
开发语言·后端·golang
宇宙大豹发39 分钟前
【Python】爬虫实战:高效爬取电影网站信息指南(涵盖了诸多学习内容)
开发语言·爬虫·python·学习·python爬虫·python代码·python使用
测试小工匠42 分钟前
移动端自动化环境搭建_Android
android·运维·自动化
蓝桉柒71 小时前
web前端开发--动画效果
开发语言·前端·css
大风吹PP凉1 小时前
45系统调用与内核API
java·linux·服务器
as_jopo1 小时前
-Dspring.profiles.active=dev与--spring.profiles.active=dev的区别
java·后端·spring