1、Lombok配置
1.1、准备工作
准备一个数据库
数据库命名规范
- 数据库字段命名下划线形式,例如:student_id
- 数据库名字和表的名字采用下划线的形式命名
1.2、创建实体类——lombok
什么是Lombok?
- Lombok是一种简化创建实体类的插件
- 使用Lombok会简化代码,提高代码的可读性
Lombok该如何简化代码?
- 答案当然是注解的形式
注解的含义都是什么?
@Setter
添加Set方法
@Getter
添加Get方法
@ToString
添加ToString方法
@Data
@ToString
@EqualsAndHashCode
@Getter
@Setter
@RequiredArgsConstructor
的缩写
@AllArgsConstructor
添加有参构造方法
@NoArgsConstructor
添加无参构造方法
@TableName("tax_user")
映射数据库表名,绑定数据库表
TableLogic
标志该字段是逻辑删除字段
- value属性:这个属性是设置未删除的标志
- delval属性:这个属性是设置删除后的标志
@TableId
映射数据库表字段,绑定主键id
type
属性绑定主键策略
@TableField
字段映射,如果数据库和实体类的字段出现命名不一样的情况,使用此注解,将字段绑定在一起
如果出现实体类中的字段而数据库库表中没有这个字段情况下,使用此注解,
exist
默认为true
密码字段在查询的时候不想被查询出来,就可以设置
select
属性为falsepackage com.demone.entity.po; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor @TableName("tax_user") public class User { @TableId private Long userId; private String userName; private String userPhone; @TableField(value = "user_pwd" ) private String userPassword; private Integer userVipLevel; private Integer userBalance; private Integer parentId; private Date createTime; @TableField(exist = false) private Integer online; }
Lombok注意事项:
- 实体类字段需要使用驼峰命名,命名一定要规范,要不mybatis-plus识别不出来
- myabtis-plus自动开启下划线转驼峰命名
Lombok该怎么使用?
1.2.1、引入Lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
1.2.2、实体类配置
注意:
- 实体类字段需要使用驼峰命名,命名一定要规范,要不
mybatis-plus
识别不出来myabtis-plus
默认自动开启下划线转驼峰命名- 添加上该实体类要绑定的数据库表名,只有绑定完表名之后,
mybatis-plus
才能识别出来操作的对象- 如果要通过
id
删除数据,需要告诉mybatis-plus
id
字段,添加@TableId
注解
package com.demone.entity.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tax_user")
public class User implements Serializable {
@TableId
private Long userId;
private String userName;
private String userPhone;
@TableField(value = "user_pwd")
private String userPassword;
private Integer userVipLevel;
private Integer userBalance;
private Integer parentId;
private Date createTime;
//数据库不存在这个字段,开启注解让mybatis-plus识别到数据库不存在这个字段,但是类中要使用
@TableField(exist = false)
private Integer online;
}
2、Mybatis-PLus配置
2.1、引入mybatis-plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
2.2、引入MySQL驱动依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.3、引入连接池依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
2.4、数据源配置
注意:
- 实体类的位置一定要配对位置,初次使用踩大坑
#数据源配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/cloud?userUnicode=true&characterEncoding=utf-8
username: root
password: root
#myabtis-plus配置
mybatis-plus:
#实体类位置
type-aliases-package: study.springCloud.pojo
#mapper.xml位置
mapper-locations: calsspath:study/springCloud/mapper/xml/*.xml
#日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.5、创建mapper层
继承
BaseMapper<User>
,泛型给实体类这样
mapper
层就好了,接下来就可以使用mybatis-plus
了
package com.demone.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demone.entity.User;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
3、CRUD操作
3.1、增加操作
调用方法:
inser()
参数:
entity
// 插入操作
@Test
void insert() {
User user = new User();
user.setUserName("Dem_one");
user.setUserPhone("123123");
user.setUserPwd("00000");
userMapper.insert(user);
}
3.2、删除操作
3.2.1、通过id删除数据
调用方法:
deleteById()
参数:
id
注意:需要在实体类给出那个是id,在id字段上边添加@TableId注解,mybatis-Plus便可以识别出来
@Test
void deleteById() {
userMapper.deleteById(19L);
}
3.2.2、批量删除
调用方法:
deleteBatchIds()
参数:集合
说明:按照id批量删除
@Test
void selectById5() {
ArrayList<String> list = new ArrayList<>();
list.add("3c7976d5dac2aee011d90f5cf5009c99");
int i = userMapper.deleteBatchIds(list);
System.out.println(i);
}
3.3、更新操作
3.3.1、通过id更新数据
调用方法:
deleteById()
参数:
id
注意:需要在实体类给出那个是
id
,在id
字段上边添加@TableId
注解,mybatis-Plus
便可以识别出来如果不给出需要更新的
id
,mybaitis-plus
仍会执行更新操作,程序依旧正常执行不报错,但是不会更新成功
@Test
void updateById() {
User user = new User();
user.setUserId(18L);
user.setUserName("XXX");
userMapper.updateById(user);
}
3.4、查询操作
3.4.1、通过id查询数据
调用方法:
selectById()
参数:
id
@Test
void selectById() {
userMapper.selectById(18L);
}
3.4.2、查询全部数据
调用方法:
selectList()
参数:
wrapper
说明:这里直接给了空等价于给了一个空wrapper
@Test
void selectALl() {
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
3.4.3、分页查询
注意:
要想让分页生效,需要先配置拦截器——PaginationInnerInterceptor()
3.4.3.1、分页拦截器
package com.demone.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1.先定义一个MybatisPlus的拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
3.4.3.2、执行分页查询
调用方法:
selectPage()
参数:
page
、wrapper
说明:
page中给的两个参数,一个是当前页
current
,一个是分页的大小size
执行的SQL依旧是转换为LIMIT
@Test
void selectPage() {
IPage<User> page = new Page<>(1,2);
QueryWrapper<User> wrapper = new QueryWrapper<>();
userMapper.selectPage(page,wrapper);
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());
}
3.4.4、条件查询
3.4.4.1、一般形式的条件查询
调用方法:
selectList()
参数:
wrapper
说明:
wrapper
中的第一个参数是要匹配的列名,第二个参数是要匹配的值查询使用的
wrapper
是QueryWrapper
,wrapper
中放入的条件
@Test
void selectByCondition() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_phone",123456);
userMapper.selectList(wrapper);
}
3.4.4.2、lambda格式的条件查询(复杂)
调用方法:
selectList()
参数:
wrapper
好处:减少由于字段写错而带来不必要的bug
说明:
- 在
wrapper
中添加条件之前添加lambda()
- 添加条件的格式发生变化
eq(User::getUserId, 1)
- 前边是要查询的实体类 : : 查询条件的字段,查询条件的值
//lambda格式的条件查询
@Test
void selectById() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(User::getUserId, 1)
userMapper.selectList(wrapper);
}
3.4.4.3、lambda格式的条件查询(简化)
调用方法:
selectList()
参数:
wrapper
好处:可以使用链式查询,简化代码数量,减少由于字段写错而带来不必要的bug
说明:
- 支持链式编程,追加条件
or()
表示或者的意思,如果不加直接连起来表示并且的意思
@Test
void selectByCondition3() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.lt(User::getUserBalance,1000000000)
.gt(User::getUserBalance,0)
userMapper.selectList(wrapper);
}
3.4.4.4、空值处理
调用方法:
selectList()
参数:
wrapper
好处:空值情况下,程序健壮性良好,代码简洁
说明:
- 第一个参数是要判断的条件,成立则拼接条件,反之不拼接条件
- 第二个参数是要要匹配的列名
- 第三个参数是要匹配的值
-
先去创建一个实体类
这个实体类需要做出来一些变化,在你判断条件的字段复制一份
目的:
- 在输出条件范围时只输入了一个上限或者一个下限也能查出来对应的数据
package com.demone.entity.vo;
import com.demone.entity.po.User;
import lombok.Data;
@Data
public class UserVO extends User {
//这里查询的时vipLevel这个字段,复制一份字段
private Integer userVipLevel2;
}
- 模拟传入两个值
//空值条件查询
@Test
void selectByConditionIsNull() {
UserVO userVO = new UserVO();
//模拟传入两个值
userVO.setUserVipLevel(2);
userVO.setUserVipLevel2(5);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//如果为空就不拼接条件,不为空就拼接条件
wrapper.gt(null != userVO.getUserVipLevel(), User::getUserVipLevel, userVO.getUserVipLevel())
.lt(null!=userVO.getUserVipLevel2(),User::getUserVipLevel,userVO.getUserVipLevel2());
userMapper.selectList(wrapper);
}
3.4.4.5、le
,ge
,lt
,gt
,eq
——范围查询
名词解释:
le
:小于等于
lt
:小于
ge
:大于等于
gt
:大于
eq
:等于
3.4.4.6、between
名词解释:
between
:大于等于A,小于等于B
//范围查询
@Test
void selectById4() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("user_balance",100000000,500000000);
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
for (Map<String, Object> map : maps) {
System.out.println(map);
}
}
3.4.4.7、like
——模糊查询
名词解释:
like
:%J%,查询包含J
的数据likeRight
:J%,查询以J
开头的数据likeLeft
:%J,查询以J
结尾的数据notLike
:NOT LIKE %J%,查询不包含的J
的数据
3.4.5、查询投影
什么是查询投影呢?
- 查询数据有多少个字段就是查询投影
3.5.1、使用lambda表达式
为什么要使用查询投影呢?
- 因为我们不想用户的隐私信息被查出来,如密码
如何实现查询投影呢?
- 使用wrapper中的
select()
,参数用lambda表示,参数就是要显示的字段- 这样其他字段就算被查出来,但是他的值显示为null
局限:
- 使用lambda表达式只能列出来实体类内部的投影
- 不能使用数据库的函数
//查询投影
@Test
void selectById() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//需要哪些字段就给出那些字段
wrapper.select(User::getUserName,User::getUserPhone);
List<User> users = userMapper.selectList(wrapper);
}
3.5.2、普通投影
3.5.2.1、投影
注意:
- 使用投影时,需要将字段名和数据库字段名一致,不一致会报错
//普通投影
@Test
void selectById1() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("user_name","user_phone");
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
3.5.2.2、分组并统计所有数据
调用方法:
selectMaps()
参数:
wrapper
说明:
- 此方法能根据投影计算出总数
- 可以分组计算总数
- 并不是支持所有函数
//统计
@Test
void selectById3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("count(*) as count","user_phone")
.groupBy("user_phone");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
for (Map<String, Object> map : maps) {
System.out.println(map);
}
}
4、主键策略
主键策略:
ASSIGN_UUID
:UUID 主键类型String
AUTO
:自增 主键类型int
ASSIGN_ID
:雪花算法 主键类型 bigint 或者String
全局配置主键策略
配置全局主键生成策略之后,每个实体类就不需要在配置主键策略了,采用yaml中的主键生成策略
mybatis-plus:
global-config:
db-config:
id-type: auto
批量查询
调用方法:
selectBatchIds()
参数:集合
说明:依照id批量进行查询
@Test
void selectById6() {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("13");
list.add("14");
list.add("15");
list.add("16");
List<User> users = userMapper.selectBatchIds(list);
users.forEach(System.out::println);
}
5、逻辑删除
逻辑删除是什么?
- 通过设置一个字段标志数据库中数据是否被删除
- 用来解决物理删除数据
- 实际上执行的是更新操作
注意:
- 逻辑删除执行的是更新操作而不是执行删除操作
- 配置逻辑删除之后,查询操作会加上条件
5.1、逻辑删除实现
-
数据库中添加一个字段,作为是否删除的标志
-
实体类中添加字段并开启注解
@TabLogic
value
:数据未删除标志
delval
:数据删除标志
5.2、通过yaml
来全局配置逻辑删除
mybatis-plus:
global-config:
db-config:
#做逻辑删除的字段
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
6、乐观锁
什么是乐观锁?
- 乐观锁就是用来控制并发的一个方式,适用于秒杀
- 乐观锁的并发量适用于2000左右
乐观锁的实现原理
- 乐观锁是一般用int数据类型进行存储
- 每次操作一次数据version(乐观锁字段)加一
- 这样如果有多个人同时操作同一条数据时,就能保证只有一个人能操作成功
6.1、配置乐观锁
-
数据库添加字段
乐观锁字段一般用int数据类型进行存储
-
实体类添加字段并添加
@Version
注解
-
添加一个乐观锁拦截器
//添加乐观锁的拦截器 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
-
乐观锁测试
@Test void test() { User user = new User(); user.setUserName("Dem_one2"); user.setUserId("16"); userMapper.updateById(user); }
注意:
-
当你不提供乐观锁字段时,操作数据乐观锁就不会生效
@Test void selectById62() { User user = new User(); user.setUserName("Dem_one2"); user.setUserId("16"); user.setVersion(1); //提供操作之前的乐观锁 userMapper.updateById(user); }
注意:
- 当提供乐观锁字段时,每次操作数据乐观锁就会加一
-
6.2、并发测试
问题描述:
用户A和用户B同时秒杀一件物品
用户A操作时的乐观锁是1,然后操作之后乐观锁变为2,
当用户B去操作时这条数据的乐观锁已经是2了,但是他获取的乐观锁仍然是1,这样用户B就不能在对这条数据进行操作了
@Test
void selectById61() {
User userA = userMapper.selectById(13);//模拟用户A version=1
User userB = userMapper.selectById(13);//模拟用户B version=1
userA.setUserBalance(10000);
userMapper.updateById(userA); //操作之后是version=2
userB.setUserBalance(20000); //B用户拿着version=1去操作数据就会失败
userMapper.updateById(userB);
}