一、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(1) default 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里面如果需要使用乐观锁的步骤:
数据库的表必须添加version字段 添加乐观锁的拦截器 以后你执行update语句的时候记得实体类要设置当前version的值
五、Mybatis-Plus的Service封装
Mybatis-Plus为了开发更加快捷,对业务层(Service)也进行了封装,直接提供了相关的接口和实现类。我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效。
com.baomidou.mybatisplus.extension.service.IService
接口
该接口是一个泛型接口,里面提供了很多方法,包括基本的增删改查。
2. com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
实现IService接口中所有方法
注意接口实现类的实现方式,之后我们自己定义的接口实现类的使用也使用这种方式

测试用例
1)自定义业务层接口,继承IService:
public interface StudentService extends IService<Student> {
}
2)自定义业务层实现类,继承ServiceImpl:
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements 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(","));
}
}
步骤三:执行

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!
原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!




