mybatisPlus笔记
MybatisPlus:
一些容易不知道是什么含义的方法:
MybaitsPlus中我们没有进行设置表名,MybaitsPlus是如何进行确定表名的?
MybatisPlus中默认是通过实体类(Mapper接口中继承BaseMapper接口中填写的泛型对应的实体类)的名字进行确定的。
实例:
@Repository //表示为持久层组件,和@component的作用相同
public interface UserMapper extends BaseMapper<User> {
//在这个Mapper接口中继承的BaseMapper接口中填写的泛型为User类型,所以在执行的时候,MybaitsPlus就会在配置好的Url地址中的数据库下去寻找叫做user的表名
}
当我们的表名和实体类的名字不相同的时候,MybatisPlus中的默认设置就不能满足我们的需求(不能找到对应实体类的表),我们可以在实体类上使用MybatisPlus中的@TableName进行指定表名。
实例:
@TableName("t_user") //mybatisPlus中默认是将实体类作为表名,可以通过@TableName进行执行这个实体类需要进行映射的表的名字(指定这个实体类对应的表的名字为t_user)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
如果之后我们所有的表名都是具有一个相同的特点(相同的前缀,例如都具有浅醉 t_ ),就可以在全局配置文件中进行设置MybatisPlus的全局配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus
username: root
password: 208262
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #进行指定日志用什进行输出
global-config: #mybatisPlus的全局配置
db-config:
table-prefix: t_ #在所有实体类所对应表的统一前缀为t_
在MybatisPlus中,默认是将实体类中的id属性名作为表的主键,一旦实体类中没有为属性名为id的属性,MybatisPlus在执行程序的时候就会报错(找不到主键),在这种时候,就需要通过@TableId注解将实体类中的一个属性作为该对象抽象成表中的主键(前提是在数据库表中含有id这个字段)
实例代码:
package com.littleshark.mybatisplusinidea.pojo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//@TableName("t_user") //mybatisPlus中默认是将实体类作为表名,可以通过@TableName进行执行这个实体类需要进行映射的表的名字
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@TableId //将User实体类中的id属性作为该对象在数据库表中的主键,当然,因为本生属性名就叫id,所以也可以不用设置
private Long id;
private String name;
private Integer age;
private String email;
}
当数据库表中没有id字段的时候,如何通过@TableId将实体类中的某一个属性和数据库中的主键进行绑定?
可以通过@TableId中的value进行指定
示例代码:
//@TableName("t_user") //mybatisPlus中默认是将实体类作为表名,可以通过@TableName进行执行这个实体类需要进行映射的表的名字
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@TableId(value = "uid") //将实体类中的id属性和表的uid主键进行绑定
private Long id;
private String name;
private Integer age;
private String email;
}
@TableId注解中的type属性:
该属性的作用使用来指定主键的生成策略(通过数据库中的自增还是通过mybatiisPlus中的雪花算法进行设置id属性)
这是可以作为type属性的值的枚举源码:
@Getter
public enum IdType {
/**
* 数据库ID自增
* <p>该类型请确保数据库设置了 ID自增 否则无效</p>
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型可以通过自己注册自动填充插件进行填充</p>
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 分配ID (主键类型为number或string),
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
示例代码:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@TableId(value = "uid",type = IdType.AUTO) //将实体类中的id属性和表的uid主键进行绑定,并将id设置为数据库中的自增(前提是数据库中的主键设置了自增)
private Long id;
private String name;
private Integer age;
private String email;
}
处理可以通过@TableId中的type进行设置主键的生成策略,还可以通过SpringBoot中的全局配置文件进行设置mybatisPlus中的全局配置进行设置主键的生成策略
实例代码:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus
username: root
password: 208262
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #进行指定日志用什进行输出
global-config: #mybatisPlus的全局配置
db-config:
table-prefix: t_ #在所有的表名前面添加 t_ 前缀
id-type: auto #将mybatisPlus的主键生成策略修改为数据库的自增(在全局配置中进行设置)。
MybatisPlus中的@TableField注解的作用:
在mybatisPlus中,会默认将驼峰命名方式和下划线进行对应,不需要我们在进行设置对应规则
将实体类中的非主键对应的属性进行和数据库表中的字段名进行绑定,使用方式,通过@TableField注解中的value属性进行指定进行映射的字段名
实例代码:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@TableId(value = "uid",type = IdType.AUTO) //将实体类中的id属性和表的uid主键进行绑定,并将id设置为数据库中的自增(前提是数据库中的主键设置了自增)
private Long id;
@TableField("user_name") //将数据库表中的user_name字段名和name属性进行绑定
private String name;
private Integer age;
private String email;
}
@TableLogic注解的作用:进行逻辑上的删除
需要我们自行添加一个字段用于作为是否删除的标志(0表示未删除,1表示已删除),如果使用了这个注解之后,在我们执行删除语句的时候,就会将符合条件的数据中的删除标志修改为1,在数据本身还是存在在表中的,所以叫做逻辑删除。
实例:
//@TableName("t_user") //mybatisPlus中默认是将实体类作为表名,可以通过@TableName进行执行这个实体类需要进行映射的表的名字
@NoArgsConstructor
@Data
public class User {
@TableId(value = "uid",type = IdType.AUTO) //将实体类中的id属性和表的uid主键进行绑定,并将id设置为数据库中的自增(前提是数据库中的主键设置了自增)
private Long id;
@TableField("user_name") //将数据库表中的user_name字段名和name属性进行绑定
private String name;
private Integer age;
private String email;
@TableLogic
@TableField("deleted") //将这个属性作为逻辑删除的标志
private Integer isDelete;
public User(Long id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
}
当我们在进行删除的时候,删除操作就会变成修改操作(将数据库表中的deleted字段中的值修改为1)。
实例:
@DisplayName("进行测试BaseMapper接口中进行删除数据的方法")
@org.junit.jupiter.api.Test
public void test01(){
User user = new User();
user.setId(1560097224753463299L);
int i = userMapper.deleteById(user); //进行删除用户
System.out.println("i-删除的行数为:->:"+i);
}
删除之后的结果:(逻辑上的删除,查询的时候会默认带上条件 where deleted=0)
MybatisPlus中的条件操作(wrapper):
结构:
说明:
- 通过条件进行查询:
实例代码:
@Autowired
private UserMapper userMapper;
@DisplayName("进行条件查询")
@Test
public void test01(){
//查询场景,名字中包含s,年龄在12到45之间的人
QueryWrapper<User> wrapper=new QueryWrapper<>();
//需要注意:在使用条件操作的时候,使用的是字段名进行筛选,而不是实体类的实行名进行筛选
//也就是说这里的column参数必须是数据库表中的字段名,不能是实体类中的属性名
wrapper.like("name",'s').between("age",12,45);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
//执行的Sql语句
//SELECT id,name,age,email,deleted AS isDelete FROM user WHERE deleted=0 AND (name LIKE ? AND age BETWEEN ? AND ?)
//参数:%s%(String), 12(Integer), 45(Integer)
}
- 通过QueryWrapper进行条件修改:
实例代码:
@DisplayName("进行条件修改")
@Test
public void test03(){
//将(年龄大于20并且名字中含有k)或者邮箱为null的用户信息进行修改
//第一种方式:通过QueryWrapper进行修改
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.gt("age",20).like("name",'k') //两个条件直接通过 . 进行连接,是使用的AND(and)
//gt()方法表示的是大于,lt()方法表示的是小于
.or().isNull("email"); //如果需要使用or,需要通过使用方法or() 在进行添加条件
User user=new User();
user.setName("小明");
int update = userMapper.update(user, wrapper);
System.out.println("修改的行数为:"+update);
//执行的Sql语句:UPDATE user SET name=? WHERE deleted=0 AND (age > ? AND name LIKE ? OR email IS NULL)
}
通过QueryWrapper进行设置条件优先级:
实例代码:
@DisplayName("进行测试优先等级")
@Test
public void test04(){
//场景:将名字中由 z 字母的,并且(age>20或者邮箱为空的)
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.like("name",'l').and(x -> x.gt("age",20).or().isNull("email"));
User user =new User();
user.setName("小红");
user.setEmail("xiaohong@qq.cpm");
int update = userMapper.update(user, wrapper);
System.out.println(user);
//执行的Sql语句
//UPDATE user SET name=?, email=? WHERE deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
//可以看出使用了lambda表达式的的条件会优先执行
}
使用Lambda表达式的条件会优先执行
通过QueryWrapper进行执行字段进行查询:
实例代码:
@DisplayName("进行指定字段查询")
@Test
public void test05(){
//查询name、age、email信息
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.select("name","age","email");
userMapper.selectMaps(wrapper).forEach(System.out::println);
//执行的Sql语句:SELECT name,age,email FROM user WHERE deleted=0
}
通过QueryWrapper进行子表查询(将第一次查询得到的结果作为第二次查询的条件):
实例代码:
@DisplayName("进行子查询,将第一次查询的结果作为第二次查询的条件")
@Test
public void test06(){
//举一个简单的例子:查询age>20的信息
QueryWrapper<User> wrapper=new QueryWrapper<>();
//inSql()方法中的两个参数:第一个参数为第二次查询的时候将当前字段和第一次查询的结果进行比较,第二个参数为:第一次执行的Sql语句
wrapper.inSql("age","select age from user where age>20");
userMapper.selectList(wrapper).forEach(System.out::println);
//执行的Sql语句:
//SELECT id,name,age,email,deleted AS isDelete FROM user
// WHERE deleted=0 AND (age IN (select age from user where age>20))
}
通过UpdateWrapper进行修改用户信息:
实例代码:
@DisplayName("通过UpdateWrapper,进行修改用户信息")
@Test
public void test07(){
//将age>20 并且(email为空,name中含有a)的用户的邮箱进行修改
UpdateWrapper<User> wrapper=new UpdateWrapper<>();
//进行添加需要修改的用户需要满足的条件
wrapper.gt("age",20).and(i->i.isNull("null").or().like("name",'a'));
//将满足条件的用户的信息进行修改(通过指定字段进行修改)
wrapper.set("email","just1t@qq.com");
//因为我们是使用的UpdateWrapper进行修改用户,是通过执行字段的方式进行修改,没有创建保存有修改信息的User对象
//所以,第一个参数为null
int update = userMapper.update(null, wrapper);
System.out.println(update);
//执行的Sql语句:UPDATE user SET email=? WHERE deleted=0 AND (age > ? AND (null IS NULL OR name LIKE ?))
}
通过Condition进行组装条件:
实例代码:
@DisplayName("通过Condition进行组装体条件")
@Test
public void test08(){
//通过前端传入的信息进行查询用户
QueryWrapper<User> wrapper=new QueryWrapper<>();
//模拟前端传入的信息
String username="";
String ageBegin="10";
String ageEnd="18";
//只有当前面的布尔表达式返回为true的时候,才会将其拼接到Sql语句中
wrapper.like(StringUtils.isNotBlank(username),"name",username)
.ge(ageBegin!=null,"age",ageBegin) //ge()方法表示大于等于 gt()方法表示大于
.le(ageEnd!=null,"age",ageEnd); //le()方法表示小于等于 lt()方法表示小于
userMapper.selectList(wrapper).forEach(System.out::println);
//执行的Sql语句:SELECT id,name,age,email,deleted AS isDelete FROM user WHERE deleted=0 AND (age > ? AND age < ?)
//为什么没有name字段,因为StringUtils.isNotBlank(username)这个布尔表达式返回为false
// mybatisPlus中的StringUtils中的isNotBlank()方法的作用是用来判断参数字符串是否为null或者”“ 空字符传
}
通过LamdbaQueryWrapper类进行查询用户(不需要填写字段名,只需要填写该表中字段对应的实体类中哪一个属性名):
实例代码:
@DisplayName("通过LambdaQueryWrapper进行查询信息")
@Test
public void test09(){
//模拟前端传入的信息
String username="a";
String ageBegin="";
String ageEnd="18";
LambdaQueryWrapper<User> lambdaQueryWrapper=new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
.ge(StringUtils.isNotBlank(ageBegin),User::getAge,ageBegin)
.le(StringUtils.isNotBlank(ageEnd),User::getAge,ageEnd);
userMapper.selectList(lambdaQueryWrapper).forEach(System.out::println);
//执行的Sql语句:SELECT id,name,age,email,deleted AS isDelete FROM user WHERE deleted=0 AND (name LIKE ? AND age <= ?)
//LambdaWrapper的特点就是使用的是实体类中的属性名,不是使用的表中的字段名,是通过实体类中的属性名找到对应的字段名,再将字段名
//添加到Sql语句中
}
只要是LambdaWrapper,就可以通过实体类中的属性名进行设置或者查询该实体类的属性对应的表中的字段名的值。(包括LambdaUpdateWrapper,LambdaQueryWrapper)
通过MybatisPlus中的分页插件实现分页功能:
需要先进行配置分页插件:
//进行配置MybatisPlus中的分页插件
@Configuration
@MapperScan("com/littleshark/mybatisplusinidea/mapper") //用于扫描mapper所在的mapper接口
public class MybatisPlusPagePlugins {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//是通过创建拦截器进行实现分页功能
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
//指定类型为MySql数据库
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试的代码:
@Test
public void test01(){
Page<User> page=new Page<>();
page.setCurrent(2); //当前页数
page.setSize(3); //每一页显示多少条数据
userMapper.selectPage(page,null); //返回的结果就是传入参数的page对象
page.getRecords().forEach(System.out::println);
//执行的Sql语句:SELECT id,name,age,email,deleted AS isDelete FROM user WHERE deleted=0 LIMIT ?,?
}
如何将自己查询的信息进行实现分页操作:
实例:
@Repository //表示为持久层组件,和@component的作用相同
public interface UserMapper extends BaseMapper<User> {
//如果需要将自定义的查询结果进行分页,第一个参数必须是Page对象
//@Param注解的作用是将参数进行封装的Map集合中的key进行修改为@Param注解中的value值
Page<User> MyPageQuery(@Param("page") Page<User> page,@Param("age") Integer age);
}
<?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.littleshark.mybatisplusinidea.mapper.UserMapper">
<!--Page<User> MyPageQuery(@Param("page") Page<User> page,@Param("age") Integer age);-->
<!-- 通过# 进行获取参数,不需要注意双引号问题,如果使用 $ 的方式,需要考虑双引号问题-->
<select id="MyPageQuery" resultType="user">
select id,name,age,email from user where age > #{age}
</select>
</mapper>
测试的方法:
@Test
public void test02(){
Page<User> page=new Page<>();
page.setCurrent(2); //当前页数
page.setSize(3); //每一页显示多少条数据
userMapper.MyPageQuery(page,12);
page.getRecords().forEach(System.out::println);
//执行的Sql语句:select id,name,age,email from user where age > ? LIMIT ?,?
}
MyBatisPlus中的乐观锁插件:
案例:有小明,小红两人,老板仙然小明将商品的价格提升50,但因为小明有事情没有及时修改,后老板又叫小红将价格的价格下降30元,在小红进行修改的同时,小明也刚好将自己的事情办完了,也同时进行修改,如果使用锁的话,想要的结果就无法实现
使用悲观锁:在小明进行操作的时候,小红就会无限的阻塞,直到小明完成操作之后才能进行操作。
使用乐观锁:在表的字段中多添加一个version乐观锁版本号,在进行修改的时候,只有在版本号和之前获取到的版本号相同的时候才能修改成功。
代码演示:
@DisplayName("进行演示乐观锁")
@Test
public void test(){
Product productByMing = productMapper.selectById(1);
System.out.println("小明获取到的商品价格为:"+productByMing.getPrice());
Product productByHong = productMapper.selectById(1);
System.out.println("小红获取到的商品价格为:"+productByHong.getPrice());
productByMing.setPrice(productByMing.getPrice()+50);
productMapper.updateById(productByMing);
productByHong.setPrice(productByHong.getPrice()-30);
productMapper.updateById(productByHong);
Product productByBoss = productMapper.selectById(1);
System.out.println("老板获取到的商品价格为:"+productByBoss.getPrice());
//不添加锁后的结果:老板获取到的商品价格为:70(决定原因在于代码执行的先后顺序)
//如果配置了乐观锁插件之后:老板获取到的商品价格为:150 (小红的修改没有生效)
//小明执行修改的Sql语句:UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
//小红执行修改的Sql语句;UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
//为什么小红执行会失败:因为在获取的时候,小明和小红获取到的信心是相同的,但在小明进行修改之后,version字段中的值就会增加(改变)
//在小红进行修改的时候,由于之前获取到版本号和在修改的时候指定的版本号不相同(将之前获取到的版本号信息作为条件),所以就会失败
}
配置MyBatisPlus的乐观锁插件:
//进行配置MybatisPlus中的分页插件
@Configuration
@MapperScan("com/littleshark/mybatisplusinidea/mapper") //用于扫描mapper所在的mapper接口
public class MybatisPlusPagePlugins {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//是通过创建拦截器进行实现分页功能
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
//指定类型为MySql数据库
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//将乐观锁插件进行配置
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
通用枚举:
需要实现两个步骤:
- 使用@EnumValue注解将枚举类型中需要存入数据库的属性进行标注
- 在SpringBoot全局配置文件中进行配置枚举的扫描位置
实例代码:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus
username: root
password: 208262
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #进行指定日志用什进行输出
global-config: #mybatisPlus的全局配置
db-config:
#table-prefix: t_ #在所有的表名前面添加 t_ 前缀
id-type: auto #将mybatisPlus的主键生成策略修改为数据库的自增(在全局配置中进行设置)。
# 将指定包中的类进行取别名,默认别名为类名 不区分大小写
type-aliases-package: com/littleshark/mybatisplusinidea/pojo
# 将指定包进行枚举扫描
type-enums-package: com/littleshark/mybatisplusinidea/userEnum
枚举类型:
package com.littleshark.mybatisplusinidea.userEnum;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum SexEnum {
MALE(1,"男"),FEMALE(2,"女");
@EnumValue //如果将这个枚举类型添加到数据库的时候,将该属性值进行添加(将该属性值作为该枚举的代表)
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
测试代码:
@DisplayName("将枚举类型进行添加到数据库中")
@Test
public void test(){
User user = new User();
user.setName("小明");
user.setSex(SexEnum.MALE);
userMapper.insert(user);
System.out.println("添加到数据库中的用户为:"+user);
//执行的Sql语句和参数:
//==> Preparing: INSERT INTO user ( name, sex ) VALUES ( ?, ? )
//==> Parameters: 小明(String), 1(Integer)
//<== Updates: 1
}
MyBatisPlus中自动生成代码:
依赖:
<!--以下为MyBatisPlus的代码生成所需要的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
生成的代码位置实例:
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus", "root", "208262").globalConfig(builder -> {
builder.author("just1t") // 设置作者
// .enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D:\\MybatisPlusInIDEA\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.littleshark") // 设置父包名
.moduleName("mybatisplusinidea") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D:\\MybatisPlusInIDEA\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker 引擎模板,默认的是Velocity引擎模板
.execute();
}
}