mybatisPlus (入门)
MybatisPlus
(本文档只会写入一些个人认为需要记录的内容,不会记录所有内容)
1、mybatis实现简单CRUD
导入依赖
<!--springboot父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5+ 版本</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
配置yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/student?serverTimezone=GMT%2b8
username: root
password: 123456
server:
port: 8080
//日志实现
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
需要在springboot启动类中添加@MapperScan(Mapper包路径)
注:(此注解也可写在配置类中)
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Mapper继承BaseMapper
@Repository
public interface UserMapper extends BaseMapper<User> { //泛型类型为实体类类型
}
测试
@SpringBootTest
class Demo01ApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void testSelect() {
//由于继承了父类BaseMapper,所有方法都来自父类
List<User> users = userMapper.selectList(null);
for (User u: users) {
System.out.println(u);
}
}
@Test
void testInsert(){
User user = new User();
user.setName("wyz");
user.setAge(19);
user.setEmail("456@qq.com");
userMapper.insert(user); //返回的是受影响行数
System.out.println(user);
}
@Test
void testUpdate(){
User user = new User();
user.setId(6L);
user.setName("wang");
user.setEmail("789@.com");
userMapper.updateById(user); //虽然名字为updateById但实际上传入的参数是对象
System.out.println(user); //注意一点,这里的更新操作中所有要更新的字段都是动态配置的(动态sql),
// 你传入哪些,就修改哪些
}
@Test
void testDelete(){
User user = userMapper.selectById(1653234646309052418L);
userMapper.deleteById(user);
}
}
其他CRUD
查看官方文档https://www.baomidou.com/pages/49cc81/#mapper-crud-%E6%8E%A5%E5%8F%A3
2、CRUD扩展
看下面的代码:
@Test
void testInsert(){
User user = new User();
user.setName("wyz");
user.setAge(19);
user.setEmail("456@qq.com");
userMapper.insert(user);
System.out.println(user);
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PU9TwXsG-1683298115355)(C:\Users\31628\AppData\Roaming\Typora\typora-user-images\image-20230503132516717.png)]
思考问题:我们明明没有设置插入id。数据库也没有设置默认值,为什么id还是被插入了?
①主键策略
解答上述问题:因为mybatis存在默认主键策略为雪花算法。https://blog.youkuaiyun.com/jiaomubai/article/details/124385324
该算法为分布式id生成策略中的一种,其他方案可以看参考这篇博客了解:https://www.cnblogs.com/haoxinyue/p/5208136.html
mybatis集成了很多主键策略,使用@TableId注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
//要使用非自增生成id策略的话,数据库的自增必须关闭,否则不会生效
@TableId
private Long id;
private String name;
private int age;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime insertTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Version
private int version;
}
@TableId
@TableIdz有两个属性:value和type
注解源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.mybatisplus.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
String value() default "";
IdType type() default IdType.NONE;
}
**value用于解决数据库Id和实体类Id字段名称不一致的问题:**如果数据库中的主键字段和实体类中的主键属性字段名不一样且不是驼峰之类的对应关系,就可以在value属性中设置数据库中字段名。
若数据库中主键字段名为student_id,实体类中字段为id:
@Data
public class Student{
@Table(value = "student_id")
private int id
}
type用于设置主键策略,默认为IdType.NONE(雪花算法)。
type源码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.mybatisplus.annotation;
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 | 数据库自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | 手动赋值,如果不赋值,则Id为null |
ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | UUID ,String,使用时数据库字段类型需为varchar,且不能勾选字段自增 |
②自动填充(插入与更新时使用)
在插入和更新时,我们一般需要带有插入时间和更新时间等(甚至还有插入者,更新者等)。
实现该功能方法是有两种(省略创建字段步骤):
方式一、数据库级别(不推荐)
设置数据库中的字段默认值为CURRENT_TIMESTAMP
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ml6DnAQ4-1683298115357)(C:\Users\31628\AppData\Roaming\Typora\typora-user-images\image-20230503140153817.png)]
方式二、代码级别
老方法
每次插入\更新字段时使用Date date = new Date(),然后将其插入进去。
新方法
使用**@TableField**
@TableField
在此顺便讲解下@TableField注解的功能
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 数据库字段名(未开启驼峰命名法时使用) |
exist | boolean | 否 | true | 是否为数据库表字段(实例类中有而数据库中没有的字段) |
condition | String | 否 | “” | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s} ,参考(opens new window) |
update | String | 否 | “” | 字段 update set 部分注入(如果update方法中也设置了该字段的值,则会忽略update中设置的字段),例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (%会填充为本字段) |
insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNORED update table_a set column=#{columnProperty} |
whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
select | boolean | 否 | true | 是否进行 select 查询(该字段是否在查询时被查询出来) |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 类型 (该默认值不代表会按照该值生效) |
typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) |
numericScale | String | 否 | “” | 指定小数点后保留的位数 |
FieldStrategy属性(insertStrategy,updateStrategy,whereStrategy)
值 | 描述 |
---|---|
IGNORED | 忽略空值判断,实体对象的字段是什么事就用什么值更新,支持null值更新操作 |
NOT_NULL | 非 NULL 判断,字段不能为空 |
NOT_EMPTY | 非空判断(只对String类型字段,其他类型字段依然为非 NULL 判断)相当于name != null AND name != “” |
DEFAULT | 追随全局配置 |
NEVER | 不加入SQL,更新,不论字段是否有值,都不更新 |
FieldFill属性
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入时填充字段 |
UPDATE | 更新时填充字段 |
INSERT_UPDATE | 插入和更新时填充字段 |
实现自动填充,除了@TableField(fill = FieldFill.INSERT_UPDATE),还需要实现元对象处理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler
{
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill");
this.strictInsertFill(metaObject,"insertTime", LocalDateTime.class,LocalDateTime.now());
this.strictInsertFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now());
//在插入时,既填充插入时间,也插入更新时间
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill");
this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class, LocalDateTime.now());
}
}
注意事项:
填充原理是直接给entity属性设置值。
注解则是指定该属性在对应情况下必有值,如果无值则入库会是
null
填充处理器
MyMetaObjectHandler
在 Spring Boot 中需要声明@Component
或@Bean
注入关于其他相关信息参考官方文档https://www.baomidou.com/pages/4c6bcf/
③乐观锁
每次去拿数据的时候都认为别人不会修改,所以不会上锁,如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据,如果修改过,则重新读取,再次尝试更新,直至成功。
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
在mybatis中提供了@version设置乐观锁的注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
//要使用非自增生成id策略的话,数据库的自增必须关闭,否则不会生效
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
@TableField(update = "%s+1")
private int age;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime insertTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Version
private int version;
}
同时还需要配置拦截器(配置类)
@Configuration //替换xml配置文件
//必须扫描所有Mapper接口(也可以放到启动类中)
@MapperScan("com.example.demo01.Mapper")
@EnableTransactionManagement //开启事务
//@Component //为什么这里不能使用@Component:@Configuration本质上也是一个@Component,所以不能同时使用
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建拦截器实例
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//往拦截器中添加乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
}
}
④分页插件
mybatis继承了分页功能,我们无需再自己写limit
配置类中添加分页拦截器
@Configuration //替换xml配置文件
//必须扫描所有Mapper接口(也可以放到启动类中)
@MapperScan("com.example.demo01.Mapper")
@EnableTransactionManagement //开启事务
//@Component //为什么这里不能使用@Component:@Configuration本质上也是一个@Component,所以不能同时使用
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建拦截器实例
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//往拦截器中新增分页
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
测试,简单实现分页
@Test
void testSelectpage(){
Page<User> userPage = new Page<>(1,5); //这里是使用Page的构造器,也有其他构造器,建议看源码配合文档使用
userPage.setSize(4);
userMapper.selectPage(userPage,null);
userPage.getRecords().forEach(System.out::println); //语法糖
}
⑤逻辑删除
通常删除:直接从数据库中彻底删除、
逻辑删除:没有彻底删除,在用户看来删除了,但数据在数据库中依然存在。
通过deleted字段判断是否为逻辑删除
新增deleted字段
@TableLogic //逻辑删除
private int deleted;
配置yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
补充
该逻辑删除只对自动注入的sql起效
- 查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 删除: 转变为 更新
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
)- 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
⑥条件构造器
条件构造器提供了便捷的方式实现where中的各种条件。
AbstractWrapper
AbstractWrapper是QueryWrapper和UpdateWrapper的父类,用于生成sql的where条件,entity也用于生成sql的where条件
该类中有许多的方法可用于代替复杂的where语句,具体方法API参考官方文档https://www.baomidou.com/pages/10c804/#abstractwrapper
举一个栗子:
查询age不为空且名称带有w的数据。
@Test
void WrapperTest(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询age不为空且名称带有w的数据。
wrapper
.isNotNull("age")
.like("name","w"); //支持链式编程
userMapper.selectList(wrapper);
}
⑦代码生成器
代码生成器可以仅根据数据库表就为我们生成MVC,甚至可以帮我们设置mybatlsplus中的一些插件,扩展等,大幅度提升我们的编码效率。爽歪歪!
要想使用代码生成器,要遵循以下方法:
首先我们需要导包
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>最新版本</version>
</dependency>
要想使用代码生成器,需要有一个代码生成器类,可以我们自己编写。
public class CodeGenerator{
public void testCodeGenerator(){
FastAutoGenerator.create("url", "username", "password") //设置数据库信息,代码生成器通过这个数据库中的表
//生成代码
/**
* 全局配置
*/
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
/**
* 包配置
*/
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
/**
* 策略配置
*/
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
/**
* 模板配置
*/
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
nfig(builder -> {
builder.parent(“com.baomidou.mybatisplus.samples.generator”) // 设置父包名
.moduleName(“system”) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, “D://”)); // 设置mapperXml生成路径
})
/**
* 策略配置
*/
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
/**
* 模板配置
*/
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
上述只是部分的配置信息,要想知晓所有的配置信息,参阅官方文档:https://www.baomidou.com/pages/981406/