暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot-7-MyBatis-Plus进阶篇:自定义扩展插件实战

560

一、DQL时出现的问题和一些解决方式

1. 字段映射与表名映射

思考:表的字段和实体类的属性不对应,查询会怎么样?

1.1 问题一:表字段与编码属性设计不同步

alter table tb_student change name username varchar(30);

出现结果:

解决方法1:

  • 在模型类属性上方,使用 @TableField属性注解,通过value属性,设置当前属性对应的数据库表中的字段关系

解决方法2:起别名

select id ,username as name ,age,email from tb_student;

代码演示:

QueryWrapper<Student> qw = new QueryWrapper<>();
qw.select("username as name","count(*) as nums");
qw.groupBy("name");
List<Map<String, Object>> maps = studentMapper.selectMaps(qw);
System.out.println(maps);

1.2 问题二:编码中添加了数据库中未定义的属性

Student
类上添加address
属性

使用测试方法

/**
 * 根据ID查询
 */

@Test
void testGetById() {
    Student student = studentMapper.selectById(2L);
    System.out.println(student);
}

出现结果:

解决方法:

  • 在模型类属性上方,使用@TableField注解,通过exist=属性,设置属性在数据库表字段中是否存在,默认为true此属性无法与value合并使用

1.3 问题三:某些字段和属性是否参与查询

需求:name这个字段不对用户显示值(比如:用户查询时实体类中有密码,我们可以用在查询时不显示密码值)解决方法:

  • 在模型类属性上方,使用@TableField**注解,通过select属性:设置该属性是否参与查询。此属性与select()映射配置不冲突

测试结果

1.4 问题四:表名与编码开发设计不同步

解决方法1:

  • 在模型类上方,使用@TableName注解,通过value属性,设置当前类对应的数据库表名称

解决方法2:步骤一: 将刚才类注解@TableName,注释掉

步骤二:yml配置表名忽略前缀

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启mp输出到控制台
  global-config:
    db-config:
      table-prefix: tb_ # 设置表名前缀

思考:这两种使用哪些场景?如果大部分表都有相同前缀,则可以使用方法2解决,否则使用方法一Student类完整代码:

@Data
//@TableName("tb_student")
public class Student {

    @TableId(value = "ID",type = IdType.AUTO)
    private Long id;
    @TableField(value = "username",select = false)
    private String name;
    private Integer age;
    private String email;
    @TableField(exist = false)
    private String address;
}

二、Mybatis-Plus扩展

1. id生成策略控制(Insert)

主键生成的策略有哪几种方式?

不同的表应用不同的id生成策略

  • 日志:自增(1,2,3,4,……)
  • 购物订单:特殊规则(FQ23948AK3843)
  • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
  • 关系表:可省略id
  • ……

1.1 id生成策略控制(@TableId注解)

  • 名称:@TableId

  • 类型:属性注解

  • 位置:模型类中用于表示主键的属性定义上方

  • 作用:设置当前类中主键属性的生成策略

  • 相关属性

    type:设置主键属性的生成策略,值参照IdType枚举值

Studet修改

@Data
//@TableName("tb_student")
public class Student {

    @TableId(value = "ID",type = IdType.ASSIGN_ID)
    private Long id;
    @TableField(value = "username",select = false)
    private String name;
    private Integer age;
    private String email;
    @TableField(exist = false)
    private String address;
}

测试结果:

思考:表明前缀可以通过配置(可以理解为是全局配置),用来省略注解方式,那么id-type可以进行全局配置?答案是可以

mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id

2. 多记录操作(批量Delete/Select)

思考:MyBatisPlus是否支持批量操作?

2.1 按照主键删除多条记录

//删除指定多条数据
@Test
public  void testdeleteBatchIds(){
    List<Long> list = new ArrayList<>();
    list.add(6L);
    list.add(7L);
    studentMapper.deleteBatchIds(list);
}

测试结果:

2.2 根据主键查询多条记录

//查询指定多条数据
@Test
public  void testselectBatchIds(){
    List<Long> list = new ArrayList<>();
    list.add(6L);
    list.add(7L);
    studentMapper.selectBatchIds(list);
}

测试结果:

3. 逻辑删除(Delete/Update)

思考

在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?

  • 删除操作业务问题:业务数据从数据库中丢弃

  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中

3.1 逻辑删除案例

步骤一:数据库表中添加逻辑删除标记字段

alter table  tb_student add column status tinyint(1default 0 comment '0表示启用 1表示禁用';

步骤二:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段

@TableLogic包含以下属性

  • value:未删除时的值
  • delval:删除了的值单个类配置逻辑删除
@Data
//@TableName("tb_student")
public class Student {

    @TableId(value = "ID",type = IdType.ASSIGN_ID)
    private Long id;
    @TableField(value = "username",select = false)
    private String name;
    private Integer age;
    private String email;
    @TableField(exist = false)
    private String address;
    //逻辑删除字段,标记当前记录是否被删除
    @TableLogic(value = "0",delval = "1")
    private Integer status;
}

全部实体类配置逻辑删除

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true  #开启驼峰命名
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启mp输出到控制台
  global-config:
    db-config:
      table-prefix: tb_ # 设置表名前缀
      id-type: assign_id
      logic-delete-field: status # 逻辑删除的字段
      logic-not-delete-value: 0 # 逻辑删除字面值:0表示未删除
      logic-delete-value: 1  #逻辑删除字面值:1表示删除

逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。

4. 乐观锁(Update)

4.1 什么是悲观锁和乐观锁

悲观锁(Pessimistic Lock)

当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观锁。

之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。

乐观锁(Optimistic Locking)

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。

4.2 Mybatis-Plus乐观锁案例

步骤一:数据库表中添加锁标记字段

ALTER TABLE tb_student ADD COLUMN `version` INT DEFAULT 0;

步骤二:实体类中添加对应version字段,并设定当前字段为版本控制字段

@Version注解

@Data
//@TableName("tb_student")
public class Student {

    @TableId(value = "ID",type = IdType.ASSIGN_ID)
    private Long id;
    ...
    ...
    /**
     * 实现乐观锁
     */

    @Version
    private Integer version;
}

步骤三:配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装

package com.zbbmeta.config;

/**
 * @author springboot葵花宝典
 * @description: TODO
 */

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //3.配置乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

步骤四:使用乐观锁机制在修改前必须先获取到对应数据的version方可正常进行

@Test
public void testUpdateOne() {
    //1.先通过要修改的数据id将当前数据查询出来
    Student student = studentMapper.selectById(2L);
    //2.将要修改的属性逐一设置进去
    student.setName("李四四");
    studentMapper.updateById(student);
}

模拟多条记录同时更新

@Test
public void testUpdateTwo() {
    //先通过要修改的数据id将当前数据查询出来
    Student student1 = studentMapper.selectById(2L);     //version=2
    Student student2 = studentMapper.selectById(2L);    //version=2
    student1.setName("李四四2");
    studentMapper.updateById(student1);              //version=>3
    student2.setName("李四四2");
    studentMapper.updateById(student2);               //verion=2 更新失败
}

第二条记录更新失败

小结

在mp里面如果需要使用乐观锁的步骤:

  1. 数据库的表必须添加version字段
  2. 添加乐观锁的拦截器
  3. 以后你执行update语句的时候记得实体类要设置当前version的值

五、Mybatis-Plus的Service封装

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

  1. com.baomidou.mybatisplus.extension.service.IService
    接口

该接口是一个泛型接口,里面提供了很多方法,包括基本的增删改查。2.  com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
实现IService接口中所有方法

注意接口实现类的实现方式,之后我们自己定义的接口实现类的使用也使用这种方式

  1. 测试用例

1)自定义业务层接口,继承IService:

public interface StudentService extends IService<Student{
}

2)自定义业务层实现类,继承ServiceImpl:

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapperStudentimplements StudentService {
}


3)测试类:

package com.zbbmeta.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zbbmeta.entity.Student;
import com.zbbmeta.service.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author 周棒棒
 * @ClassName springboot葵花宝典
 * @description: TODO
 * @date 2023年08月17日
 * @version: 1.0
 */

@SpringBootTest
public class StudentServiceTest {

    @Autowired
    private StudentService studentService;
    /**
     * 根据ID查询
     */

    @Test
    void testGetById() {
        Student student = studentService.getById(2L);
        System.out.println(student);
    }

    /**
     * 新增
     */

    @Test
    void testSave() {
        Student student = new Student();
        student.setName("周久良3");
        student.setAge(99);
        student.setEmail("zhoujiuliang3@zbbmeta.cn");
        studentService.save(student);
    }
    /**
     * 根据ID删除
     */

    @Test
    void testDelete() {
        studentService.removeById(1);
    }
    /**
     * 更新数据
     */

    @Test
    void testUpdate() {
        Student student = new Student();
        student.setId(1L);
        student.setName("张三三");
        student.setAge(99);
        studentService.updateById(student);
    }
    //分页查询
    @Test
    void testSelectPage(){
        //1 创建IPage分页对象,设置分页参数
        IPage<Student> page=new Page<>(1,3);
        //2 执行分页查询
        studentService.page(page,null);
        //3 获取分页结果
        System.out.println("当前页码值:"+page.getCurrent());
        System.out.println("每页显示数:"+page.getSize());
        System.out.println("总页数:"+page.getPages());
        System.out.println("总条数:"+page.getTotal());
        System.out.println("当前页数据:"+page.getRecords());
    }
}

六、快速开发-代码生成器

步骤一:导入依赖


<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>
<!--        freemarker-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>

注意:生成代码的时候有不同的模板,本次演示使用的freemarker模板所以要添加模板依赖

步骤二:代码生成类

package com.zbbmeta;

/**
 * @author springboot葵花宝典
 * @description: TODO
 */

public class CodeGenerator {

    public static void main(String[] args) {

        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mp?serverTimezone=UTC",
                "root",
                "root")
                // 全局配置
                .globalConfig((scanner, builder) -> builder
                        .author(scanner.apply("请输入作者名称?"))
                        .fileOverride()
                        .outputDir(scanner.apply("请输入指定目录"))
                )
                // 包配置
                .packageConfig((scanner, builder) ->{
                            builder.parent(scanner.apply("请输入包名?"))
//                                    .pathInfo(Collections.singletonMap(OutputFile.xml, scanner.apply("请输入mapperxml生成路径?")))
                            ;
                        }

                )
                // 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                        .addTablePrefix(scanner.apply("请输入过滤表前缀?"))
                        .controllerBuilder().enableRestStyle().enableHyphenStyle()
/*                        .entityBuilder().enableLombok().addTableFills(
                                new Column("create_time", FieldFill.INSERT)
                        )*/

                        .build())
                /*
                    模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                   .templateEngine(new BeetlTemplateEngine())
                   .templateEngine(new FreemarkerTemplateEngine())
                 */

                .templateEngine(new FreemarkerTemplateEngine())
                .execute();

    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
}

步骤三:执行

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!


文章转载自springboot葵花宝典,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论